mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 11:07:42 +00:00
* fix(daemon): stop giving new terminals to a daemon whose AppImage files are gone On Linux AppImage, a terminal daemon can outlive the mount it runs from and keep answering health checks until it SIGBUSes. Orca now reads the daemon's /proc exe link and mount table at adoption: an idle severed daemon is replaced; a busy one is kept for its sessions but quarantined, so fresh terminals use the local provider and a healthy RPC never re-admits them. Status and a verified restart are available over RPC and as `orca terminal-daemon status|restart`. Co-Authored-By: Claude <noreply@anthropic.com> * fix(daemon): lift the severed quarantine once a new daemon owns the endpoint Co-Authored-By: Claude <noreply@anthropic.com> * fix(daemon): a severed-daemon restart counts fallback terminals and never stops a replacement The restart re-checks the authenticated endpoint owner before any teardown and again at shutdown, and never falls back to a pid kill when targeted. Status counts the local fallback terminals the restart also ends. Co-Authored-By: Claude <noreply@anthropic.com> * chore(daemon): merge duplicate import Co-Authored-By: Claude <noreply@anthropic.com> * fix(runtime): classify the terminalDaemon RPC namespace as host-owned Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude <noreply@anthropic.com>
99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
/**
|
|
* Who decides where an operation runs.
|
|
* - `host`: the host that owns the resource (a workspace, terminal, session or account).
|
|
* - `credential`: forge APIs. A runtime-owned repo goes to its owner runtime; a local or SSH repo
|
|
* uses this desktop's credentials. Never chosen by focus.
|
|
* - `client`: an effect on this client (reveal, open in editor); never sent to a host.
|
|
* - `stream`: a subscription that lives on the host owning the subscribed resource.
|
|
* - `scope-picked`: row-less work whose host comes from an explicit picker, never from focus.
|
|
*/
|
|
export type HostOperationOwnership = 'host' | 'credential' | 'client' | 'stream' | 'scope-picked'
|
|
|
|
/** By runtime RPC namespace; client effects by their preload API name. */
|
|
export const HOST_OPERATION_OWNERSHIP = {
|
|
accounts: 'host',
|
|
agent: 'host',
|
|
agentHooks: 'host',
|
|
agentSession: 'host',
|
|
agentSessionAttachment: 'host',
|
|
agentTeams: 'host',
|
|
aiVault: 'host',
|
|
artifacts: 'host',
|
|
automation: 'host',
|
|
browser: 'host',
|
|
clipboard: 'host',
|
|
computer: 'host',
|
|
diagnostics: 'host',
|
|
emulator: 'host',
|
|
files: 'host',
|
|
folderWorkspace: 'host',
|
|
git: 'host',
|
|
github: 'credential',
|
|
gitlab: 'credential',
|
|
host: 'host',
|
|
hostedReview: 'credential',
|
|
jira: 'scope-picked',
|
|
layout: 'host',
|
|
linear: 'scope-picked',
|
|
managedServer: 'host',
|
|
markdown: 'host',
|
|
mobileRelay: 'host',
|
|
mobileWeb: 'host',
|
|
nativeChat: 'host',
|
|
network: 'host',
|
|
notifications: 'host',
|
|
orcad: 'host',
|
|
orchestration: 'host',
|
|
pairing: 'host',
|
|
plugins: 'host',
|
|
preflight: 'host',
|
|
project: 'host',
|
|
projectGroup: 'host',
|
|
projectHostSetup: 'host',
|
|
reference: 'host',
|
|
repo: 'host',
|
|
runtime: 'host',
|
|
session: 'host',
|
|
settings: 'scope-picked',
|
|
shell: 'client',
|
|
skills: 'host',
|
|
speech: 'host',
|
|
ssh: 'host',
|
|
stats: 'host',
|
|
status: 'host',
|
|
terminal: 'host',
|
|
terminalDaemon: 'host',
|
|
ui: 'host',
|
|
updater: 'host',
|
|
workspacePorts: 'host',
|
|
worktree: 'host'
|
|
} as const satisfies Record<string, HostOperationOwnership>
|
|
|
|
/** Methods whose owner differs from their namespace: row-less forge reads use the source picker. */
|
|
const HOST_OPERATION_METHOD_OWNERSHIP = {
|
|
'github.project.listAccessible': 'scope-picked',
|
|
'github.project.listViews': 'scope-picked',
|
|
'github.project.resolveRef': 'scope-picked',
|
|
'github.project.viewTable': 'scope-picked'
|
|
} as const satisfies Record<string, HostOperationOwnership>
|
|
|
|
const OWNERSHIP_BY_METHOD: ReadonlyMap<string, HostOperationOwnership> = new Map(
|
|
Object.entries(HOST_OPERATION_METHOD_OWNERSHIP)
|
|
)
|
|
const OWNERSHIP_BY_NAMESPACE: ReadonlyMap<string, HostOperationOwnership> = new Map(
|
|
Object.entries(HOST_OPERATION_OWNERSHIP)
|
|
)
|
|
const STREAM_METHOD = /^(un)?(subscribe|watch)/i
|
|
|
|
/** Null for a namespace nobody classified, so callers fail closed. */
|
|
export function getHostOperationOwnership(method: string): HostOperationOwnership | null {
|
|
const ownership =
|
|
OWNERSHIP_BY_METHOD.get(method) ??
|
|
OWNERSHIP_BY_NAMESPACE.get(method.slice(0, method.indexOf('.')))
|
|
if (!ownership || !method.includes('.')) {
|
|
return null
|
|
}
|
|
const leaf = method.slice(method.lastIndexOf('.') + 1)
|
|
return ownership !== 'client' && STREAM_METHOD.test(leaf) ? 'stream' : ownership
|
|
}
|