mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
Revert Claude scoped settings launch path (#2809)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -17,8 +17,6 @@ vi.mock('electron', () => ({
|
||||
import type { SFTPWrapper } from 'ssh2'
|
||||
import { ClaudeHookService } from './hook-service'
|
||||
|
||||
const CLAUDE_SETTINGS_FILE = 'claude-agent-status-settings.json'
|
||||
|
||||
type FakeFs = {
|
||||
files: Map<string, string>
|
||||
dirs: Set<string>
|
||||
@@ -104,7 +102,7 @@ function createFakeSftp(): { sftp: SFTPWrapper; fs: FakeFs } {
|
||||
}
|
||||
|
||||
describe('ClaudeHookService.install', () => {
|
||||
it('keeps the scoped settings hook-only and preserves user Bedrock settings', () => {
|
||||
it('installs managed hooks into Claude settings and preserves user Bedrock settings', () => {
|
||||
const tmpHome = mkdtempSync(join(tmpdir(), 'orca-claude-hooks-'))
|
||||
vi.stubEnv('HOME', tmpHome)
|
||||
try {
|
||||
@@ -141,11 +139,6 @@ describe('ClaudeHookService.install', () => {
|
||||
const status = new ClaudeHookService().install()
|
||||
expect(status.state).toBe('installed')
|
||||
|
||||
const scoped = JSON.parse(
|
||||
readFileSync(join(tmpHome, '.orca', 'agent-hooks', CLAUDE_SETTINGS_FILE), 'utf-8')
|
||||
)
|
||||
expect(Object.keys(scoped)).toEqual(['hooks'])
|
||||
|
||||
const legacy = JSON.parse(readFileSync(legacyPath, 'utf-8'))
|
||||
expect(legacy).toMatchObject({
|
||||
apiKeyHelper: '/opt/company/claude-key-helper',
|
||||
@@ -160,7 +153,15 @@ describe('ClaudeHookService.install', () => {
|
||||
(definition: { hooks: { command: string }[] }) =>
|
||||
definition.hooks.map((hook) => hook.command)
|
||||
)
|
||||
expect(legacyCommands).toEqual(['/usr/local/bin/user-hook'])
|
||||
expect(legacyCommands).toContain('/usr/local/bin/user-hook')
|
||||
expect(legacyCommands.some((command: string) => command.includes('claude-hook.sh'))).toBe(
|
||||
true
|
||||
)
|
||||
expect(
|
||||
legacyCommands.some((command: string) =>
|
||||
command.includes('/Users/old/.orca/agent-hooks/claude-hook.sh')
|
||||
)
|
||||
).toBe(false)
|
||||
} finally {
|
||||
vi.unstubAllEnvs()
|
||||
rmSync(tmpHome, { recursive: true, force: true })
|
||||
@@ -169,13 +170,13 @@ describe('ClaudeHookService.install', () => {
|
||||
})
|
||||
|
||||
describe('ClaudeHookService.installRemote', () => {
|
||||
it('writes scoped settings + managed script under the remote $HOME', async () => {
|
||||
it('writes Claude settings + managed script under the remote $HOME', async () => {
|
||||
const svc = new ClaudeHookService()
|
||||
const { sftp, fs } = createFakeSftp()
|
||||
const status = await svc.installRemote(sftp, '/home/dev')
|
||||
expect(status.state).toBe('installed')
|
||||
expect(status.configPath).toBe('/home/dev/.orca/agent-hooks/claude-agent-status-settings.json')
|
||||
const settings = fs.files.get('/home/dev/.orca/agent-hooks/claude-agent-status-settings.json')
|
||||
expect(status.configPath).toBe('/home/dev/.claude/settings.json')
|
||||
const settings = fs.files.get('/home/dev/.claude/settings.json')
|
||||
expect(settings).toBeTruthy()
|
||||
const parsed = JSON.parse(settings!)
|
||||
// Why: every load-bearing event must be present and point at the
|
||||
@@ -199,20 +200,19 @@ describe('ClaudeHookService.installRemote', () => {
|
||||
// Managed script body
|
||||
expect(fs.files.get('/home/dev/.orca/agent-hooks/claude-hook.sh')).toContain('#!/bin/sh')
|
||||
expect(fs.modes.get('/home/dev/.orca/agent-hooks/claude-hook.sh')).toBe(0o755)
|
||||
expect(fs.files.has('/home/dev/.claude/settings.json')).toBe(false)
|
||||
})
|
||||
|
||||
it('reports parse error when legacy remote settings.json cannot be cleaned', async () => {
|
||||
it('reports parse error when remote settings.json cannot be parsed', async () => {
|
||||
const svc = new ClaudeHookService()
|
||||
const { sftp, fs } = createFakeSftp()
|
||||
fs.files.set('/home/dev/.claude/settings.json', 'not json')
|
||||
const status = await svc.installRemote(sftp, '/home/dev')
|
||||
expect(status.state).toBe('error')
|
||||
expect(status.managedHooksPresent).toBe(true)
|
||||
expect(status.detail).toContain('Scoped Claude hooks installed')
|
||||
expect(status.managedHooksPresent).toBe(false)
|
||||
expect(status.detail).toContain('Could not parse remote Claude settings.json')
|
||||
})
|
||||
|
||||
it('preserves user-authored legacy hook entries while sweeping old managed entries', async () => {
|
||||
it('preserves user-authored hook entries while sweeping old managed entries', async () => {
|
||||
const svc = new ClaudeHookService()
|
||||
const { sftp, fs } = createFakeSftp()
|
||||
fs.files.set(
|
||||
@@ -238,14 +238,11 @@ describe('ClaudeHookService.installRemote', () => {
|
||||
)
|
||||
await svc.installRemote(sftp, '/home/dev')
|
||||
const parsed = JSON.parse(fs.files.get('/home/dev/.claude/settings.json')!)
|
||||
// Original user-authored entry survives, while legacy global Orca entries
|
||||
// are removed because scoped --settings carries the managed hook now.
|
||||
// Original user-authored entry survives, while stale Orca entries are
|
||||
// replaced with the current managed hook command.
|
||||
const stopDefs = parsed.hooks.Stop as { hooks: { command: string }[] }[]
|
||||
const userCmds = stopDefs.flatMap((d) => d.hooks.map((h) => h.command))
|
||||
expect(userCmds).toContain('/usr/local/bin/my-user-hook')
|
||||
expect(userCmds.some((c) => c.includes('claude-hook.sh'))).toBe(false)
|
||||
expect(fs.files.get('/home/dev/.orca/agent-hooks/claude-agent-status-settings.json')).toContain(
|
||||
'claude-hook.sh'
|
||||
)
|
||||
expect(userCmds.filter((c) => c.includes('claude-hook.sh'))).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { existsSync, unlinkSync } from 'fs'
|
||||
import type { SFTPWrapper } from 'ssh2'
|
||||
import type { AgentHookInstallState, AgentHookInstallStatus } from '../../shared/agent-hook-types'
|
||||
import { ORCA_CLAUDE_AGENT_STATUS_SETTINGS_ENV } from '../../shared/claude-settings'
|
||||
import {
|
||||
buildWindowsAgentHookPostCommand,
|
||||
readHooksJson,
|
||||
@@ -16,13 +14,11 @@ import {
|
||||
import {
|
||||
applyManagedHooks,
|
||||
CLAUDE_EVENTS,
|
||||
getLegacyConfigPath,
|
||||
getConfigPath,
|
||||
getManagedCommand,
|
||||
getManagedScriptPath,
|
||||
getRemoteLegacyConfigPath,
|
||||
getRemoteConfigPath,
|
||||
getRemoteManagedCommand,
|
||||
getRemoteScopedSettingsPath,
|
||||
getScopedSettingsPath,
|
||||
removeManagedHooks
|
||||
} from './hook-settings'
|
||||
|
||||
@@ -92,7 +88,7 @@ function getManagedScript(target: 'local' | 'posix' = 'local'): string {
|
||||
|
||||
export class ClaudeHookService {
|
||||
getStatus(): AgentHookInstallStatus {
|
||||
const configPath = getScopedSettingsPath()
|
||||
const configPath = getConfigPath()
|
||||
const scriptPath = getManagedScriptPath()
|
||||
const config = readHooksJson(configPath)
|
||||
if (!config) {
|
||||
@@ -101,7 +97,7 @@ export class ClaudeHookService {
|
||||
state: 'error',
|
||||
configPath,
|
||||
managedHooksPresent: false,
|
||||
detail: 'Could not parse Orca Claude settings file'
|
||||
detail: 'Could not parse Claude settings.json'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,41 +138,18 @@ export class ClaudeHookService {
|
||||
}
|
||||
|
||||
install(): AgentHookInstallStatus {
|
||||
const scopedStatus = this.installScopedSettings()
|
||||
if (scopedStatus.state === 'error') {
|
||||
return scopedStatus
|
||||
}
|
||||
const legacyStatus = this.removeLegacyGlobalHooks()
|
||||
if (legacyStatus.state === 'error') {
|
||||
return {
|
||||
...legacyStatus,
|
||||
managedHooksPresent: scopedStatus.managedHooksPresent,
|
||||
detail: scopedStatus.managedHooksPresent
|
||||
? `Scoped Claude hooks installed, but ${legacyStatus.detail}`
|
||||
: legacyStatus.detail
|
||||
}
|
||||
}
|
||||
return scopedStatus
|
||||
}
|
||||
|
||||
buildPtyEnv(): Record<string, string> {
|
||||
try {
|
||||
const status = this.installScopedSettings()
|
||||
if (status.state === 'error') {
|
||||
console.warn(`[agent-hooks] Failed to prepare Claude scoped settings: ${status.detail}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[agent-hooks] Failed to prepare Claude scoped settings:', error)
|
||||
}
|
||||
return {
|
||||
[ORCA_CLAUDE_AGENT_STATUS_SETTINGS_ENV]: getScopedSettingsPath()
|
||||
}
|
||||
}
|
||||
|
||||
private installScopedSettings(): AgentHookInstallStatus {
|
||||
const configPath = getScopedSettingsPath()
|
||||
const configPath = getConfigPath()
|
||||
const scriptPath = getManagedScriptPath()
|
||||
const config = readHooksJson(configPath) ?? {}
|
||||
const config = readHooksJson(configPath)
|
||||
if (!config) {
|
||||
return {
|
||||
agent: 'claude',
|
||||
state: 'error',
|
||||
configPath,
|
||||
managedHooksPresent: false,
|
||||
detail: 'Could not parse Claude settings.json'
|
||||
}
|
||||
}
|
||||
|
||||
const command = getManagedCommand(scriptPath)
|
||||
const nextConfig = applyManagedHooks(config, command)
|
||||
@@ -185,37 +158,15 @@ export class ClaudeHookService {
|
||||
return this.getStatus()
|
||||
}
|
||||
|
||||
private removeLegacyGlobalHooks(): AgentHookInstallStatus {
|
||||
const configPath = getLegacyConfigPath()
|
||||
const config = readHooksJson(configPath)
|
||||
if (!config) {
|
||||
return {
|
||||
agent: 'claude',
|
||||
state: 'error',
|
||||
configPath,
|
||||
managedHooksPresent: false,
|
||||
detail: 'Could not parse Claude settings.json to remove legacy global hooks'
|
||||
}
|
||||
}
|
||||
const { config: nextConfig, changed } = removeManagedHooks(config)
|
||||
if (changed) {
|
||||
writeHooksJson(configPath, nextConfig)
|
||||
}
|
||||
return this.getStatus()
|
||||
}
|
||||
|
||||
// Why: install Orca's scoped Claude hook settings on the remote box rather
|
||||
// than the local Mac/Linux machine. Caller passes the user's SFTP handle
|
||||
// from the SshConnection plus the resolved remote `$HOME` used to compute
|
||||
// the Orca-owned settings path. POSIX-only by design — see
|
||||
// docs/design/agent-status-over-ssh.md §3 / §6 (Windows-remote deferred).
|
||||
// Why: install Orca's Claude hook settings on the remote box rather than the
|
||||
// local machine. Caller passes the user's SFTP handle plus the resolved
|
||||
// remote `$HOME`; POSIX-only by design (Windows-remote deferred).
|
||||
async installRemote(sftp: SFTPWrapper, remoteHome: string): Promise<AgentHookInstallStatus> {
|
||||
// Why: remote-Windows is out of scope for v1 — we ship POSIX-shaped paths
|
||||
// and a `.sh` managed script body. The remote platform is gated by the
|
||||
// relay's capability RPC at a higher layer; we cannot detect it from
|
||||
// `process.platform` here (that's the local box).
|
||||
const remoteConfigPath = getRemoteScopedSettingsPath(remoteHome)
|
||||
const remoteLegacyConfigPath = getRemoteLegacyConfigPath(remoteHome)
|
||||
const remoteConfigPath = getRemoteConfigPath(remoteHome)
|
||||
const remoteScriptPath = `${remoteHome.replace(/\/$/, '')}/.orca/agent-hooks/claude-hook.sh`
|
||||
// Why: SFTP reads/writes fail far more often than local fs (network drops,
|
||||
// EACCES on remote dirs, disk full, channel closed). Wrap the entire
|
||||
@@ -225,7 +176,16 @@ export class ClaudeHookService {
|
||||
// specifically means "file present but unparseable" — keep that branch
|
||||
// distinct so the user sees an actionable message.
|
||||
try {
|
||||
const config = (await readHooksJsonRemote(sftp, remoteConfigPath)) ?? {}
|
||||
const config = await readHooksJsonRemote(sftp, remoteConfigPath)
|
||||
if (!config) {
|
||||
return {
|
||||
agent: 'claude',
|
||||
state: 'error',
|
||||
configPath: remoteConfigPath,
|
||||
managedHooksPresent: false,
|
||||
detail: 'Could not parse remote Claude settings.json'
|
||||
}
|
||||
}
|
||||
|
||||
// Why: the POSIX wrapper is identical regardless of where the script
|
||||
// lands; only the path differs. Reuse the same wrapper helper.
|
||||
@@ -243,25 +203,6 @@ export class ClaudeHookService {
|
||||
await writeManagedScriptRemote(sftp, remoteScriptPath, getManagedScript('posix'))
|
||||
await writeHooksJsonRemote(sftp, remoteConfigPath, nextConfig)
|
||||
|
||||
const legacyConfig = await readHooksJsonRemote(sftp, remoteLegacyConfigPath)
|
||||
if (!legacyConfig) {
|
||||
return {
|
||||
agent: 'claude',
|
||||
state: 'error',
|
||||
configPath: remoteLegacyConfigPath,
|
||||
managedHooksPresent: true,
|
||||
detail:
|
||||
'Scoped Claude hooks installed, but could not parse remote Claude settings.json to remove legacy global hooks'
|
||||
}
|
||||
}
|
||||
const { config: nextLegacyConfig, changed } = removeManagedHooks(
|
||||
legacyConfig,
|
||||
'claude-hook.sh'
|
||||
)
|
||||
if (changed) {
|
||||
await writeHooksJsonRemote(sftp, remoteLegacyConfigPath, nextLegacyConfig)
|
||||
}
|
||||
|
||||
return {
|
||||
agent: 'claude',
|
||||
state: 'installed',
|
||||
@@ -281,13 +222,20 @@ export class ClaudeHookService {
|
||||
}
|
||||
|
||||
remove(): AgentHookInstallStatus {
|
||||
const scopedSettingsPath = getScopedSettingsPath()
|
||||
if (existsSync(scopedSettingsPath)) {
|
||||
unlinkSync(scopedSettingsPath)
|
||||
const configPath = getConfigPath()
|
||||
const config = readHooksJson(configPath)
|
||||
if (!config) {
|
||||
return {
|
||||
agent: 'claude',
|
||||
state: 'error',
|
||||
configPath,
|
||||
managedHooksPresent: false,
|
||||
detail: 'Could not parse Claude settings.json'
|
||||
}
|
||||
}
|
||||
const legacyStatus = this.removeLegacyGlobalHooks()
|
||||
if (legacyStatus.state === 'error') {
|
||||
return legacyStatus
|
||||
const { config: nextConfig, changed } = removeManagedHooks(config)
|
||||
if (changed) {
|
||||
writeHooksJson(configPath, nextConfig)
|
||||
}
|
||||
return this.getStatus()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { homedir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { ORCA_CLAUDE_AGENT_STATUS_SETTINGS_FILE } from '../../shared/claude-settings'
|
||||
import {
|
||||
createManagedCommandMatcher,
|
||||
getSharedManagedScriptPath,
|
||||
@@ -33,14 +32,10 @@ export const CLAUDE_EVENTS = [
|
||||
}
|
||||
] as const
|
||||
|
||||
export function getLegacyConfigPath(): string {
|
||||
export function getConfigPath(): string {
|
||||
return join(homedir(), '.claude', 'settings.json')
|
||||
}
|
||||
|
||||
export function getScopedSettingsPath(): string {
|
||||
return join(homedir(), '.orca', 'agent-hooks', ORCA_CLAUDE_AGENT_STATUS_SETTINGS_FILE)
|
||||
}
|
||||
|
||||
export function getManagedScriptFileName(): string {
|
||||
return process.platform === 'win32' ? 'claude-hook.cmd' : 'claude-hook.sh'
|
||||
}
|
||||
@@ -49,11 +44,7 @@ export function getManagedScriptPath(): string {
|
||||
return getSharedManagedScriptPath(getManagedScriptFileName())
|
||||
}
|
||||
|
||||
export function getRemoteScopedSettingsPath(remoteHome: string): string {
|
||||
return `${remoteHome.replace(/\/$/, '')}/.orca/agent-hooks/${ORCA_CLAUDE_AGENT_STATUS_SETTINGS_FILE}`
|
||||
}
|
||||
|
||||
export function getRemoteLegacyConfigPath(remoteHome: string): string {
|
||||
export function getRemoteConfigPath(remoteHome: string): string {
|
||||
return `${remoteHome.replace(/\/$/, '')}/.claude/settings.json`
|
||||
}
|
||||
|
||||
|
||||
+55
-24
@@ -20,7 +20,6 @@ const {
|
||||
spawnMock,
|
||||
openCodeBuildPtyEnvMock,
|
||||
openCodeClearPtyMock,
|
||||
claudeBuildPtyEnvMock,
|
||||
buildAgentHookEnvMock,
|
||||
clearAgentHookPaneStateMock,
|
||||
registerPaneKeyAliasMock,
|
||||
@@ -50,7 +49,6 @@ const {
|
||||
getPathMock: vi.fn(),
|
||||
spawnMock: vi.fn(),
|
||||
openCodeBuildPtyEnvMock: vi.fn(),
|
||||
claudeBuildPtyEnvMock: vi.fn(),
|
||||
isPwshAvailableMock: vi.fn(),
|
||||
openCodeClearPtyMock: vi.fn(),
|
||||
buildAgentHookEnvMock: vi.fn(),
|
||||
@@ -105,12 +103,6 @@ vi.mock('../opencode/hook-service', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../claude/hook-service', () => ({
|
||||
claudeHookService: {
|
||||
buildPtyEnv: claudeBuildPtyEnvMock
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../agent-hooks/server', () => ({
|
||||
agentHookServer: {
|
||||
buildPtyEnv: buildAgentHookEnvMock,
|
||||
@@ -198,7 +190,7 @@ describe('registerPtyHandlers', () => {
|
||||
const savedOrcaCodexHome = process.env.ORCA_CODEX_HOME
|
||||
const savedOrcaOmpAgentDir = process.env.ORCA_OMP_CODING_AGENT_DIR
|
||||
const savedOrcaOmpSourceAgentDir = process.env.ORCA_OMP_SOURCE_AGENT_DIR
|
||||
const savedOrcaClaudeSettings = process.env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS
|
||||
const savedOrcaClaudeAgentStatusSettings = process.env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.OPENCODE_CONFIG_DIR
|
||||
@@ -228,7 +220,6 @@ describe('registerPtyHandlers', () => {
|
||||
spawnMock.mockReset()
|
||||
openCodeBuildPtyEnvMock.mockReset()
|
||||
openCodeClearPtyMock.mockReset()
|
||||
claudeBuildPtyEnvMock.mockReset()
|
||||
buildAgentHookEnvMock.mockReset()
|
||||
clearAgentHookPaneStateMock.mockReset()
|
||||
registerPaneKeyAliasMock.mockReset()
|
||||
@@ -265,9 +256,6 @@ describe('registerPtyHandlers', () => {
|
||||
ORCA_AGENT_HOOK_PORT: '5678',
|
||||
ORCA_AGENT_HOOK_TOKEN: 'agent-token'
|
||||
})
|
||||
claudeBuildPtyEnvMock.mockReturnValue({
|
||||
ORCA_CLAUDE_AGENT_STATUS_SETTINGS: '/tmp/orca-claude-settings.json'
|
||||
})
|
||||
piBuildPtyEnvMock.mockImplementation(
|
||||
(_ptyId: string, existingAgentDir?: string, _kind?: string) => ({
|
||||
PI_CODING_AGENT_DIR: existingAgentDir
|
||||
@@ -335,10 +323,10 @@ describe('registerPtyHandlers', () => {
|
||||
} else {
|
||||
delete process.env.ORCA_OMP_SOURCE_AGENT_DIR
|
||||
}
|
||||
if (savedOrcaClaudeSettings === undefined) {
|
||||
if (savedOrcaClaudeAgentStatusSettings === undefined) {
|
||||
delete process.env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS
|
||||
} else {
|
||||
process.env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS = savedOrcaClaudeSettings
|
||||
process.env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS = savedOrcaClaudeAgentStatusSettings
|
||||
}
|
||||
})
|
||||
|
||||
@@ -731,7 +719,7 @@ describe('registerPtyHandlers', () => {
|
||||
expect(env.ORCA_PI_SOURCE_AGENT_DIR).toBe('/home/tester/.config/pi-agent')
|
||||
})
|
||||
|
||||
it('injects the Claude/Codex hook receiver env into Orca terminal PTYs', async () => {
|
||||
it('injects the agent hook receiver env into Orca terminal PTYs', async () => {
|
||||
const env = await spawnAndGetEnv()
|
||||
// Why: after the daemon-parity refactor, buildAgentHookEnv runs exactly
|
||||
// once for a local spawn — inside the shared buildPtyHostEnv helper,
|
||||
@@ -739,10 +727,8 @@ describe('registerPtyHandlers', () => {
|
||||
// both route through. The handler's separate ad-hoc injection (which
|
||||
// used to cause a double-call for local spawns) is gone.
|
||||
expect(buildAgentHookEnvMock).toHaveBeenCalledTimes(1)
|
||||
expect(claudeBuildPtyEnvMock).toHaveBeenCalledTimes(1)
|
||||
expect(env.ORCA_AGENT_HOOK_PORT).toBe('5678')
|
||||
expect(env.ORCA_AGENT_HOOK_TOKEN).toBe('agent-token')
|
||||
expect(env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS).toBe('/tmp/orca-claude-settings.json')
|
||||
})
|
||||
|
||||
it('strips stale inherited hook receiver env before injecting this runtime', async () => {
|
||||
@@ -752,7 +738,7 @@ describe('registerPtyHandlers', () => {
|
||||
ORCA_AGENT_HOOK_ENV: 'production',
|
||||
ORCA_AGENT_HOOK_VERSION: 'stale-version',
|
||||
ORCA_AGENT_HOOK_ENDPOINT: '/tmp/stale-endpoint.env',
|
||||
ORCA_CLAUDE_AGENT_STATUS_SETTINGS: '/tmp/stale-claude-settings.json'
|
||||
ORCA_CLAUDE_AGENT_STATUS_SETTINGS: '/tmp/orca/agent-hooks/claude-agent-status-settings.json'
|
||||
})
|
||||
|
||||
expect(env.ORCA_AGENT_HOOK_PORT).toBe('5678')
|
||||
@@ -760,7 +746,7 @@ describe('registerPtyHandlers', () => {
|
||||
expect(env.ORCA_AGENT_HOOK_ENV).toBeUndefined()
|
||||
expect(env.ORCA_AGENT_HOOK_VERSION).toBeUndefined()
|
||||
expect(env.ORCA_AGENT_HOOK_ENDPOINT).toBeUndefined()
|
||||
expect(env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS).toBe('/tmp/orca-claude-settings.json')
|
||||
expect(env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS).toBeUndefined()
|
||||
})
|
||||
|
||||
it('does not leak inherited hook receiver env if the hook server is unavailable', async () => {
|
||||
@@ -772,7 +758,7 @@ describe('registerPtyHandlers', () => {
|
||||
ORCA_AGENT_HOOK_ENV: 'production',
|
||||
ORCA_AGENT_HOOK_VERSION: 'stale-version',
|
||||
ORCA_AGENT_HOOK_ENDPOINT: '/tmp/stale-endpoint.env',
|
||||
ORCA_CLAUDE_AGENT_STATUS_SETTINGS: '/tmp/stale-claude-settings.json'
|
||||
ORCA_CLAUDE_AGENT_STATUS_SETTINGS: '/tmp/orca/agent-hooks/claude-agent-status-settings.json'
|
||||
})
|
||||
|
||||
expect(env.ORCA_AGENT_HOOK_PORT).toBeUndefined()
|
||||
@@ -780,7 +766,7 @@ describe('registerPtyHandlers', () => {
|
||||
expect(env.ORCA_AGENT_HOOK_ENV).toBeUndefined()
|
||||
expect(env.ORCA_AGENT_HOOK_VERSION).toBeUndefined()
|
||||
expect(env.ORCA_AGENT_HOOK_ENDPOINT).toBeUndefined()
|
||||
expect(env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS).toBe('/tmp/orca-claude-settings.json')
|
||||
expect(env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS).toBeUndefined()
|
||||
})
|
||||
|
||||
it('prepends local git/gh attribution shims when attribution is enabled', async () => {
|
||||
@@ -1086,7 +1072,53 @@ describe('registerPtyHandlers', () => {
|
||||
const env = await daemonSpawnAndGetEnv({})
|
||||
expect(env.ORCA_AGENT_HOOK_PORT).toBe('5678')
|
||||
expect(env.ORCA_AGENT_HOOK_TOKEN).toBe('agent-token')
|
||||
expect(env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS).toBe('/tmp/orca-claude-settings.json')
|
||||
})
|
||||
|
||||
it('deletes stale Claude scoped settings env from daemon-hosted PTYs', async () => {
|
||||
const spawnOptions = await daemonSpawnAndGetOptions({}, undefined, undefined, {
|
||||
ORCA_CLAUDE_AGENT_STATUS_SETTINGS:
|
||||
'/tmp/orca/agent-hooks/claude-agent-status-settings.json'
|
||||
})
|
||||
expect(spawnOptions.env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS).toBeUndefined()
|
||||
expect(spawnOptions.envToDelete).toEqual(
|
||||
expect.arrayContaining(['ORCA_CLAUDE_AGENT_STATUS_SETTINGS'])
|
||||
)
|
||||
expect(spawnOptions.env.ORCA_AGENT_HOOK_PORT).toBe('5678')
|
||||
expect(spawnOptions.env.ORCA_AGENT_HOOK_TOKEN).toBe('agent-token')
|
||||
})
|
||||
|
||||
it('deletes stale Claude scoped settings env from runtime-created daemon PTYs', async () => {
|
||||
type RuntimeSpawnController = {
|
||||
spawn(args: {
|
||||
cols: number
|
||||
rows: number
|
||||
worktreeId?: string
|
||||
env?: Record<string, string>
|
||||
}): Promise<{ id: string }>
|
||||
}
|
||||
const daemonSpawn = setupDaemonAdapter()
|
||||
const runtime = {
|
||||
setPtyController: vi.fn(),
|
||||
registerPty: vi.fn(),
|
||||
onPtySpawned: vi.fn(),
|
||||
onPtyExit: vi.fn(),
|
||||
onPtyData: vi.fn()
|
||||
}
|
||||
process.env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS =
|
||||
'/tmp/orca/agent-hooks/claude-agent-status-settings.json'
|
||||
handlers.clear()
|
||||
registerPtyHandlers(mainWindow as never, runtime as never)
|
||||
const controller = runtime.setPtyController.mock.calls[0]?.[0] as RuntimeSpawnController
|
||||
|
||||
await controller.spawn({ cols: 80, rows: 24, worktreeId: 'wt-runtime', env: {} })
|
||||
|
||||
const spawnOptions = daemonSpawn.mock.calls.at(-1)?.[0] as DaemonSpawnCall
|
||||
expect(spawnOptions.env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS).toBeUndefined()
|
||||
expect(spawnOptions.envToDelete).toEqual(
|
||||
expect.arrayContaining(['ORCA_CLAUDE_AGENT_STATUS_SETTINGS'])
|
||||
)
|
||||
expect(spawnOptions.env.ORCA_AGENT_HOOK_PORT).toBe('5678')
|
||||
expect(spawnOptions.env.ORCA_AGENT_HOOK_TOKEN).toBe('agent-token')
|
||||
})
|
||||
|
||||
it('strips inherited agent-hook endpoint env from development daemon PTYs', async () => {
|
||||
@@ -1381,7 +1413,6 @@ describe('registerPtyHandlers', () => {
|
||||
// worst a credential leak.
|
||||
expect(env.ORCA_AGENT_HOOK_PORT).toBeUndefined()
|
||||
expect(env.ORCA_AGENT_HOOK_TOKEN).toBeUndefined()
|
||||
expect(env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS).toBeUndefined()
|
||||
expect(env.ORCA_ENABLE_GIT_ATTRIBUTION).toBeUndefined()
|
||||
expect(env.OPENCODE_CONFIG_DIR).toBeUndefined()
|
||||
expect(env.ORCA_OPENCODE_CONFIG_DIR).toBeUndefined()
|
||||
|
||||
+23
-8
@@ -10,8 +10,6 @@ export { getBashShellReadyRcfileContent } from '../providers/local-pty-shell-rea
|
||||
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
|
||||
import type { Store } from '../persistence'
|
||||
import type { GlobalSettings } from '../../shared/types'
|
||||
import { ORCA_CLAUDE_AGENT_STATUS_SETTINGS_ENV } from '../../shared/claude-settings'
|
||||
import { claudeHookService } from '../claude/hook-service'
|
||||
import { openCodeHookService } from '../opencode/hook-service'
|
||||
import { agentHookServer } from '../agent-hooks/server'
|
||||
import { isAgentStatusHooksEnabled } from '../agent-hooks/managed-agent-hook-controls'
|
||||
@@ -93,7 +91,9 @@ const AGENT_HOOK_RUNTIME_ENV_KEYS = [
|
||||
'ORCA_AGENT_HOOK_ENV',
|
||||
'ORCA_AGENT_HOOK_VERSION',
|
||||
'ORCA_AGENT_HOOK_ENDPOINT',
|
||||
ORCA_CLAUDE_AGENT_STATUS_SETTINGS_ENV
|
||||
// Why: PR 2778 briefly exported this scoped Claude settings path. Keep
|
||||
// deleting stale inherited values so older PTYs cannot leak the reverted path.
|
||||
'ORCA_CLAUDE_AGENT_STATUS_SETTINGS'
|
||||
] as const
|
||||
|
||||
export function getPtyIdForPaneKey(paneKey: string): string | undefined {
|
||||
@@ -332,6 +332,16 @@ function mergePtyEnvDeletions(
|
||||
return Array.from(new Set([...(existingKeys ?? []), ...additionalKeys]))
|
||||
}
|
||||
|
||||
function getInheritedAgentHookEnvKeysToDelete(
|
||||
spawnEnv: Record<string, string> | undefined
|
||||
): string[] {
|
||||
const env = spawnEnv ?? {}
|
||||
// Why: daemon/local providers merge process.env after main-process cleanup.
|
||||
// Delete reverted or unavailable hook env keys there without dropping fresh
|
||||
// receiver coordinates that buildPtyHostEnv intentionally set.
|
||||
return AGENT_HOOK_RUNTIME_ENV_KEYS.filter((key) => env[key] === undefined)
|
||||
}
|
||||
|
||||
// Why: when agent status is disabled, a nested Orca terminal can still pass
|
||||
// through a prior PTY's OpenCode/Pi overlay env. Restore the user's original
|
||||
// source dir when Orca recorded one, otherwise strip only values known to be ours.
|
||||
@@ -431,7 +441,6 @@ export function buildPtyHostEnv(
|
||||
}
|
||||
if (opts.agentStatusHooksEnabled) {
|
||||
Object.assign(baseEnv, agentHookServer.buildPtyEnv())
|
||||
Object.assign(baseEnv, claudeHookService.buildPtyEnv())
|
||||
}
|
||||
|
||||
// Why: PI_CODING_AGENT_DIR owns Pi's / OMP's full config/session root (OMP
|
||||
@@ -1103,9 +1112,12 @@ export function registerPtyHandlers(
|
||||
cwd: args.cwd,
|
||||
env
|
||||
}
|
||||
if (claudeAuth?.stripAuthEnv) {
|
||||
spawnOptions.envToDelete = [...CLAUDE_AUTH_ENV_VARS, 'ANTHROPIC_CUSTOM_HEADERS']
|
||||
}
|
||||
spawnOptions.envToDelete = mergePtyEnvDeletions(
|
||||
claudeAuth?.stripAuthEnv
|
||||
? [...CLAUDE_AUTH_ENV_VARS, 'ANTHROPIC_CUSTOM_HEADERS']
|
||||
: undefined,
|
||||
args.connectionId ? [] : getInheritedAgentHookEnvKeysToDelete(env)
|
||||
)
|
||||
if (skipCodexHomeEnv) {
|
||||
spawnOptions.envToDelete = mergePtyEnvDeletions(
|
||||
spawnOptions.envToDelete,
|
||||
@@ -1507,7 +1519,10 @@ export function registerPtyHandlers(
|
||||
? [...CLAUDE_AUTH_ENV_VARS, 'ANTHROPIC_CUSTOM_HEADERS']
|
||||
: undefined
|
||||
const combinedEnvToDelete = mergePtyEnvDeletions(
|
||||
envToDelete,
|
||||
mergePtyEnvDeletions(
|
||||
envToDelete,
|
||||
args.connectionId ? [] : getInheritedAgentHookEnvKeysToDelete(spawnEnv)
|
||||
),
|
||||
skipCodexHomeEnv ? CODEX_HOME_ENV_KEYS : []
|
||||
)
|
||||
const spawnOptions: PtySpawnOptions = {
|
||||
|
||||
@@ -7189,7 +7189,7 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
cwd: '/remote/mobile-startup-draft',
|
||||
command: `claude --settings "$HOME/.orca/agent-hooks/claude-agent-status-settings.json" --prefill '${draftUrl}'`,
|
||||
command: `claude --prefill '${draftUrl}'`,
|
||||
connectionId: 'ssh-1',
|
||||
worktreeId: result.worktree.id
|
||||
})
|
||||
|
||||
@@ -6730,8 +6730,7 @@ export class OrcaRuntimeService {
|
||||
agent,
|
||||
draft: content,
|
||||
cmdOverrides: settings.agentCmdOverrides ?? {},
|
||||
platform: agentLaunchPlatform,
|
||||
useOrcaClaudeAgentStatusSettings: settings.agentStatusHooksEnabled !== false
|
||||
platform: agentLaunchPlatform
|
||||
})
|
||||
if (draftLaunchPlan) {
|
||||
return {
|
||||
@@ -6748,8 +6747,7 @@ export class OrcaRuntimeService {
|
||||
prompt: '',
|
||||
cmdOverrides: settings.agentCmdOverrides ?? {},
|
||||
platform: agentLaunchPlatform,
|
||||
allowEmptyPromptLaunch: true,
|
||||
useOrcaClaudeAgentStatusSettings: settings.agentStatusHooksEnabled !== false
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
if (!startupPlan) {
|
||||
return null
|
||||
|
||||
@@ -48,8 +48,7 @@ export function FloatingTerminalWindowControls({
|
||||
prompt: '',
|
||||
cmdOverrides: state.settings?.agentCmdOverrides ?? {},
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: true,
|
||||
useOrcaClaudeAgentStatusSettings: state.settings?.agentStatusHooksEnabled !== false
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
if (!startupPlan) {
|
||||
toast.error(`Could not build launch command for ${defaultAgentLabel ?? defaultAgent}.`)
|
||||
|
||||
@@ -147,7 +147,6 @@ describe('AgentsPane', () => {
|
||||
it('includes hook search metadata for the status setting', () => {
|
||||
expect(matchesSettingsSearch('hooks', AGENTS_PANE_SEARCH_ENTRIES)).toBe(true)
|
||||
expect(matchesSettingsSearch('waiting', AGENTS_PANE_SEARCH_ENTRIES)).toBe(true)
|
||||
expect(matchesSettingsSearch('scoped', AGENTS_PANE_SEARCH_ENTRIES)).toBe(true)
|
||||
expect(matchesSettingsSearch('codex', AGENTS_PANE_SEARCH_ENTRIES)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const AGENT_STATUS_HOOKS_TITLE = 'Agent status hooks'
|
||||
|
||||
export const AGENT_STATUS_HOOKS_DESCRIPTION =
|
||||
'Shows working, waiting, and done states in Orca. For Claude and Codex, Orca uses scoped settings and profiles so terminal sessions outside Orca keep your existing config. Turn off to remove Orca-managed hooks and stop reinstalling them.'
|
||||
'Shows working, waiting, and done states in Orca. Turn off to remove Orca-managed hooks and stop reinstalling them.'
|
||||
|
||||
export const AGENT_STATUS_HOOKS_SEARCH_KEYWORDS = [
|
||||
'hooks',
|
||||
@@ -11,8 +11,6 @@ export const AGENT_STATUS_HOOKS_SEARCH_KEYWORDS = [
|
||||
'done',
|
||||
'remove',
|
||||
'restore',
|
||||
'scoped',
|
||||
'profile',
|
||||
'settings',
|
||||
'config',
|
||||
'claude',
|
||||
|
||||
@@ -1761,8 +1761,7 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
agent: tuiAgent,
|
||||
prompt: startupPrompt,
|
||||
cmdOverrides: settings?.agentCmdOverrides ?? {},
|
||||
platform: CLIENT_PLATFORM,
|
||||
useOrcaClaudeAgentStatusSettings: settings?.agentStatusHooksEnabled !== false
|
||||
platform: CLIENT_PLATFORM
|
||||
})
|
||||
|
||||
// Why: thread agent_started telemetry through the queued startup so
|
||||
@@ -1836,7 +1835,6 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
selectedRepoIsGit,
|
||||
selectedRepoRequiresConnection,
|
||||
settings?.agentCmdOverrides,
|
||||
settings?.agentStatusHooksEnabled,
|
||||
setSidebarOpen,
|
||||
setupDecision,
|
||||
sparseEnabled,
|
||||
@@ -1980,8 +1978,7 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
agent,
|
||||
draft: quickDraftPrompt,
|
||||
cmdOverrides: settings?.agentCmdOverrides ?? {},
|
||||
platform: CLIENT_PLATFORM,
|
||||
useOrcaClaudeAgentStatusSettings: settings?.agentStatusHooksEnabled !== false
|
||||
platform: CLIENT_PLATFORM
|
||||
})
|
||||
|
||||
let startupPlan: ReturnType<typeof buildAgentStartupPlan> = null
|
||||
@@ -1999,8 +1996,7 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
prompt: quickPrompt,
|
||||
cmdOverrides: settings?.agentCmdOverrides ?? {},
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: true,
|
||||
useOrcaClaudeAgentStatusSettings: settings?.agentStatusHooksEnabled !== false
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
if (startupPlan && quickDraftPrompt) {
|
||||
startupPlan.draftPrompt = quickDraftPrompt
|
||||
@@ -2077,7 +2073,6 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
selectedRepoIsGit,
|
||||
selectedRepoRequiresConnection,
|
||||
settings?.agentCmdOverrides,
|
||||
settings?.agentStatusHooksEnabled,
|
||||
setSidebarOpen,
|
||||
setupDecision,
|
||||
sparseEnabled,
|
||||
|
||||
@@ -18,8 +18,6 @@ const mockSubscribeToPtyExit = vi.fn()
|
||||
const mockPasteDraftWhenAgentReady = vi.fn()
|
||||
const mockMarkTrusted = vi.fn()
|
||||
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
||||
const CLAUDE_SCOPED_SETTINGS =
|
||||
'claude --settings "$HOME/.orca/agent-hooks/claude-agent-status-settings.json"'
|
||||
|
||||
function expectStablePaneSpawn(): string {
|
||||
const spawnArgs = mockSpawn.mock.calls[0]?.[0]
|
||||
@@ -124,7 +122,7 @@ describe('launchAgentBackgroundSession', () => {
|
||||
expect(mockSpawn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
cwd: '/repo/worktree',
|
||||
command: `${CLAUDE_SCOPED_SETTINGS} 'run the automation'`,
|
||||
command: "claude 'run the automation'",
|
||||
env: expect.objectContaining({
|
||||
ORCA_TAB_ID: 'tab-1',
|
||||
ORCA_WORKTREE_ID: 'wt-1'
|
||||
@@ -269,10 +267,7 @@ describe('launchAgentBackgroundSession', () => {
|
||||
dataSidecar('user@remote repo % ')
|
||||
vi.advanceTimersByTime(50)
|
||||
|
||||
expect(mockWrite).toHaveBeenCalledWith(
|
||||
'pty-1',
|
||||
`${CLAUDE_SCOPED_SETTINGS} 'run the automation'\r`
|
||||
)
|
||||
expect(mockWrite).toHaveBeenCalledWith('pty-1', "claude 'run the automation'\r")
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
@@ -306,7 +301,7 @@ describe('launchAgentBackgroundSession', () => {
|
||||
method: 'terminal.create',
|
||||
params: expect.objectContaining({
|
||||
worktree: 'wt-1',
|
||||
command: `${CLAUDE_SCOPED_SETTINGS} 'run the automation'`,
|
||||
command: "claude 'run the automation'",
|
||||
env: expect.objectContaining({
|
||||
ORCA_PANE_KEY: `tab-1:${leafId}`,
|
||||
ORCA_TAB_ID: 'tab-1',
|
||||
|
||||
@@ -63,7 +63,6 @@ export async function launchAgentBackgroundSession(
|
||||
}
|
||||
}
|
||||
const cmdOverrides = store.settings?.agentCmdOverrides ?? {}
|
||||
const useOrcaAgentStatusHooks = store.settings?.agentStatusHooksEnabled !== false
|
||||
const trimmedPrompt = prompt?.trim() ?? ''
|
||||
const hasPrompt = trimmedPrompt.length > 0
|
||||
const isFollowupPath = TUI_AGENT_CONFIG[agent].promptInjectionMode === 'stdin-after-start'
|
||||
@@ -76,8 +75,7 @@ export async function launchAgentBackgroundSession(
|
||||
prompt: '',
|
||||
cmdOverrides,
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: true,
|
||||
useOrcaClaudeAgentStatusSettings: useOrcaAgentStatusHooks
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
pasteDraftAfterLaunch = trimmedPrompt
|
||||
} else {
|
||||
@@ -86,8 +84,7 @@ export async function launchAgentBackgroundSession(
|
||||
prompt: hasPrompt ? trimmedPrompt : '',
|
||||
cmdOverrides,
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: !hasPrompt,
|
||||
useOrcaClaudeAgentStatusSettings: useOrcaAgentStatusHooks
|
||||
allowEmptyPromptLaunch: !hasPrompt
|
||||
})
|
||||
}
|
||||
if (!startupPlan) {
|
||||
|
||||
@@ -72,7 +72,6 @@ export function launchAgentInNewTab(args: LaunchAgentInNewTabArgs): LaunchAgentI
|
||||
} = args
|
||||
const store = useAppStore.getState()
|
||||
const cmdOverrides = store.settings?.agentCmdOverrides ?? {}
|
||||
const useOrcaAgentStatusHooks = store.settings?.agentStatusHooksEnabled !== false
|
||||
const trimmedPrompt = prompt?.trim() ?? ''
|
||||
const hasPrompt = trimmedPrompt.length > 0
|
||||
const isFollowupPath = TUI_AGENT_CONFIG[agent].promptInjectionMode === 'stdin-after-start'
|
||||
@@ -96,8 +95,7 @@ export function launchAgentInNewTab(args: LaunchAgentInNewTabArgs): LaunchAgentI
|
||||
prompt: '',
|
||||
cmdOverrides,
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: true,
|
||||
useOrcaClaudeAgentStatusSettings: useOrcaAgentStatusHooks
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
pasteDraftAfterLaunch = trimmedPrompt
|
||||
submitPastedPrompt = true
|
||||
@@ -107,8 +105,7 @@ export function launchAgentInNewTab(args: LaunchAgentInNewTabArgs): LaunchAgentI
|
||||
agent,
|
||||
draft: trimmedPrompt,
|
||||
cmdOverrides,
|
||||
platform: CLIENT_PLATFORM,
|
||||
useOrcaClaudeAgentStatusSettings: useOrcaAgentStatusHooks
|
||||
platform: CLIENT_PLATFORM
|
||||
})
|
||||
if (draftLaunchPlan) {
|
||||
startupPlan = {
|
||||
@@ -124,8 +121,7 @@ export function launchAgentInNewTab(args: LaunchAgentInNewTabArgs): LaunchAgentI
|
||||
prompt: '',
|
||||
cmdOverrides,
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: true,
|
||||
useOrcaClaudeAgentStatusSettings: useOrcaAgentStatusHooks
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
pasteDraftAfterLaunch = trimmedPrompt
|
||||
}
|
||||
@@ -135,8 +131,7 @@ export function launchAgentInNewTab(args: LaunchAgentInNewTabArgs): LaunchAgentI
|
||||
prompt: '',
|
||||
cmdOverrides,
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: true,
|
||||
useOrcaClaudeAgentStatusSettings: useOrcaAgentStatusHooks
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
pasteDraftAfterLaunch = trimmedPrompt
|
||||
} else {
|
||||
@@ -145,8 +140,7 @@ export function launchAgentInNewTab(args: LaunchAgentInNewTabArgs): LaunchAgentI
|
||||
prompt: hasPrompt ? trimmedPrompt : '',
|
||||
cmdOverrides,
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: !hasPrompt,
|
||||
useOrcaClaudeAgentStatusSettings: useOrcaAgentStatusHooks
|
||||
allowEmptyPromptLaunch: !hasPrompt
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -298,8 +298,7 @@ export async function launchWorkItemDirect(args: LaunchWorkItemDirectArgs): Prom
|
||||
agent: effectiveAgent,
|
||||
draft: draftContent,
|
||||
cmdOverrides: settings?.agentCmdOverrides ?? {},
|
||||
platform: CLIENT_PLATFORM,
|
||||
useOrcaClaudeAgentStatusSettings: settings?.agentStatusHooksEnabled !== false
|
||||
platform: CLIENT_PLATFORM
|
||||
})
|
||||
if (draftLaunchPlan) {
|
||||
startupPlan = {
|
||||
@@ -316,8 +315,7 @@ export async function launchWorkItemDirect(args: LaunchWorkItemDirectArgs): Prom
|
||||
prompt: '',
|
||||
cmdOverrides: settings?.agentCmdOverrides ?? {},
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: true,
|
||||
useOrcaClaudeAgentStatusSettings: settings?.agentStatusHooksEnabled !== false
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ export function buildOnboardingFolderAgentStartup(
|
||||
prompt: '',
|
||||
cmdOverrides: settings.agentCmdOverrides ?? {},
|
||||
platform: getClientPlatform(),
|
||||
allowEmptyPromptLaunch: true,
|
||||
useOrcaClaudeAgentStatusSettings: settings.agentStatusHooksEnabled !== false
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
if (!startupPlan) {
|
||||
return undefined
|
||||
|
||||
@@ -97,9 +97,7 @@ function buildCreatedAgentReopenStartup(worktree: Worktree):
|
||||
prompt: '',
|
||||
cmdOverrides: useAppStore.getState().settings?.agentCmdOverrides ?? {},
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: true,
|
||||
useOrcaClaudeAgentStatusSettings:
|
||||
useAppStore.getState().settings?.agentStatusHooksEnabled !== false
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
if (!startupPlan) {
|
||||
return undefined
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
export const ORCA_CLAUDE_AGENT_STATUS_SETTINGS_ENV = 'ORCA_CLAUDE_AGENT_STATUS_SETTINGS'
|
||||
export const ORCA_CLAUDE_AGENT_STATUS_SETTINGS_FILE = 'claude-agent-status-settings.json'
|
||||
|
||||
type ClaudeSettingsShell = 'posix' | 'powershell' | 'cmd'
|
||||
|
||||
export function appendOrcaClaudeAgentStatusSettings(
|
||||
command: string,
|
||||
shell: ClaudeSettingsShell
|
||||
): string {
|
||||
// Why: Claude's --settings is a per-process overlay. CLAUDE_CONFIG_DIR
|
||||
// would fork auth/session state and normal external Claude launches.
|
||||
if (shell === 'powershell') {
|
||||
return `${command} --settings $Env:${ORCA_CLAUDE_AGENT_STATUS_SETTINGS_ENV}`
|
||||
}
|
||||
if (shell === 'cmd') {
|
||||
return `${command} --settings "%${ORCA_CLAUDE_AGENT_STATUS_SETTINGS_ENV}%"`
|
||||
}
|
||||
return `${command} --settings "$HOME/.orca/agent-hooks/${ORCA_CLAUDE_AGENT_STATUS_SETTINGS_FILE}"`
|
||||
}
|
||||
@@ -47,55 +47,16 @@ describe('tui agent startup plans', () => {
|
||||
expect(plan?.launchCommand).toBe("codex 'fix it'")
|
||||
})
|
||||
|
||||
it('does not inject Codex profile flags through the shared agent-status option', () => {
|
||||
const plan = buildAgentStartupPlan({
|
||||
agent: 'codex',
|
||||
prompt: 'fix it',
|
||||
cmdOverrides: {},
|
||||
platform: 'linux',
|
||||
useOrcaClaudeAgentStatusSettings: true
|
||||
})
|
||||
|
||||
expect(plan?.launchCommand).toBe("codex 'fix it'")
|
||||
expect(plan?.launchCommand).not.toContain('--profile')
|
||||
expect(plan?.launchCommand).not.toContain('orca-agent-status')
|
||||
})
|
||||
|
||||
it('launches Claude with the Orca settings file when agent status hooks are enabled', () => {
|
||||
it('launches Claude without Orca settings injection', () => {
|
||||
const plan = buildAgentStartupPlan({
|
||||
agent: 'claude',
|
||||
prompt: 'fix it',
|
||||
cmdOverrides: {},
|
||||
platform: 'linux',
|
||||
useOrcaClaudeAgentStatusSettings: true
|
||||
platform: 'linux'
|
||||
})
|
||||
|
||||
expect(plan?.launchCommand).toBe(
|
||||
'claude --settings "$HOME/.orca/agent-hooks/claude-agent-status-settings.json" \'fix it\''
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the target shell syntax for Claude settings injection', () => {
|
||||
expect(
|
||||
buildAgentStartupPlan({
|
||||
agent: 'claude',
|
||||
prompt: 'fix it',
|
||||
cmdOverrides: {},
|
||||
platform: 'win32',
|
||||
useOrcaClaudeAgentStatusSettings: true
|
||||
})?.launchCommand
|
||||
).toBe("claude --settings $Env:ORCA_CLAUDE_AGENT_STATUS_SETTINGS 'fix it'")
|
||||
|
||||
expect(
|
||||
buildAgentStartupPlan({
|
||||
agent: 'claude',
|
||||
prompt: 'fix it',
|
||||
cmdOverrides: {},
|
||||
platform: 'win32',
|
||||
shell: 'cmd',
|
||||
useOrcaClaudeAgentStatusSettings: true
|
||||
})?.launchCommand
|
||||
).toBe('claude --settings "%ORCA_CLAUDE_AGENT_STATUS_SETTINGS%" "fix it"')
|
||||
expect(plan?.launchCommand).toBe("claude 'fix it'")
|
||||
expect(plan?.launchCommand).not.toContain('--settings')
|
||||
})
|
||||
|
||||
it('leaves Claude command overrides untouched', () => {
|
||||
@@ -103,8 +64,7 @@ describe('tui agent startup plans', () => {
|
||||
agent: 'claude',
|
||||
prompt: 'fix it',
|
||||
cmdOverrides: { claude: 'claude --dangerously-skip-permissions' },
|
||||
platform: 'linux',
|
||||
useOrcaClaudeAgentStatusSettings: true
|
||||
platform: 'linux'
|
||||
})
|
||||
|
||||
expect(plan?.launchCommand).toBe("claude --dangerously-skip-permissions 'fix it'")
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { isShellProcess } from './agent-detection'
|
||||
import { appendOrcaClaudeAgentStatusSettings } from './claude-settings'
|
||||
import { TUI_AGENT_CONFIG } from './tui-agent-config'
|
||||
import type { TuiAgent } from './types'
|
||||
|
||||
@@ -49,16 +48,12 @@ function resolveBaseCommand(args: {
|
||||
agent: TuiAgent
|
||||
cmdOverrides: Partial<Record<TuiAgent, string>>
|
||||
shell: AgentStartupShell
|
||||
useOrcaClaudeAgentStatusSettings?: boolean
|
||||
}): string {
|
||||
const override = args.cmdOverrides[args.agent]
|
||||
if (override) {
|
||||
return override
|
||||
}
|
||||
const command = TUI_AGENT_CONFIG[args.agent].launchCmd
|
||||
if (args.agent === 'claude' && args.useOrcaClaudeAgentStatusSettings) {
|
||||
return appendOrcaClaudeAgentStatusSettings(command, args.shell)
|
||||
}
|
||||
// Why: Codex status hooks live in Orca's runtime CODEX_HOME; adding
|
||||
// --profile-v2 makes Codex load a second hook representation and warn.
|
||||
return command
|
||||
@@ -71,7 +66,6 @@ export function buildAgentStartupPlan(args: {
|
||||
platform: NodeJS.Platform
|
||||
shell?: AgentStartupShell
|
||||
allowEmptyPromptLaunch?: boolean
|
||||
useOrcaClaudeAgentStatusSettings?: boolean
|
||||
}): AgentStartupPlan | null {
|
||||
const { agent, prompt, cmdOverrides, platform, allowEmptyPromptLaunch = false } = args
|
||||
const shell = resolveStartupShell(platform, args.shell)
|
||||
@@ -80,8 +74,7 @@ export function buildAgentStartupPlan(args: {
|
||||
const baseCommand = resolveBaseCommand({
|
||||
agent,
|
||||
cmdOverrides,
|
||||
shell,
|
||||
useOrcaClaudeAgentStatusSettings: args.useOrcaClaudeAgentStatusSettings
|
||||
shell
|
||||
})
|
||||
|
||||
if (!trimmedPrompt) {
|
||||
@@ -155,7 +148,6 @@ export function buildAgentDraftLaunchPlan(args: {
|
||||
cmdOverrides: Partial<Record<TuiAgent, string>>
|
||||
platform: NodeJS.Platform
|
||||
shell?: AgentStartupShell
|
||||
useOrcaClaudeAgentStatusSettings?: boolean
|
||||
}): AgentDraftLaunchPlan | null {
|
||||
const { agent, draft, cmdOverrides, platform } = args
|
||||
const shell = resolveStartupShell(platform, args.shell)
|
||||
@@ -167,8 +159,7 @@ export function buildAgentDraftLaunchPlan(args: {
|
||||
const baseCommand = resolveBaseCommand({
|
||||
agent,
|
||||
cmdOverrides,
|
||||
shell,
|
||||
useOrcaClaudeAgentStatusSettings: args.useOrcaClaudeAgentStatusSettings
|
||||
shell
|
||||
})
|
||||
if (config.draftPromptFlag) {
|
||||
const quoted = quoteStartupArg(trimmed, shell)
|
||||
|
||||
Reference in New Issue
Block a user