Files
orca/src/main/serve-update-handoff.ts
T
mirror 34fa2cd5ca
Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
source snapshot 2026-09-02
2026-09-02 02:03:16 +08:00

118 lines
3.4 KiB
TypeScript

import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { app } from 'electron'
import {
SERVE_UPDATE_HANDOFF_PATH_ENV,
getServeUpdateHandoffPath,
parseServeUpdateHandoffState,
type ServeSupervisorMessage,
type ServeUpdateHandoffState
} from '../shared/serve-update-handoff'
import { getCanonicalUserDataPath } from './persistence'
function getConfiguredHandoffPath(): string | null {
const configuredPath = process.env[SERVE_UPDATE_HANDOFF_PATH_ENV]
if (!configuredPath) {
return null
}
const expectedPath = getServeUpdateHandoffPath(getCanonicalUserDataPath())
return resolve(configuredPath) === resolve(expectedPath) ? expectedPath : null
}
export function hasServeUpdateSupervisor(): boolean {
return process.platform === 'darwin' && getConfiguredHandoffPath() !== null
}
export function requestServeUpdateHandoff(targetVersion: string): boolean {
const handoffPath = getConfiguredHandoffPath()
if (!handoffPath || !targetVersion) {
return false
}
return writeHandoffState(handoffPath, {
schemaVersion: 1,
phase: 'install-requested',
fromVersion: app.getVersion(),
targetVersion,
servingPid: process.pid
})
}
export function failServeUpdateHandoff(reason: string): void {
const handoffPath = getConfiguredHandoffPath()
if (!handoffPath) {
return
}
try {
const state = parseServeUpdateHandoffState(JSON.parse(readFileSync(handoffPath, 'utf8')))
if (state?.phase !== 'install-requested' || state.servingPid !== process.pid) {
return
}
writeHandoffState(handoffPath, { ...state, phase: 'failed', reason })
} catch {
// The updater already reports the primary failure; a missing handoff file needs no second error.
}
}
export function notifyServeSupervisorReady(runtimeId: string): void {
if (!process.send || process.connected === false) {
return
}
const message: ServeSupervisorMessage = {
type: 'orca:serve-ready',
version: app.getVersion(),
runtimeId
}
try {
process.send(message)
} catch {
// The disconnect listener owns parent-loss recovery; readiness reporting must not throw through startup.
}
}
export function installServeSupervisorDisconnectQuit(
isServeMode: boolean,
parent: {
once(event: 'disconnect', listener: () => void): unknown
off(event: 'disconnect', listener: () => void): unknown
} = process
): () => void {
if (!isServeMode || !hasServeUpdateSupervisor()) {
return () => undefined
}
const quit = (): void => app.quit()
parent.once('disconnect', quit)
return () => parent.off('disconnect', quit)
}
export function getServeUpdateHandoffFailure(): string | null {
const handoffPath = getConfiguredHandoffPath()
if (!handoffPath) {
return null
}
try {
const state = parseServeUpdateHandoffState(JSON.parse(readFileSync(handoffPath, 'utf8')))
if (state?.phase !== 'failed') {
return null
}
if (state.targetVersion === app.getVersion()) {
unlinkSync(handoffPath)
return null
}
return state.reason
} catch {
return null
}
}
function writeHandoffState(path: string, state: ServeUpdateHandoffState): boolean {
const temporaryPath = `${path}.${process.pid}.tmp`
try {
mkdirSync(dirname(path), { recursive: true })
writeFileSync(temporaryPath, JSON.stringify(state), { mode: 0o600 })
renameSync(temporaryPath, path)
return true
} catch {
return false
}
}