mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
Restore agent hook opt-out controls (#2778)
This commit is contained in:
@@ -98,8 +98,10 @@ export function isCommandGroup(commandPath: string[]): boolean {
|
||||
'storage',
|
||||
'orchestration',
|
||||
'computer',
|
||||
'agent',
|
||||
'environment'
|
||||
].includes(commandPath[0])) ||
|
||||
(commandPath.length === 2 && commandPath[0] === 'agent' && commandPath[1] === 'hooks') ||
|
||||
(commandPath.length === 2 &&
|
||||
commandPath[0] === 'storage' &&
|
||||
['local', 'session'].includes(commandPath[1]))
|
||||
|
||||
@@ -16,6 +16,7 @@ import { BROWSER_STORAGE_HANDLERS } from './handlers/browser-storage'
|
||||
import { ORCHESTRATION_HANDLERS } from './handlers/orchestration'
|
||||
import { COMPUTER_HANDLERS } from './handlers/computer'
|
||||
import { ENVIRONMENT_HANDLERS } from './handlers/environment'
|
||||
import { AGENT_HOOK_HANDLERS } from './handlers/agent-hooks'
|
||||
|
||||
export type HandlerContext = {
|
||||
flags: Map<string, string | boolean>
|
||||
@@ -44,6 +45,7 @@ function buildHandlers(): Map<string, CommandHandler> {
|
||||
BROWSER_STORAGE_HANDLERS,
|
||||
ORCHESTRATION_HANDLERS,
|
||||
COMPUTER_HANDLERS,
|
||||
AGENT_HOOK_HANDLERS,
|
||||
ENVIRONMENT_HANDLERS
|
||||
]
|
||||
for (const group of groups) {
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'fs'
|
||||
import { homedir } from 'os'
|
||||
import { dirname, join } from 'path'
|
||||
import { randomUUID } from 'crypto'
|
||||
import type { CommandHandler } from '../dispatch'
|
||||
import { printResult } from '../format'
|
||||
import { RuntimeClientError, type RuntimeClient, type RuntimeRpcSuccess } from '../runtime-client'
|
||||
import type { AgentHookInstallStatus } from '../../shared/agent-hook-types'
|
||||
import { getDefaultPersistedState } from '../../shared/constants'
|
||||
import type { PersistedState } from '../../shared/types'
|
||||
import {
|
||||
applyAgentStatusHooksEnabled,
|
||||
getManagedAgentHookStatuses
|
||||
} from '../../main/agent-hooks/managed-agent-hook-controls'
|
||||
import { getDefaultUserDataPath } from '../runtime-client'
|
||||
|
||||
type AgentHookCommandResult = {
|
||||
enabled: boolean
|
||||
settingsPath: string
|
||||
appliedBy: 'runtime' | 'offline'
|
||||
statuses: AgentHookInstallStatus[]
|
||||
}
|
||||
|
||||
function getDataPath(): string {
|
||||
return join(getDefaultUserDataPath(), 'orca-data.json')
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function readPersistedState(dataPath: string): PersistedState {
|
||||
if (!existsSync(dataPath)) {
|
||||
return getDefaultPersistedState(homedir())
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(dataPath, 'utf-8'))
|
||||
if (!isRecord(parsed)) {
|
||||
throw new Error('file does not contain a JSON object')
|
||||
}
|
||||
return parsed as PersistedState
|
||||
} catch (error) {
|
||||
throw new RuntimeClientError(
|
||||
'runtime_error',
|
||||
`Could not read ${dataPath}: ${error instanceof Error ? error.message : String(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function writePersistedState(dataPath: string, state: PersistedState): void {
|
||||
mkdirSync(dirname(dataPath), { recursive: true })
|
||||
const tmpPath = join(dirname(dataPath), `.${Date.now()}-${randomUUID()}.tmp`)
|
||||
let renamed = false
|
||||
try {
|
||||
writeFileSync(tmpPath, `${JSON.stringify(state, null, 2)}\n`, 'utf-8')
|
||||
renameSync(tmpPath, dataPath)
|
||||
renamed = true
|
||||
} finally {
|
||||
if (!renamed && existsSync(tmpPath)) {
|
||||
try {
|
||||
unlinkSync(tmpPath)
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readEnabledFromDisk(): boolean {
|
||||
const state = readPersistedState(getDataPath())
|
||||
return state.settings?.agentStatusHooksEnabled !== false
|
||||
}
|
||||
|
||||
function updateEnabledOnDisk(enabled: boolean): string {
|
||||
const dataPath = getDataPath()
|
||||
const state = readPersistedState(dataPath)
|
||||
state.settings = {
|
||||
...getDefaultPersistedState(homedir()).settings,
|
||||
...state.settings,
|
||||
agentStatusHooksEnabled: enabled
|
||||
}
|
||||
writePersistedState(dataPath, state)
|
||||
return dataPath
|
||||
}
|
||||
|
||||
async function updateRunningRuntime(client: RuntimeClient, enabled: boolean): Promise<boolean> {
|
||||
try {
|
||||
const status = await client.getCliStatus()
|
||||
if (!status.result.runtime.reachable) {
|
||||
return false
|
||||
}
|
||||
await client.call(
|
||||
'settings.update',
|
||||
{ agentStatusHooksEnabled: enabled },
|
||||
{ timeoutMs: 10_000 }
|
||||
)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function localSuccess<TResult>(result: TResult): RuntimeRpcSuccess<TResult> {
|
||||
return {
|
||||
id: 'local',
|
||||
ok: true,
|
||||
result,
|
||||
_meta: {
|
||||
runtimeId: 'local'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formatAgentHookCommandResult(result: AgentHookCommandResult): string {
|
||||
const statusSummary = result.statuses
|
||||
.map((status) => `${status.agent}: ${status.state}`)
|
||||
.join('\n')
|
||||
return [
|
||||
`agentStatusHooksEnabled: ${result.enabled}`,
|
||||
`appliedBy: ${result.appliedBy}`,
|
||||
`settingsPath: ${result.settingsPath}`,
|
||||
statusSummary
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
async function setAgentHooksEnabled(
|
||||
client: RuntimeClient,
|
||||
enabled: boolean
|
||||
): Promise<AgentHookCommandResult> {
|
||||
const updatedRuntime = await updateRunningRuntime(client, enabled)
|
||||
const settingsPath = updatedRuntime ? getDataPath() : updateEnabledOnDisk(enabled)
|
||||
const statuses = updatedRuntime
|
||||
? getManagedAgentHookStatuses()
|
||||
: applyAgentStatusHooksEnabled(enabled)
|
||||
return {
|
||||
enabled,
|
||||
settingsPath,
|
||||
appliedBy: updatedRuntime ? 'runtime' : 'offline',
|
||||
statuses
|
||||
}
|
||||
}
|
||||
|
||||
export const AGENT_HOOK_HANDLERS: Record<string, CommandHandler> = {
|
||||
'agent hooks status': async ({ json }) => {
|
||||
const result: AgentHookCommandResult = {
|
||||
enabled: readEnabledFromDisk(),
|
||||
settingsPath: getDataPath(),
|
||||
appliedBy: 'offline',
|
||||
statuses: getManagedAgentHookStatuses()
|
||||
}
|
||||
printResult(localSuccess(result), json, formatAgentHookCommandResult)
|
||||
},
|
||||
'agent hooks off': async ({ client, json }) => {
|
||||
const result = await setAgentHooksEnabled(client, false)
|
||||
printResult(localSuccess(result), json, formatAgentHookCommandResult)
|
||||
},
|
||||
'agent hooks on': async ({ client, json }) => {
|
||||
const result = await setAgentHooksEnabled(client, true)
|
||||
printResult(localSuccess(result), json, formatAgentHookCommandResult)
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -17,7 +17,9 @@ export { COMMAND_SPECS } from './specs'
|
||||
export { buildCurrentWorktreeSelector, normalizeWorktreeSelector } from './selectors'
|
||||
|
||||
function shouldIgnoreRemoteSelection(commandPath: string[]): boolean {
|
||||
return commandPath[0] === 'environment' || commandPath[0] === 'serve'
|
||||
return (
|
||||
commandPath[0] === 'environment' || commandPath[0] === 'serve' || commandPath[0] === 'agent'
|
||||
)
|
||||
}
|
||||
|
||||
export async function main(argv = process.argv.slice(2), cwd = process.cwd()): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { CommandSpec } from '../args'
|
||||
import { GLOBAL_FLAGS } from '../args'
|
||||
|
||||
export const AGENT_HOOK_COMMAND_SPECS: CommandSpec[] = [
|
||||
{
|
||||
path: ['agent', 'hooks', 'status'],
|
||||
summary: 'Show whether Orca-managed agent status hooks are enabled',
|
||||
usage: 'orca agent hooks status [--json]',
|
||||
allowedFlags: [...GLOBAL_FLAGS],
|
||||
examples: ['orca agent hooks status', 'orca agent hooks status --json']
|
||||
},
|
||||
{
|
||||
path: ['agent', 'hooks', 'off'],
|
||||
summary: 'Disable Orca-managed agent status hooks and remove local hook entries',
|
||||
usage: 'orca agent hooks off [--json]',
|
||||
allowedFlags: [...GLOBAL_FLAGS],
|
||||
examples: ['orca agent hooks off']
|
||||
},
|
||||
{
|
||||
path: ['agent', 'hooks', 'on'],
|
||||
summary: 'Enable Orca-managed agent status hooks',
|
||||
usage: 'orca agent hooks on [--json]',
|
||||
allowedFlags: [...GLOBAL_FLAGS],
|
||||
examples: ['orca agent hooks on']
|
||||
}
|
||||
]
|
||||
@@ -6,6 +6,7 @@ import { CORE_COMMAND_SPECS } from './core'
|
||||
import { ORCHESTRATION_COMMAND_SPECS } from './orchestration'
|
||||
import { COMPUTER_COMMAND_SPECS } from './computer'
|
||||
import { ENVIRONMENT_COMMAND_SPECS } from './environment'
|
||||
import { AGENT_HOOK_COMMAND_SPECS } from './agent-hooks'
|
||||
|
||||
export const COMMAND_SPECS: CommandSpec[] = [
|
||||
...CORE_COMMAND_SPECS,
|
||||
@@ -14,5 +15,6 @@ export const COMMAND_SPECS: CommandSpec[] = [
|
||||
...BROWSER_ADVANCED_COMMAND_SPECS,
|
||||
...ORCHESTRATION_COMMAND_SPECS,
|
||||
...COMPUTER_COMMAND_SPECS,
|
||||
...AGENT_HOOK_COMMAND_SPECS,
|
||||
...ENVIRONMENT_COMMAND_SPECS
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user