diff --git a/config/scripts/verify-packaged-daemon-entry.cjs b/config/scripts/verify-packaged-daemon-entry.cjs index 8f5e802825c..006f5715012 100644 --- a/config/scripts/verify-packaged-daemon-entry.cjs +++ b/config/scripts/verify-packaged-daemon-entry.cjs @@ -4,7 +4,7 @@ const { join } = require('node:path') // Why: `asarUnpack` in config/electron-builder.config.cjs lists // out/main/daemon-entry.js on every platform, and the packaged daemon fork -// (src/main/daemon/daemon-init.ts) resolves exactly this unpacked path. A +// (src/main/daemon/daemon-launch-paths.ts) resolves exactly this unpacked path. A // missing entry means the package layout regressed, so the check throws // instead of skipping — a silent skip false-passed exactly the layout bug // this gate exists to catch. diff --git a/src/cli/runtime-client.ts b/src/cli/runtime-client.ts index f88b116981b..981759a005a 100644 --- a/src/cli/runtime-client.ts +++ b/src/cli/runtime-client.ts @@ -1,14 +1,11 @@ -// Why: the runtime client used to live here as a single file. It was split -// into ./runtime/{types,metadata,transport,status,launch,client}.ts so each -// concern can be tested in isolation. This barrel preserves the original -// import surface so call sites (src/cli/index.ts, tests) remain unchanged. +// Why: one import surface (and one vi.mock target) for CLI handlers over ./runtime/*. +export { RuntimeClient } from './runtime/client' +export { serveOrcaApp } from './runtime/launch' +export { getDefaultUserDataPath } from './runtime/metadata' export { - RuntimeClient, RuntimeClientError, RuntimeRpcFailureError, - serveOrcaApp, - getDefaultUserDataPath, type RuntimeRpcFailure, type RuntimeRpcResponse, type RuntimeRpcSuccess -} from './runtime/index' +} from './runtime/types' diff --git a/src/cli/runtime/index.ts b/src/cli/runtime/index.ts deleted file mode 100644 index 81d9fdf16c6..00000000000 --- a/src/cli/runtime/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { RuntimeClient } from './client' -export { serveOrcaApp } from './launch' -export { getDefaultUserDataPath } from './metadata' -export { - RuntimeClientError, - RuntimeRpcFailureError, - type RuntimeRpcFailure, - type RuntimeRpcResponse, - type RuntimeRpcSuccess -} from './types' diff --git a/src/cli/runtime/launch.ts b/src/cli/runtime/launch.ts index a4d24de8bd2..0b88807f827 100644 --- a/src/cli/runtime/launch.ts +++ b/src/cli/runtime/launch.ts @@ -108,9 +108,6 @@ function spawnDetached(command: string, args: string[], options: SpawnOptions): export function serveOrcaApp(args: ServeOrcaAppArgs = {}): Promise { const executable = resolveForegroundOrcaExecutable() - if (args.recipeJson && !args.projectRoot) { - throw new RuntimeClientError('invalid_argument', 'Recipe JSON output requires --project-root.') - } // Why one value: the selector, orcad and Electron must all serve the profile the caller chose. const userDataPath = resolveLaunchUserDataPath() // Why synchronous on the opt-out: it must spawn Electron exactly as before, without asking. diff --git a/src/main/daemon/daemon-init-fresh-import.ts b/src/main/daemon/daemon-init-fresh-import.ts index 39f3625c063..d63815e88ee 100644 --- a/src/main/daemon/daemon-init-fresh-import.ts +++ b/src/main/daemon/daemon-init-fresh-import.ts @@ -125,5 +125,11 @@ export async function importFreshDaemonInit(state: DaemonInitMockState) { getAppMetrics: () => [] } as unknown as AppEnvironment) // Why: import after resetModules so module-level spawner/adapter/restartInFlight start fresh — needed to test first-init and the coalescer. - return import('./daemon-init') + const [init, restart, providerState, cleanup] = await Promise.all([ + import('./daemon-provider-init'), + import('./daemon-provider-restart'), + import('./daemon-provider-state'), + import('./daemon-protocol-cleanup') + ]) + return { ...init, ...restart, ...providerState, ...cleanup } } diff --git a/src/main/daemon/daemon-init-wedged-daemon-grace.test.ts b/src/main/daemon/daemon-init-wedged-daemon-grace.test.ts index 9cd55946112..3c0f9ef8e43 100644 --- a/src/main/daemon/daemon-init-wedged-daemon-grace.test.ts +++ b/src/main/daemon/daemon-init-wedged-daemon-grace.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { WEDGED_DAEMON_GRACE_RETRIES } from './daemon-init' +import { WEDGED_DAEMON_GRACE_RETRIES } from './daemon-replacement-preflight' import { FAKE_RUNTIME_DIR } from './daemon-init-test-harness' import { DAEMON_RECOVERY_BUDGET_MS, diff --git a/src/main/daemon/daemon-init.ts b/src/main/daemon/daemon-init.ts deleted file mode 100644 index 6f0bd4eefc5..00000000000 --- a/src/main/daemon/daemon-init.ts +++ /dev/null @@ -1,23 +0,0 @@ -export { createLegacyDaemonAdapters } from './daemon-legacy-adapters' -export { initDaemonPtyProvider } from './daemon-provider-init' -export { restartDaemon, type RestartDaemonResult } from './daemon-provider-restart' -export { - daemonOwnsFreshPersistentPtys, - disconnectDaemon, - getCurrentDaemonMacTccAttributionHealth, - getDaemonEndpointFacts, - getDaemonProvider, - listLiveDaemonPtyIds, - listLiveDaemonSessions, - requestIdleDaemonRetirement, - releaseDaemonRetirementFence, - readDaemonPidRecord, - replaceDaemonProvider, - shutdownDaemon, - type DaemonEndpointFacts -} from './daemon-provider-state' -export { - cleanupDaemonForProtocol, - type OrphanedDaemonCleanupResult -} from './daemon-protocol-cleanup' -export { WEDGED_DAEMON_GRACE_RETRIES } from './daemon-replacement-preflight' diff --git a/src/main/daemon/daemon-pty-process-inspection.ts b/src/main/daemon/daemon-pty-process-inspection.ts index 335c641da62..0c10a2a2c85 100644 --- a/src/main/daemon/daemon-pty-process-inspection.ts +++ b/src/main/daemon/daemon-pty-process-inspection.ts @@ -1,6 +1,6 @@ import { isShellProcess } from '../../shared/agent-detection' import { DaemonPtyBufferSnapshots } from './daemon-pty-buffer-snapshots' -import { parsePtySessionId } from './pty-session-id' +import { parsePtySessionId } from '../../shared/pty-session-id-format' import { COMPLETION_PROCESS_INSPECTION_PROTOCOL_VERSION, GET_FOREGROUND_PROCESS_PROTOCOL_VERSION, diff --git a/src/main/daemon/daemon-pty-session-inventory.ts b/src/main/daemon/daemon-pty-session-inventory.ts index 39d0bdafcce..7f1a4f6cc47 100644 --- a/src/main/daemon/daemon-pty-session-inventory.ts +++ b/src/main/daemon/daemon-pty-session-inventory.ts @@ -11,7 +11,7 @@ import { recordAuthenticatedInventory } from './daemon-audit-classifier' import { isMissingWindowsNamedPipeError } from './daemon-endpoint-errors' import { DaemonPtyProcessInspection } from './daemon-pty-process-inspection' import { remainingDaemonRequestTimeoutMs } from './daemon-request-deadline' -import { parsePtySessionId } from './pty-session-id' +import { parsePtySessionId } from '../../shared/pty-session-id-format' import type { ListSessionsResult, SessionInfo } from './types' import { PtyProcessListAdmission } from '../providers/pty-process-list-admission' import type { PtyProcessInfo } from '../providers/types' diff --git a/src/main/daemon/pty-session-id.test.ts b/src/main/daemon/pty-session-id.test.ts index c8e7077c8a0..6d8f7c1b8ea 100644 --- a/src/main/daemon/pty-session-id.test.ts +++ b/src/main/daemon/pty-session-id.test.ts @@ -2,9 +2,9 @@ import { describe, expect, it } from 'vitest' import { isSafePtySessionId, mintPtySessionId, - parsePtySessionId, ptySessionIdForAgentCreateOperation } from './pty-session-id' +import { parsePtySessionId } from '../../shared/pty-session-id-format' const USER_DATA = '/tmp/orca-userdata' diff --git a/src/main/daemon/pty-session-id.ts b/src/main/daemon/pty-session-id.ts index 69324104af6..dab9c6ab3c3 100644 --- a/src/main/daemon/pty-session-id.ts +++ b/src/main/daemon/pty-session-id.ts @@ -2,12 +2,6 @@ import { randomUUID } from 'node:crypto' import { isAbsolute, join, relative, resolve, sep } from 'node:path' import { PTY_SESSION_ID_SEPARATOR } from '../../shared/pty-session-id-format' -// Why: re-exported here so main-side callers can keep importing -// `parsePtySessionId` from this module (next to `mintPtySessionId`). The -// implementation lives in `src/shared/` because the renderer-side merge -// helper also needs it and cannot import node-only modules. -export { parsePtySessionId } from '../../shared/pty-session-id-format' - /** * Session IDs use the format `${worktreeId}@@${shortUuid}` so that * DaemonPtyAdapter.reconcileOnStartup (see daemon-pty-adapter.ts) can diff --git a/src/main/daemon/pty-subprocess/foreground-process-tracker.ts b/src/main/daemon/pty-subprocess/foreground-process-tracker.ts index 7a96877c204..1a7277903c3 100644 --- a/src/main/daemon/pty-subprocess/foreground-process-tracker.ts +++ b/src/main/daemon/pty-subprocess/foreground-process-tracker.ts @@ -23,7 +23,7 @@ import { } from '../../../shared/foreground-wrapper-agent' import { isShellProcess } from '../../../shared/shell-process-detection' import { resolveFallbackForegroundProcess } from './foreground-fallback-process' -import { parsePtySessionId } from '../pty-session-id' +import { parsePtySessionId } from '../../../shared/pty-session-id-format' import { ptyProcessNameIsSpawnFile, createPtyForegroundResolver, diff --git a/src/main/daemon/wsl-session-context.ts b/src/main/daemon/wsl-session-context.ts index 0cf419872f9..a594d6d9344 100644 --- a/src/main/daemon/wsl-session-context.ts +++ b/src/main/daemon/wsl-session-context.ts @@ -1,7 +1,7 @@ import { splitWorktreeIdForFilesystem } from '../../shared/worktree/id' import { isWslShellName } from '../../shared/local-windows-terminal-runtime' import { getDefaultWslDistro, parseWslPath } from '../wsl' -import { parsePtySessionId } from './pty-session-id' +import { parsePtySessionId } from '../../shared/pty-session-id-format' import { parseWslUncPath } from '../../shared/wsl-paths' export type WslSessionContext = { diff --git a/src/main/ipc/pty-management.test.ts b/src/main/ipc/pty-management.test.ts index 0e46f2ab6bb..17787a3c5a0 100644 --- a/src/main/ipc/pty-management.test.ts +++ b/src/main/ipc/pty-management.test.ts @@ -45,9 +45,11 @@ vi.mock('../daemon/daemon-folder-access-reset', () => ({ resetFolderAccessForDaemon: resetFolderAccessForDaemonMock })) -vi.mock('../daemon/daemon-init', () => ({ +vi.mock('../daemon/daemon-provider-restart', () => ({ + restartDaemon: restartDaemonMock +})) +vi.mock('../daemon/daemon-provider-state', () => ({ getDaemonProvider: getDaemonProviderMock, - restartDaemon: restartDaemonMock, getCurrentDaemonMacTccAttributionHealth: getCurrentDaemonMacTccAttributionHealthMock })) diff --git a/src/main/ipc/pty-management.ts b/src/main/ipc/pty-management.ts index 2503ced8bbd..871545d8adf 100644 --- a/src/main/ipc/pty-management.ts +++ b/src/main/ipc/pty-management.ts @@ -3,9 +3,9 @@ import { DegradedDaemonPtyProvider } from '../daemon/degraded-daemon-pty-provide import type { DaemonPtyAdapter } from '../daemon/daemon-pty-adapter' import { getCurrentDaemonMacTccAttributionHealth, - getDaemonProvider, - restartDaemon -} from '../daemon/daemon-init' + getDaemonProvider +} from '../daemon/daemon-provider-state' +import { restartDaemon } from '../daemon/daemon-provider-restart' import { getAllDaemonAdapters, getCurrentDaemonAdapter } from '../daemon/daemon-provider-routing' import { getDaemonFolderAccessMismatch, diff --git a/src/main/local-builds/local-build-compatibility.ts b/src/main/local-builds/local-build-compatibility.ts index 482d7a4f643..f36fcf4678c 100644 --- a/src/main/local-builds/local-build-compatibility.ts +++ b/src/main/local-builds/local-build-compatibility.ts @@ -4,7 +4,7 @@ import { type LocalBuildCompatibility } from '../../shared/local-build-compatibility' import { DegradedDaemonPtyProvider } from '../daemon/degraded-daemon-pty-provider' -import { getDaemonProvider } from '../daemon/daemon-init' +import { getDaemonProvider } from '../daemon/daemon-provider-state' import { getAllDaemonAdapters } from '../daemon/daemon-provider-routing' import { getLocalPtyProvider } from '../ipc/pty' import { LocalPtyProvider } from '../providers/local-pty-provider' diff --git a/src/main/memory/hydrate-local-pty-registry-folder-workspaces.test.ts b/src/main/memory/hydrate-local-pty-registry-folder-workspaces.test.ts index c67e4772bee..2464817783d 100644 --- a/src/main/memory/hydrate-local-pty-registry-folder-workspaces.test.ts +++ b/src/main/memory/hydrate-local-pty-registry-folder-workspaces.test.ts @@ -4,7 +4,7 @@ import type { SessionInfo } from '../daemon/types' import type { Store } from '../persistence' const getDaemonProviderMock = vi.fn() -vi.mock('../daemon/daemon-init', () => ({ +vi.mock('../daemon/daemon-provider-state', () => ({ getDaemonProvider: () => getDaemonProviderMock() })) vi.mock('../project-runtime-git-options', () => ({ diff --git a/src/main/memory/hydrate-local-pty-registry.test.ts b/src/main/memory/hydrate-local-pty-registry.test.ts index a9e8e312a75..02094b17ac3 100644 --- a/src/main/memory/hydrate-local-pty-registry.test.ts +++ b/src/main/memory/hydrate-local-pty-registry.test.ts @@ -29,7 +29,7 @@ const LARGE_SESSION_COUNT = 150_000 // getter. Stubbing it lets us drive the offline / throwing / live paths // without spinning up real sockets. const getDaemonProviderMock = vi.fn() -vi.mock('../daemon/daemon-init', () => ({ +vi.mock('../daemon/daemon-provider-state', () => ({ getDaemonProvider: () => getDaemonProviderMock() })) diff --git a/src/main/memory/hydrate-local-pty-registry.ts b/src/main/memory/hydrate-local-pty-registry.ts index fa1c176728a..9e4e97bceb2 100644 --- a/src/main/memory/hydrate-local-pty-registry.ts +++ b/src/main/memory/hydrate-local-pty-registry.ts @@ -5,7 +5,7 @@ import { parsePtySessionId } from '../../shared/pty-session-id-format' import { isFolderRepo } from '../../shared/repo-kind' import type { Repo } from '../../shared/repo-types' import { splitWorktreeId, worktreeIdComparisonKey } from '../../shared/worktree/id' -import { getDaemonProvider } from '../daemon/daemon-init' +import { getDaemonProvider } from '../daemon/daemon-provider-state' import { getAllDaemonAdapters, type DaemonProvider } from '../daemon/daemon-provider-routing' import type { SessionInfo } from '../daemon/types' import { isFolderWorkspaceIdForRepo } from '../ipc/worktrees/folder-workspace-model' diff --git a/src/main/orcad/orcad-command-arguments.ts b/src/main/orcad/orcad-command-arguments.ts index 03e4eb0b128..2f9ca1d4519 100644 --- a/src/main/orcad/orcad-command-arguments.ts +++ b/src/main/orcad/orcad-command-arguments.ts @@ -1,6 +1,7 @@ import type { OrcadOptions } from './orcad-entry' import { DEFAULT_WS_PORT } from '../runtime/runtime-rpc/runtime-rpc-pairing-types' import { ORCAD_LOOPBACK_BIND_HOST } from './orcad-bind-address' +import { getServeOptionValidationError } from '../../shared/serve-option-validation' export const ORCAD_USAGE = `orcad — the Orca runtime served without Electron. @@ -82,11 +83,15 @@ export function parseArgs(argv: string[]): OrcadCommandArguments { throw new Error(`Unknown argument: ${arg}`) } } - if (options.grantDesktopControl && (options.noPairing || options.mobilePairing)) { - throw new Error('--grant-desktop-control applies only to the default runtime pairing offer') - } - if (options.recipeJson && !options.projectRoot) { - throw new Error('--recipe-json requires --project-root') + const validationError = getServeOptionValidationError({ + noPairing: options.noPairing === true, + mobilePairing: options.mobilePairing === true, + grantDesktopControl: options.grantDesktopControl, + recipeJson: options.recipeJson === true, + projectRoot: options.projectRoot + }) + if (validationError) { + throw new Error(validationError) } return options } diff --git a/src/main/orcad/orcad-daemon-retirement.test.ts b/src/main/orcad/orcad-daemon-retirement.test.ts index 3c952cf9b6a..5414f4000c0 100644 --- a/src/main/orcad/orcad-daemon-retirement.test.ts +++ b/src/main/orcad/orcad-daemon-retirement.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' -vi.mock('../daemon/daemon-init', () => ({ +vi.mock('../daemon/daemon-provider-state', () => ({ requestIdleDaemonRetirement: vi.fn(), listLiveDaemonSessions: vi.fn(), releaseDaemonRetirementFence: vi.fn() diff --git a/src/main/orcad/orcad-daemon-retirement.ts b/src/main/orcad/orcad-daemon-retirement.ts index 0cc73c99e52..70cc62745d9 100644 --- a/src/main/orcad/orcad-daemon-retirement.ts +++ b/src/main/orcad/orcad-daemon-retirement.ts @@ -9,7 +9,7 @@ import { listLiveDaemonSessions, releaseDaemonRetirementFence, requestIdleDaemonRetirement -} from '../daemon/daemon-init' +} from '../daemon/daemon-provider-state' import type { DaemonIdleRetirementResult } from '../daemon/daemon-pty-runtime-state' import type { OrcadDaemonRetirementVerdict } from '../../shared/orcad-stop-request' import { withTimeout } from '../../shared/promise-timeout-fallback' diff --git a/src/main/orcad/orcad-daemon-supervision.test.ts b/src/main/orcad/orcad-daemon-supervision.test.ts index d301b715a9e..562717c0783 100644 --- a/src/main/orcad/orcad-daemon-supervision.test.ts +++ b/src/main/orcad/orcad-daemon-supervision.test.ts @@ -14,8 +14,10 @@ const { readDaemonPidRecordMock: vi.fn<() => { pid: number } | null>() })) -vi.mock('../daemon/daemon-init', () => ({ - initDaemonPtyProvider: initDaemonPtyProviderMock, +vi.mock('../daemon/daemon-provider-init', () => ({ + initDaemonPtyProvider: initDaemonPtyProviderMock +})) +vi.mock('../daemon/daemon-provider-state', () => ({ disconnectDaemon: disconnectDaemonMock, // Exported here purely so the test can prove it is never reached — killing the daemon on // orcad shutdown is what would make an orcad restart destructive again. diff --git a/src/main/orcad/orcad-daemon-supervision.ts b/src/main/orcad/orcad-daemon-supervision.ts index 79ec4f3ed28..6a87db81f70 100644 --- a/src/main/orcad/orcad-daemon-supervision.ts +++ b/src/main/orcad/orcad-daemon-supervision.ts @@ -14,9 +14,9 @@ import { disconnectDaemon, daemonOwnsFreshPersistentPtys, - initDaemonPtyProvider, readDaemonPidRecord -} from '../daemon/daemon-init' +} from '../daemon/daemon-provider-state' +import { initDaemonPtyProvider } from '../daemon/daemon-provider-init' import { errorMessage } from '../../shared/error-message' const WINDOWS_DAEMON_STARTUP_TIMEOUT_MS = 30_000 diff --git a/src/main/orcad/orcad-entry.ts b/src/main/orcad/orcad-entry.ts index 5d8eb854b7c..9a04f531731 100644 --- a/src/main/orcad/orcad-entry.ts +++ b/src/main/orcad/orcad-entry.ts @@ -169,7 +169,7 @@ async function startOrcadRuntime( const { buildOrcadServeReadiness } = await import('./orcad-serve-readiness') const { createOrcadProfileStateStartup } = await import('./orcad-profile-state-startup') const { startOrcadDaemon, stopOrcadDaemon } = await import('./orcad-daemon-supervision') - const { daemonOwnsFreshPersistentPtys } = await import('../daemon/daemon-init') + const { daemonOwnsFreshPersistentPtys } = await import('../daemon/daemon-provider-state') const { collectOrcadHealth } = await import('./orcad-health') // Why importable here: the singleton's module tree never reaches Electron, and orcad supplies // its persistence and endpoint paths explicitly below. diff --git a/src/main/orcad/orcad-exit-code.ts b/src/main/orcad/orcad-exit-code.ts index 6ae468fe936..8259c5f7f5f 100644 --- a/src/main/orcad/orcad-exit-code.ts +++ b/src/main/orcad/orcad-exit-code.ts @@ -1,6 +1,6 @@ import { OrcadBindAddressError } from './orcad-bind-address' import { OrcadBundledRuntimeError } from './orcad-bundled-runtime' -import { OrcadInstanceLockError } from './orcad-instance-lock' +import { OrcadInstanceLockError } from './orcad-data-root-privacy' import { ProfileStateAccessError } from '../persistence/profile-state/profile-state-access' import { ORCAD_EXIT_CONFIGURATION } from './orcad-configuration-exit' diff --git a/src/main/orcad/orcad-health.test.ts b/src/main/orcad/orcad-health.test.ts index 4f75c7c9025..dd581a02b56 100644 --- a/src/main/orcad/orcad-health.test.ts +++ b/src/main/orcad/orcad-health.test.ts @@ -26,7 +26,7 @@ vi.mock('../daemon/daemon-health', () => ({ reportedCoverage.value.shift() ?? (process.platform === 'win32' ? 'handshake' : 'pty-spawn') }) })) -vi.mock('../daemon/daemon-init', () => ({ +vi.mock('../daemon/daemon-provider-state', () => ({ getDaemonEndpointFacts: getDaemonEndpointFactsMock, readDaemonPidRecord: readDaemonPidRecordMock, daemonOwnsFreshPersistentPtys: daemonOwnsFreshPersistentPtysMock diff --git a/src/main/orcad/orcad-health.ts b/src/main/orcad/orcad-health.ts index d8cf2f705a2..4f891395f6e 100644 --- a/src/main/orcad/orcad-health.ts +++ b/src/main/orcad/orcad-health.ts @@ -16,7 +16,7 @@ import { daemonOwnsFreshPersistentPtys, getDaemonEndpointFacts, readDaemonPidRecord -} from '../daemon/daemon-init' +} from '../daemon/daemon-provider-state' import type { OrcadProfileStateAuthoritySelection } from './orcad-profile-state-telemetry' import { ORCAD_STOP_REQUESTS_CAPABILITY } from '../../shared/orcad-stop-request' import type { OrcadIdleStopRecord } from '../../shared/orcad-idle-exit' diff --git a/src/main/orcad/orcad-instance-lock.test.ts b/src/main/orcad/orcad-instance-lock.test.ts index 36dc3700f23..0d97ccbdaf8 100644 --- a/src/main/orcad/orcad-instance-lock.test.ts +++ b/src/main/orcad/orcad-instance-lock.test.ts @@ -16,10 +16,10 @@ import { afterEach, describe, expect, it } from 'vitest' import { acquireOrcadInstanceLock, ORCAD_LOCK_FILE_NAME, - OrcadInstanceLockError, type OrcadInstanceLockHooks, type OrcadLockRecord } from './orcad-instance-lock' +import { OrcadInstanceLockError } from './orcad-data-root-privacy' const roots: string[] = [] diff --git a/src/main/orcad/orcad-instance-lock.ts b/src/main/orcad/orcad-instance-lock.ts index 72fdb8263b7..ab152d2a2d8 100644 --- a/src/main/orcad/orcad-instance-lock.ts +++ b/src/main/orcad/orcad-instance-lock.ts @@ -35,8 +35,6 @@ const MAX_ORCAD_LOCK_BYTES = 64 * 1024 // A writer finishes in milliseconds; a garbled record older than this has no live writer. const GARBLED_LOCK_GRACE_MS = 10_000 -export { OrcadInstanceLockError, type OrcadInstanceLockCode } from './orcad-data-root-privacy' - const OrcadLockRecordSchema = z.object({ pid: z.number().int().positive().max(Number.MAX_SAFE_INTEGER), /** Null where the platform cannot read it; PID alone is then the (weaker) fence. */ diff --git a/src/main/orcad/orcad-launch-contract.test.ts b/src/main/orcad/orcad-launch-contract.test.ts index 93937ef49b7..09241e5372e 100644 --- a/src/main/orcad/orcad-launch-contract.test.ts +++ b/src/main/orcad/orcad-launch-contract.test.ts @@ -15,7 +15,7 @@ import { readAgentSessionRuntimeEnds } from '../runtime/agent-session-runtime-end-record' import { OrcadBindAddressError } from './orcad-bind-address' -import { OrcadInstanceLockError } from './orcad-instance-lock' +import { OrcadInstanceLockError } from './orcad-data-root-privacy' import { ProfileStateAccessError } from '../persistence/profile-state/profile-state-access' import { OrcadBundledRuntimeError } from './orcad-bundled-runtime' @@ -42,7 +42,7 @@ describe('parseArgs', () => { expect(() => parseArgs(['--help', '--port', 'bad'])).toThrow('--port expects an integer') expect(() => parseArgs(['--help', '--bind'])).toThrow('--bind expects a value') expect(() => parseArgs(['--help', '--recipe-json'])).toThrow( - '--recipe-json requires --project-root' + 'Recipe JSON output requires --project-root.' ) for (const flag of ['--no-pairing', '--mobile-pairing']) { expect(() => parseArgs(['--help', '--grant-desktop-control', flag])).toThrow( @@ -62,24 +62,29 @@ describe('parseArgs', () => { }) it('takes the desktop serve flags orca serve forwards', () => { - expect( - parseArgs([ - '--mobile-pairing', - '--recipe-json', - '--project-root', - '/work/app', - '--no-pairing' - ]) - ).toEqual({ - mobilePairing: true, + expect(parseArgs(['--recipe-json', '--project-root', '/work/app'])).toEqual({ recipeJson: true, - projectRoot: '/work/app', - noPairing: true + projectRoot: '/work/app' }) - expect(() => parseArgs(['--recipe-json'])).toThrow('--recipe-json requires --project-root') + expect(parseArgs(['--mobile-pairing'])).toEqual({ mobilePairing: true }) + expect(parseArgs(['--no-pairing'])).toEqual({ noPairing: true }) + expect(() => parseArgs(['--recipe-json'])).toThrow( + 'Recipe JSON output requires --project-root.' + ) expect(() => parseArgs(['--project-root'])).toThrow('--project-root expects a value') }) + it('rejects contradictory pairing flags with the desktop serve message', () => { + expect(() => parseArgs(['--no-pairing', '--mobile-pairing'])).toThrow( + 'Use either --mobile-pairing or --no-pairing, not both.' + ) + for (const flag of ['--no-pairing', '--mobile-pairing']) { + expect(() => parseArgs(['--recipe-json', '--project-root', '/work/app', flag])).toThrow( + `Recipe JSON output requires runtime pairing; remove ${flag}.` + ) + } + }) + it('rejects --bind with no value rather than silently binding the default', () => { expect(() => parseArgs(['--bind'])).toThrow('--bind expects a value') expect(() => parseArgs(['--bind', '--json'])).not.toThrow() diff --git a/src/main/orcad/orcad-managed-idle-exit-host.ts b/src/main/orcad/orcad-managed-idle-exit-host.ts index 7ea30102d18..f7a6d455d3b 100644 --- a/src/main/orcad/orcad-managed-idle-exit-host.ts +++ b/src/main/orcad/orcad-managed-idle-exit-host.ts @@ -63,7 +63,7 @@ async function startOrcadManagedIdleExit( } ): Promise { const { getLocalPtyProvider } = await import('../ipc/pty') - const { getDaemonEndpointFacts } = await import('../daemon/daemon-init') + const { getDaemonEndpointFacts } = await import('../daemon/daemon-provider-state') const { countLiveOrcadDaemonSessions, retireOrcadDaemonIfIdle } = await import('./orcad-daemon-retirement') const idleRecord = createIdleStopRecordOwnership(input.userDataPath) diff --git a/src/main/orcad/orcad-push-startup.test.ts b/src/main/orcad/orcad-push-startup.test.ts index b2349b560b1..140def0eb6a 100644 --- a/src/main/orcad/orcad-push-startup.test.ts +++ b/src/main/orcad/orcad-push-startup.test.ts @@ -79,7 +79,7 @@ vi.mock('./orcad-automations', () => ({ vi.mock('../runtime/agent-state-rules/agent-state-rules-live-update', () => ({ startAgentStateRulesLiveUpdates: () => {} })) -vi.mock('../daemon/daemon-init', () => ({ daemonOwnsFreshPersistentPtys: () => false })) +vi.mock('../daemon/daemon-provider-state', () => ({ daemonOwnsFreshPersistentPtys: () => false })) vi.mock('../ipc/pty', () => ({ registerHeadlessPtyRuntime: async () => {}, getLocalPtyProvider: () => null, diff --git a/src/main/startup/desktop-profile-instance-lock.test.ts b/src/main/startup/desktop-profile-instance-lock.test.ts index 8bf0ec121cf..938ff74d174 100644 --- a/src/main/startup/desktop-profile-instance-lock.test.ts +++ b/src/main/startup/desktop-profile-instance-lock.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from 'vitest' -import { OrcadInstanceLockError, type OrcadInstanceLock } from '../orcad/orcad-instance-lock' +import type { OrcadInstanceLock } from '../orcad/orcad-instance-lock' +import { OrcadInstanceLockError } from '../orcad/orcad-data-root-privacy' import { acquireDesktopProfileInstanceLock } from './desktop-profile-instance-lock' describe('the desktop share of the profile instance lock', () => { diff --git a/src/main/startup/desktop-profile-instance-lock.ts b/src/main/startup/desktop-profile-instance-lock.ts index 076e5b627dc..b16c5c4563a 100644 --- a/src/main/startup/desktop-profile-instance-lock.ts +++ b/src/main/startup/desktop-profile-instance-lock.ts @@ -5,11 +5,8 @@ * Electron's single-instance lock only fences other Electron launches; orcad is a plain Node * process that cannot see it. Sharing orcad's lock file gives both hosts one owner record. */ -import { - acquireOrcadInstanceLock, - OrcadInstanceLockError, - type OrcadInstanceLock -} from '../orcad/orcad-instance-lock' +import { acquireOrcadInstanceLock, type OrcadInstanceLock } from '../orcad/orcad-instance-lock' +import { OrcadInstanceLockError } from '../orcad/orcad-data-root-privacy' import { writeStartupDiagnosticLine, type StartupDiagnosticSink } from './startup-diagnostics' export type DesktopProfileLockResult = diff --git a/src/main/startup/main-process-pty-startup.ts b/src/main/startup/main-process-pty-startup.ts index 3e76bb855de..20136d4e9d5 100644 --- a/src/main/startup/main-process-pty-startup.ts +++ b/src/main/startup/main-process-pty-startup.ts @@ -2,11 +2,8 @@ import { app } from 'electron' import { classifyError } from '../telemetry/classify-error' import { track } from '../telemetry/client' import { getPtyIdForPaneKey } from '../ipc/pty' -import { - getDaemonProvider, - initDaemonPtyProvider, - listLiveDaemonPtyIds -} from '../daemon/daemon-init' +import { getDaemonProvider, listLiveDaemonPtyIds } from '../daemon/daemon-provider-state' +import { initDaemonPtyProvider } from '../daemon/daemon-provider-init' import { getCodexPaneAccount, hasAnyRecordedLegacyWslCodexPane, diff --git a/src/main/startup/main-process-quit-pending-writer.test.ts b/src/main/startup/main-process-quit-pending-writer.test.ts index cd2ddf84062..8bf383b8adf 100644 --- a/src/main/startup/main-process-quit-pending-writer.test.ts +++ b/src/main/startup/main-process-quit-pending-writer.test.ts @@ -11,7 +11,7 @@ const dependencies: Record> = { '../ipc/worktree-base-directory-watcher': { disposeWorktreeBaseDirectoryWatchers: settled() }, '../ipc/folder-repo-git-upgrade': { stopFolderRepoGitUpgradeWatch: idle() }, '../ipc/pty': { killAllPty: idle() }, - '../daemon/daemon-init': { disconnectDaemon: settled(), shutdownDaemon: settled() }, + '../daemon/daemon-provider-state': { disconnectDaemon: settled(), shutdownDaemon: settled() }, '../ipc/ssh-shutdown-drain': { beginSshShutdown: settled() }, '../agent-hooks/server': { agentHookServer: { stop: idle() } }, '../agent-hooks/wsl-hook-relay-manager': { wslHookRelayManager: { disposeAll: idle() } }, diff --git a/src/main/startup/main-process-quit-update-veto.test.ts b/src/main/startup/main-process-quit-update-veto.test.ts index ea46417fe40..4164f2b039a 100644 --- a/src/main/startup/main-process-quit-update-veto.test.ts +++ b/src/main/startup/main-process-quit-update-veto.test.ts @@ -6,7 +6,7 @@ const dependencyExports: [string, string[]][] = [ ['../ipc/worktree-base-directory-watcher', ['disposeWorktreeBaseDirectoryWatchers']], ['../ipc/folder-repo-git-upgrade', ['stopFolderRepoGitUpgradeWatch']], ['../ipc/pty', ['killAllPty']], - ['../daemon/daemon-init', ['disconnectDaemon', 'shutdownDaemon']], + ['../daemon/daemon-provider-state', ['disconnectDaemon', 'shutdownDaemon']], ['../ipc/ssh-shutdown-drain', ['beginSshShutdown']], ['../agent-hooks/server', ['agentHookServer']], ['../agent-hooks/wsl-hook-relay-manager', ['wslHookRelayManager']], diff --git a/src/main/startup/main-process-quit.ts b/src/main/startup/main-process-quit.ts index eb2f35bfa99..7e7af4c773e 100644 --- a/src/main/startup/main-process-quit.ts +++ b/src/main/startup/main-process-quit.ts @@ -3,7 +3,7 @@ import { closeAllWatchers } from '../ipc/filesystem-watcher' import { disposeWorktreeBaseDirectoryWatchers } from '../ipc/worktree-base-directory-watcher' import { stopFolderRepoGitUpgradeWatch } from '../ipc/folder-repo-git-upgrade' import { killAllPty } from '../ipc/pty' -import { disconnectDaemon, shutdownDaemon } from '../daemon/daemon-init' +import { disconnectDaemon, shutdownDaemon } from '../daemon/daemon-provider-state' import { beginSshShutdown } from '../ipc/ssh-shutdown-drain' import { agentHookServer } from '../agent-hooks/server' import { wslHookRelayManager } from '../agent-hooks/wsl-hook-relay-manager' diff --git a/src/main/startup/main-process-runtime-service.ts b/src/main/startup/main-process-runtime-service.ts index 61e36137688..a003e5b42cc 100644 --- a/src/main/startup/main-process-runtime-service.ts +++ b/src/main/startup/main-process-runtime-service.ts @@ -16,7 +16,7 @@ import { prepareCodexAiVaultSessionResume } from '../codex/codex-ai-vault-sessio import { prepareCodexPinnedLaunchHome } from './codex-session-resume-launch' import { resolveHostCodexSessionSourceHome } from '../codex/codex-session-source-home' import { isAgentStatusHooksEnabled } from '../agent-hooks/managed-agent-hook-controls' -import { getDaemonProvider } from '../daemon/daemon-init' +import { getDaemonProvider } from '../daemon/daemon-provider-state' import type { TerminalSideEffectBatch } from '../../shared/terminal-side-effect-facts' import type { OrchestrationEnvironmentTransport } from '../runtime/orchestration/environment-transport' import { resolveEnvironment } from '../../shared/runtime-environment-store' diff --git a/src/shared/daemon-lifecycle-telemetry.ts b/src/shared/daemon-lifecycle-telemetry.ts index 88b6f8f287f..afa9c71ec7c 100644 --- a/src/shared/daemon-lifecycle-telemetry.ts +++ b/src/shared/daemon-lifecycle-telemetry.ts @@ -3,7 +3,7 @@ // subprocess and never crosses into the app, so every reason here is one the app // itself decides: a startup replace, or an observed death→respawn. -// Startup launcher replaced a still-connectable daemon (each maps 1:1 to a `daemon-init.ts` decision). +// Startup launcher replaced a still-connectable daemon (each maps 1:1 to a `daemon-replacement-preflight.ts` decision). export const DAEMON_REPLACE_REASONS = [ 'unhealthy_resolver', 'stale_bundle', diff --git a/tests/e2e/fixtures/daemon-generation-desktop-discovery.ts b/tests/e2e/fixtures/daemon-generation-desktop-discovery.ts index 92743a443dd..e208ded947b 100644 --- a/tests/e2e/fixtures/daemon-generation-desktop-discovery.ts +++ b/tests/e2e/fixtures/daemon-generation-desktop-discovery.ts @@ -1,5 +1,5 @@ import { DaemonPtyAdapter } from '../../../src/main/daemon/daemon-pty-adapter' -import { createLegacyDaemonAdapters } from '../../../src/main/daemon/daemon-init' +import { createLegacyDaemonAdapters } from '../../../src/main/daemon/daemon-legacy-adapters' import { DaemonPtyRouter } from '../../../src/main/daemon/daemon-pty-router' export type DesktopDiscoveryGeneration = { diff --git a/tests/tools/win-update-e2e/daemon-processes.mjs b/tests/tools/win-update-e2e/daemon-processes.mjs index d292c0f24b8..950068a6de4 100644 --- a/tests/tools/win-update-e2e/daemon-processes.mjs +++ b/tests/tools/win-update-e2e/daemon-processes.mjs @@ -5,7 +5,7 @@ // matched by executable name. The only reliable discriminators are the // command-line markers the fork always passes: the daemon entry script // (daemon-entry.js) and its --socket / --token arguments. See -// src/main/daemon/daemon-init.ts (createOutOfProcessLauncher). +// src/main/daemon/daemon-out-of-process-launcher.ts (createOutOfProcessLauncher). // // The daemon also writes a PID file at /daemon/daemon-v.pid whose // JSON carries { pid, startedAtMs, entryPath, appVersion } — the appVersion