mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 00:03:52 +00:00
* fix(daemon): detect severed macOS TCC attribution and surface daemon-restart remedy (STA-3491) macOS pins the detached PTY daemon's TCC responsible process to the app binary that forked it. Once that binary is deleted (packaged updates replace the bundle), Accessibility/Automation grants on Orca silently stop covering every daemon-hosted terminal: osascript/System Events fails with -25211 no matter what the user grants. - record spawnerExecPath in the daemon pid file at fork - adoption checks it: severed + 0 live sessions -> replace the daemon (reason severed_tcc_attribution); live sessions are preserved - Settings (Developer Permissions + Manage Sessions) show a visible banner pointing at Manage Sessions -> Restart while severed * fix(daemon): harden TCC attribution recovery
141 lines
3.7 KiB
TypeScript
141 lines
3.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { parseArgs } from './daemon-entry'
|
|
|
|
describe('daemon-entry parseArgs', () => {
|
|
it('parses --socket and --token flags', () => {
|
|
const result = parseArgs(['--socket', '/tmp/test.sock', '--token', '/tmp/test.token'])
|
|
expect(result).toEqual({
|
|
socketPath: '/tmp/test.sock',
|
|
tokenPath: '/tmp/test.token'
|
|
})
|
|
})
|
|
|
|
it('handles flags in any order', () => {
|
|
const result = parseArgs(['--token', '/tmp/t.token', '--socket', '/tmp/t.sock'])
|
|
expect(result).toEqual({
|
|
socketPath: '/tmp/t.sock',
|
|
tokenPath: '/tmp/t.token'
|
|
})
|
|
})
|
|
|
|
it('throws when --socket is missing', () => {
|
|
expect(() => parseArgs(['--token', '/tmp/t.token'])).toThrow('Usage:')
|
|
})
|
|
|
|
it('throws when --token is missing', () => {
|
|
expect(() => parseArgs(['--socket', '/tmp/t.sock'])).toThrow('Usage:')
|
|
})
|
|
|
|
it('throws with no args', () => {
|
|
expect(() => parseArgs([])).toThrow('Usage:')
|
|
})
|
|
|
|
it('omits logFilePath when --log-file is absent (adopted old daemons)', () => {
|
|
const result = parseArgs(['--socket', '/tmp/t.sock', '--token', '/tmp/t.token'])
|
|
expect(result).not.toHaveProperty('logFilePath')
|
|
})
|
|
|
|
it('parses --log-file when present', () => {
|
|
const result = parseArgs([
|
|
'--socket',
|
|
'/tmp/t.sock',
|
|
'--token',
|
|
'/tmp/t.token',
|
|
'--log-file',
|
|
'/tmp/daemon.log'
|
|
])
|
|
expect(result).toEqual({
|
|
socketPath: '/tmp/t.sock',
|
|
tokenPath: '/tmp/t.token',
|
|
logFilePath: '/tmp/daemon.log'
|
|
})
|
|
})
|
|
|
|
it('parses the internal PID-record ownership pair', () => {
|
|
expect(
|
|
parseArgs([
|
|
'--socket',
|
|
'/tmp/t.sock',
|
|
'--token',
|
|
'/tmp/t.token',
|
|
'--pid-record',
|
|
'/tmp/t.pid',
|
|
'--launch-nonce',
|
|
'launch-a'
|
|
])
|
|
).toEqual({
|
|
socketPath: '/tmp/t.sock',
|
|
tokenPath: '/tmp/t.token',
|
|
pidPath: '/tmp/t.pid',
|
|
launchNonce: 'launch-a'
|
|
})
|
|
})
|
|
|
|
it('parses PID publication metadata from the launcher', () => {
|
|
expect(
|
|
parseArgs([
|
|
'--socket',
|
|
'/tmp/t.sock',
|
|
'--token',
|
|
'/tmp/t.token',
|
|
'--pid-record',
|
|
'/tmp/t.pid',
|
|
'--launch-nonce',
|
|
'launch-a',
|
|
'--entry-path',
|
|
'/app/daemon-entry.js',
|
|
'--app-version',
|
|
'1.2.3',
|
|
'--spawner-exec-path',
|
|
'/Applications/Orca.app/Contents/MacOS/Orca'
|
|
])
|
|
).toMatchObject({
|
|
pidPath: '/tmp/t.pid',
|
|
launchNonce: 'launch-a',
|
|
entryPath: '/app/daemon-entry.js',
|
|
appVersion: '1.2.3',
|
|
spawnerExecPath: '/Applications/Orca.app/Contents/MacOS/Orca'
|
|
})
|
|
})
|
|
|
|
it('rejects either PID-record ownership argument without its pair', () => {
|
|
expect(() =>
|
|
parseArgs([
|
|
'--socket',
|
|
'/tmp/t.sock',
|
|
'--token',
|
|
'/tmp/t.token',
|
|
'--pid-record',
|
|
'/tmp/t.pid'
|
|
])
|
|
).toThrow('provided together')
|
|
expect(() =>
|
|
parseArgs([
|
|
'--socket',
|
|
'/tmp/t.sock',
|
|
'--token',
|
|
'/tmp/t.token',
|
|
'--launch-nonce',
|
|
'launch-a'
|
|
])
|
|
).toThrow('provided together')
|
|
})
|
|
|
|
it('still requires --socket and --token when --log-file is given', () => {
|
|
expect(() => parseArgs(['--log-file', '/tmp/daemon.log'])).toThrow('Usage:')
|
|
})
|
|
|
|
it('parses the GUI-only --login-session-watch flag and omits it when absent', () => {
|
|
expect(
|
|
parseArgs(['--socket', '/tmp/t.sock', '--token', '/tmp/t.token', '--login-session-watch'])
|
|
).toEqual({
|
|
socketPath: '/tmp/t.sock',
|
|
tokenPath: '/tmp/t.token',
|
|
loginSessionWatch: true
|
|
})
|
|
expect(parseArgs(['--socket', '/tmp/t.sock', '--token', '/tmp/t.token'])).not.toHaveProperty(
|
|
'loginSessionWatch'
|
|
)
|
|
})
|
|
})
|