mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
* Suppress Git Credential Manager OAuth popup on git clone (fixes #7652)
Orca's git runner disables the interactive credential prompt on every git
call that goes through gitExecFileAsync/gitStreamStdout, but the two raw
'git clone' spawns (desktop repos:clone and the runtime clone path) passed
no env, so they inherited process.env with no guard. On Windows a clone
that needs GitHub auth then makes Git Credential Manager pop its
'Connect to GitHub' OAuth window, and in a network-restricted intranet the
browser/device flow never completes while git's credential retry re-pops it.
Apply nonInteractiveGitEnv() to both clone spawns so the prompt is
suppressed (GCM_INTERACTIVE=never, credential.interactive=false,
GIT_TERMINAL_PROMPT=0). The credential *helper* is kept, so cached-token
clones for private repos still work; only the interactive fallback popup is
disabled and the clone fails fast with a clear error instead.
* Suppress GCM OAuth popup in agent terminals and setup hooks too (#7652)
The clone-spawn fix stopped Orca's own managed git from popping Git
Credential Manager, but git run in terminals and setup scripts inherited
process.env with no guard. That is the more likely source of the reported
loop: agents are told to run 'git pull --rebase'/'git fetch'/retry 'git
push' (preamble + conflict/push-failure prompts), and each retry re-pops
GCM's 'Connect to GitHub' window in a network-restricted intranet.
Apply the credential-prompt guard to:
- setup/archive/hook scripts (hooks.ts non-WSL exec env), which run
unattended on worktree create/archive.
- the shared PTY host env (buildPtyHostEnv), via a small
applyTerminalGitCredentialPromptGuard helper. Agent terminals are
guarded unconditionally (they cannot dismiss a GUI popup); user
terminals are guarded by default via the new
terminalSuppressGitCredentialPrompt setting so power users can opt out.
The credential helper is kept, so cached gh auth still works; only the
interactive fallback prompt is disabled. Verified end-to-end in a real
Orca terminal (GIT_TERMINAL_PROMPT=0 + GCM_INTERACTIVE=never by default;
absent when the opt-out is set).
* Scope user-terminal credential guard to Windows, add settings toggle, forward guard into WSL (#7652)
* Retrigger PR checks (Actions dropped the synchronize dispatch for 57e7ce249)
* Keep shell locale out of the terminal/hook credential guard (#7652 review fix)
* Fix Fable review findings: guard WSL hook branch, wire settings search, catalog keyword keys, sparse-env askpass, one-shot agent classification (#7652)
* fix(terminal): harden Git credential popup guard
* test(pty): cover SSH credential guard setting
* fix(git): guard remote clones and setup runners
* fix(git): scope credential guards to unattended work
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
248 lines
9.6 KiB
TypeScript
248 lines
9.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
isAgentForegroundWrapperProcess,
|
|
isExpectedAgentProcess,
|
|
isRecognizedAgentType,
|
|
recognizeAgentProcess,
|
|
recognizeAgentProcessFromCommandLine
|
|
} from './agent-process-recognition'
|
|
|
|
describe('agent process recognition', () => {
|
|
it('recognizes packaged Codex foreground process names', () => {
|
|
expect(recognizeAgentProcess('codex-aarch64-ap')).toEqual({
|
|
agent: 'codex',
|
|
processName: 'codex-aarch64-ap'
|
|
})
|
|
expect(isRecognizedAgentType('codex-aarch64-ap')).toBe(true)
|
|
})
|
|
|
|
it('recognizes the OpenClaude foreground process', () => {
|
|
expect(recognizeAgentProcess('/usr/local/bin/openclaude')).toEqual({
|
|
agent: 'openclaude',
|
|
processName: 'openclaude'
|
|
})
|
|
expect(isRecognizedAgentType('openclaude')).toBe(true)
|
|
expect(isExpectedAgentProcess('/usr/local/bin/openclaude', 'claude')).toBe(false)
|
|
})
|
|
|
|
it('recognizes the Droid foreground process on Windows', () => {
|
|
expect(recognizeAgentProcess(String.raw`C:\Users\dev\AppData\Roaming\npm\droid.cmd`)).toEqual({
|
|
agent: 'droid',
|
|
processName: 'droid'
|
|
})
|
|
})
|
|
|
|
it('matches expected agents from platform-specific foreground process paths', () => {
|
|
expect(recognizeAgentProcess('claude')).toEqual({
|
|
agent: 'claude',
|
|
processName: 'claude'
|
|
})
|
|
expect(
|
|
isExpectedAgentProcess(String.raw`C:\Users\dev\AppData\Roaming\npm\claude.exe`, 'claude')
|
|
).toBe(true)
|
|
expect(isExpectedAgentProcess('/usr/local/bin/claude', 'claude')).toBe(true)
|
|
expect(isExpectedAgentProcess('powershell.exe', 'claude')).toBe(false)
|
|
})
|
|
|
|
it('does not recognize Claude print-mode hook subprocesses as interactive agents', () => {
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
'claude --print --model haiku "Analyze this conversation and determine: Does the assistant have more autonomous work to do RIGHT NOW?"'
|
|
)
|
|
).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`/home/dev/.local/bin/claude -p "Context: This summary will be shown in a list"`
|
|
)
|
|
).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`C:\Users\dev\AppData\Roaming\npm\claude.exe --output-format=json "hook prompt"`
|
|
)
|
|
).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('claude --resume abc123')).toEqual({
|
|
agent: 'claude',
|
|
processName: 'claude'
|
|
})
|
|
})
|
|
|
|
it('recognizes Command Code without classifying Windows cmd.exe as an agent', () => {
|
|
expect(recognizeAgentProcess('command-code')).toEqual({
|
|
agent: 'command-code',
|
|
processName: 'command-code'
|
|
})
|
|
expect(
|
|
recognizeAgentProcess(String.raw`C:\Users\dev\AppData\Roaming\npm\command-code.cmd`)
|
|
).toEqual({
|
|
agent: 'command-code',
|
|
processName: 'command-code'
|
|
})
|
|
expect(isRecognizedAgentType('command-code')).toBe(true)
|
|
expect(isRecognizedAgentType('cmd.exe')).toBe(false)
|
|
expect(recognizeAgentProcess('cmd.exe')).toBeNull()
|
|
})
|
|
|
|
it('recognizes Ante without classifying ante-prefixed path fragments as the agent', () => {
|
|
expect(recognizeAgentProcess('ante')).toEqual({
|
|
agent: 'ante',
|
|
processName: 'ante'
|
|
})
|
|
expect(recognizeAgentProcess('/Users/dev/.ante/bin/ante')).toEqual({
|
|
agent: 'ante',
|
|
processName: 'ante'
|
|
})
|
|
expect(isExpectedAgentProcess('/Users/dev/.ante/bin/ante', 'ante')).toBe(true)
|
|
expect(isRecognizedAgentType('ante')).toBe(true)
|
|
// Why: 'ante' is a common token in directory and binary names; only the
|
|
// exact normalized basename may classify as the agent.
|
|
expect(recognizeAgentProcess('ante-obsidian')).toBeNull()
|
|
expect(recognizeAgentProcess('antechamber')).toBeNull()
|
|
expect(isExpectedAgentProcess('ante-obsidian', 'ante')).toBe(false)
|
|
})
|
|
|
|
it('does not recognize Ante headless one-shot commands as interactive agents', () => {
|
|
expect(recognizeAgentProcessFromCommandLine('ante -p "summarize this diff"')).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('ante -psummarize')).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('ante --prompt "review this for security issues"')
|
|
).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('ante --prompt=review --output-format minimal')
|
|
).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('ante --resume ses_123')).toEqual({
|
|
agent: 'ante',
|
|
processName: 'ante'
|
|
})
|
|
})
|
|
|
|
it('does not recognize wrapped Ante headless one-shot commands as interactive agents', () => {
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('node /Users/dev/.ante/bin/ante --prompt "review"')
|
|
).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`node C:\Users\dev\.ante\bin\ante.cmd -p review`
|
|
)
|
|
).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('node /Users/dev/.ante/bin/ante')).toEqual({
|
|
agent: 'ante',
|
|
processName: 'ante'
|
|
})
|
|
})
|
|
|
|
it('recognizes Mistral Vibe by its installed executable and legacy alias', () => {
|
|
expect(recognizeAgentProcess('/home/dev/.local/bin/vibe')).toEqual({
|
|
agent: 'mistral-vibe',
|
|
processName: 'vibe'
|
|
})
|
|
expect(recognizeAgentProcess('mistral-vibe')).toEqual({
|
|
agent: 'mistral-vibe',
|
|
processName: 'mistral-vibe'
|
|
})
|
|
expect(isRecognizedAgentType('vibe')).toBe(true)
|
|
})
|
|
|
|
it('recognizes Qwen Code by its installed qwen executable', () => {
|
|
expect(recognizeAgentProcess('/home/dev/.local/bin/qwen')).toEqual({
|
|
agent: 'qwen-code',
|
|
processName: 'qwen'
|
|
})
|
|
expect(recognizeAgentProcess(String.raw`C:\Users\dev\AppData\Roaming\npm\qwen.cmd`)).toEqual({
|
|
agent: 'qwen-code',
|
|
processName: 'qwen'
|
|
})
|
|
expect(isExpectedAgentProcess('/usr/local/bin/qwen', 'qwen')).toBe(true)
|
|
expect(isRecognizedAgentType('qwen')).toBe(true)
|
|
})
|
|
|
|
it('recognizes agent CLIs launched through interpreter wrappers', () => {
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('node /Users/dev/.nvm/versions/node/bin/codex')
|
|
).toEqual({ agent: 'codex', processName: 'codex' })
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('node /Users/dev/.nvm/versions/node/bin/gemini')
|
|
).toEqual({ agent: 'gemini', processName: 'gemini' })
|
|
expect(recognizeAgentProcessFromCommandLine('python3 /opt/homebrew/bin/hermes --tui')).toEqual({
|
|
agent: 'hermes',
|
|
processName: 'hermes'
|
|
})
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('python3.12 /opt/homebrew/bin/hermes --tui')
|
|
).toEqual({
|
|
agent: 'hermes',
|
|
processName: 'hermes'
|
|
})
|
|
expect(recognizeAgentProcessFromCommandLine('python -m aider')).toEqual({
|
|
agent: 'aider',
|
|
processName: 'aider'
|
|
})
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`python C:\Users\dev\AppData\Roaming\Python\Python312\Scripts\aider.py`
|
|
)
|
|
).toEqual({ agent: 'aider', processName: 'aider' })
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`node C:\Users\dev\AppData\Roaming\npm\codex.cmd`
|
|
)
|
|
).toEqual({ agent: 'codex', processName: 'codex' })
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`node C:\Users\dev\AppData\Roaming\npm\node_modules\@openai\codex\bin\codex.js`
|
|
)
|
|
).toEqual({ agent: 'codex', processName: 'codex' })
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`node C:\Users\dev\AppData\Roaming\npm\node_modules\@google\gemini-cli\bundle\gemini.mjs`
|
|
)
|
|
).toEqual({ agent: 'gemini', processName: 'gemini' })
|
|
})
|
|
|
|
it('recognizes only the agent subcommand of the generic Orca CLI', () => {
|
|
expect(recognizeAgentProcessFromCommandLine('orca claude-teams')).toEqual({
|
|
agent: 'claude-agent-teams',
|
|
processName: 'orca'
|
|
})
|
|
expect(recognizeAgentProcessFromCommandLine('orca status')).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('orca-dev terminal list')).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('node /usr/local/bin/orca claude-teams')).toEqual({
|
|
agent: 'claude-agent-teams',
|
|
processName: 'orca'
|
|
})
|
|
expect(recognizeAgentProcessFromCommandLine('node /usr/local/bin/orca status')).toBeNull()
|
|
})
|
|
|
|
it('does not classify prompt text as a wrapped agent command', () => {
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
'node /tmp/not-an-agent.js "compare opencode vs orca in Gemini CLI"'
|
|
)
|
|
).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine(String.raw`node C:\tmp\not-an-agent.js`)).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`node C:\repo\server.js --plugin C:\tmp\codex.js`
|
|
)
|
|
).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine(String.raw`node C:\repo\codex.js`)).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine(String.raw`node C:\repo\gemini.mjs`)).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine(String.raw`python C:\repo\aider.py`)).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('python -m not_aider')).toBeNull()
|
|
})
|
|
|
|
it('identifies only foreground processes that can wrap agent entrypoints', () => {
|
|
expect(isAgentForegroundWrapperProcess('node.exe')).toBe(true)
|
|
expect(isAgentForegroundWrapperProcess('/usr/bin/python3')).toBe(true)
|
|
expect(isAgentForegroundWrapperProcess('python3.12.exe')).toBe(true)
|
|
expect(isAgentForegroundWrapperProcess('bash')).toBe(false)
|
|
expect(isAgentForegroundWrapperProcess('vim.exe')).toBe(false)
|
|
})
|
|
|
|
it('recognizes versioned Grok process names observed from the installed CLI', () => {
|
|
expect(recognizeAgentProcess('grok-0.2.51')).toEqual({
|
|
agent: 'grok',
|
|
processName: 'grok-0.2.51'
|
|
})
|
|
})
|
|
})
|