refactor(orcad): shared serve-option validation; no daemon barrel (#27443)

orcad now checks its flags with the same validator Electron serve and the
CLI use, so contradictory pairing flags fail with the same message instead
of one silently winning. The CLI launcher drops its duplicate recipe check
(the CLI handler already ran the shared validator).

Removes the daemon-init and cli/runtime/index re-export barrels and the
parsePtySessionId / OrcadInstanceLockError re-exports; callers and mocks
import the defining modules.

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
OrcaWin
2026-10-11 03:58:58 -07:00
committed by GitHub
co-authored by m4air Claude Opus 5.5
parent 616e9c3e3a
commit 061cb226e2
43 changed files with 88 additions and 120 deletions
@@ -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.
+5 -8
View File
@@ -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'
-10
View File
@@ -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'
-3
View File
@@ -108,9 +108,6 @@ function spawnDetached(command: string, args: string[], options: SpawnOptions):
export function serveOrcaApp(args: ServeOrcaAppArgs = {}): Promise<number> {
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.
+7 -1
View File
@@ -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 }
}
@@ -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,
-23
View File
@@ -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'
@@ -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,
@@ -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'
+1 -1
View File
@@ -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'
-6
View File
@@ -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
@@ -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,
+1 -1
View File
@@ -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 = {
+4 -2
View File
@@ -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
}))
+3 -3
View File
@@ -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,
@@ -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'
@@ -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', () => ({
@@ -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()
}))
@@ -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'
+10 -5
View File
@@ -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
}
@@ -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()
+1 -1
View File
@@ -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'
@@ -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.
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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[] = []
-2
View File
@@ -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. */
+20 -15
View File
@@ -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()
@@ -63,7 +63,7 @@ async function startOrcadManagedIdleExit(
}
): Promise<void> {
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)
+1 -1
View File
@@ -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,
@@ -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', () => {
@@ -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 =
+2 -5
View File
@@ -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,
@@ -11,7 +11,7 @@ const dependencies: Record<string, Record<string, unknown>> = {
'../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() } },
@@ -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']],
+1 -1
View File
@@ -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'
@@ -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'
+1 -1
View File
@@ -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',
@@ -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 = {
@@ -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 <userData>/daemon/daemon-v<N>.pid whose
// JSON carries { pid, startedAtMs, entryPath, appVersion } — the appVersion