mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
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:
co-authored by
m4air
Claude Opus 5.5
parent
616e9c3e3a
commit
061cb226e2
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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,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 = {
|
||||
|
||||
@@ -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,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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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[] = []
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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']],
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user