mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 08:02:02 +00:00
* feat(agents): add first-class Muse Code harness Add Muse as a supervised Orca agent across desktop, mobile, session history, source control, local hooks, SSH, WSL, and native Windows. Preserve user settings, support Muse 1.3 hook environment allowlists, and recognize versioned foreground processes. Include question, waiting, completion, resume, and readiness coverage. Co-authored-by: homesh-dev <300847526+homesh-dev@users.noreply.github.com> Co-authored-by: jeffhuen <32542276+jeffhuen@users.noreply.github.com> Co-authored-by: John Cusack <johncusackccm@gmail.com> Co-authored-by: Adrien De oliveira <75085839+adriendeoliveira@users.noreply.github.com> * test(agents): cover Muse remote hook registration * test(agents): cover Muse hook and source-control contracts * test(agents): exclude Muse hook metadata from script mode check * test(agents): keep Muse skill picker coverage stable * test(ai-vault): include Muse in every-agent fixture * test(mobile): repin Muse agent icon closure * fix(muse): detect questions and approvals from structured Muse signals Muse 1.3 fires no hook for request_user_input, so a pending question left the pane "working". Its internal reminder subagents also post hooks with their own session ids (even after Stop), which surfaced "tool failed" rows and flipped finished panes back to working. - Read pending questions from Muse's session log (user_input_prompt_requested/settled) via the existing transcript poll, now generalized from Codex subagents to Muse on main and relay. - Drop child-session hooks (SubagentStart ids, or turn_id === session_id). - Treat Notification permission_prompt as the approval wait; PermissionRequest also fires for auto-approved calls, so it only caches the approval card. - Ignore Notification copy as the prompt; poll replays are not new prompts or turn boundaries. - Allowlist USERPROFILE so Windows cmd AutoRun doesn't fail every hook. * perf(muse): parse only question events from the session log Most Muse session-log lines are large model/tool records. Filter raw lines by the user_input_prompt_ marker before JSON.parse via an optional readJsonlCursor line filter. * fix(muse): unwrap batched log records and scope questions to the live turn Review follow-ups: question events inside retained_frame batches were skipped, and a question left open by a crash or interrupt stayed pending for the pane's life. Share the history scanner's retained_frame unwrapper, and only report a pending question whose run_id matches the hook turn_id. * refactor(muse): drop type assertion in retained_frame unwrap * fix(agent-hooks): satisfy exhaustive-switch lint in transcript poll policy --------- Co-authored-by: Adrien De oliveira <75085839+adriendeoliveira@users.noreply.github.com>
549 lines
22 KiB
TypeScript
549 lines
22 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { withFreshOmpLaunch } from './omp-fresh-launch'
|
|
import { buildAgentDraftLaunchPlan } from './tui-agent-startup'
|
|
import { detectExplicitPiAgentKindFromCommand } from './pi-agent-kind'
|
|
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.each(['posix', 'powershell', 'cmd'] as const)(
|
|
'recognizes fresh OMP commands and drafts in %s',
|
|
(shell) => {
|
|
for (const command of ['omp', 'omp launch', 'omp --model example']) {
|
|
const generated = withFreshOmpLaunch(command, shell)
|
|
expect(recognizeAgentProcessFromCommandLine(generated)).toEqual({
|
|
agent: 'omp',
|
|
processName: 'omp'
|
|
})
|
|
expect(detectExplicitPiAgentKindFromCommand(generated)).toBe('omp')
|
|
}
|
|
const draft = buildAgentDraftLaunchPlan({
|
|
agent: 'omp',
|
|
draft: 'task',
|
|
cmdOverrides: {},
|
|
platform: 'linux',
|
|
shell
|
|
})
|
|
expect(recognizeAgentProcessFromCommandLine(draft?.launchCommand)).toEqual({
|
|
agent: 'omp',
|
|
processName: 'omp'
|
|
})
|
|
expect(detectExplicitPiAgentKindFromCommand(draft?.launchCommand)).toBe('omp')
|
|
}
|
|
)
|
|
|
|
it('does not classify embedded fresh-launch text as an agent command', () => {
|
|
for (const command of [
|
|
`echo 'omp --config "$ORCA_OMP_FRESH_CONFIG"'`,
|
|
`echo '${withFreshOmpLaunch('omp', 'posix')}'`
|
|
]) {
|
|
expect(recognizeAgentProcessFromCommandLine(command)).toBeNull()
|
|
expect(detectExplicitPiAgentKindFromCommand(command)).toBeNull()
|
|
}
|
|
})
|
|
|
|
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 Trae by its traecli binary, not the ambiguous trae-cli name', () => {
|
|
expect(recognizeAgentProcess('traecli')).toEqual({
|
|
agent: 'trae',
|
|
processName: 'traecli'
|
|
})
|
|
expect(recognizeAgentProcess('/Users/dev/.local/bin/traecli')).toEqual({
|
|
agent: 'trae',
|
|
processName: 'traecli'
|
|
})
|
|
expect(isExpectedAgentProcess('/Users/dev/.local/bin/traecli', 'traecli')).toBe(true)
|
|
expect(isRecognizedAgentType('traecli')).toBe(true)
|
|
// Why: `trae-cli` and `trae-agent` both name the unrelated open-source bytedance/trae-agent.
|
|
expect(recognizeAgentProcess('trae-cli')).toBeNull()
|
|
expect(recognizeAgentProcess('trae-agent')).toBeNull()
|
|
})
|
|
|
|
it('does not recognize Trae headless one-shot commands as interactive agents', () => {
|
|
expect(recognizeAgentProcessFromCommandLine('traecli -p "summarize this diff"')).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('traecli --print "review this"')).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('traecli --output-format json "review this"')
|
|
).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('traecli --output-format=stream-json review')
|
|
).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('traecli --resume AUTO')).toEqual({
|
|
agent: 'trae',
|
|
processName: 'traecli'
|
|
})
|
|
// Why: past `--` nothing is a flag, so this is the interactive pane Orca itself launches.
|
|
expect(recognizeAgentProcessFromCommandLine('traecli -- "--print the release notes"')).toEqual({
|
|
agent: 'trae',
|
|
processName: 'traecli'
|
|
})
|
|
})
|
|
|
|
it('recognizes Muse by its muse binary', () => {
|
|
expect(recognizeAgentProcess('muse')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse'
|
|
})
|
|
expect(recognizeAgentProcess('/Users/dev/.local/bin/muse')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse'
|
|
})
|
|
expect(isExpectedAgentProcess('/Users/dev/.local/bin/muse', 'muse')).toBe(true)
|
|
expect(isRecognizedAgentType('muse')).toBe(true)
|
|
})
|
|
|
|
it('recognizes Muse by its versioned muse-bin sibling binary', () => {
|
|
// Why: the `muse` launcher execs `muse-bin-<version>` (a 242MB sibling),
|
|
// so the live foreground process carries the versioned name — truncated
|
|
// to `muse-bin-1.0.3-R` in macOS comm output (verified on-device).
|
|
expect(recognizeAgentProcess('muse-bin-1.0.3-R2198.1')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse-bin-1.0.3-r2198.1'
|
|
})
|
|
expect(recognizeAgentProcess('muse-bin-1.0.3-R')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse-bin-1.0.3-r'
|
|
})
|
|
expect(recognizeAgentProcess('/Users/dev/.local/bin/muse-bin-1.0.3-R2198.1')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse-bin-1.0.3-r2198.1'
|
|
})
|
|
})
|
|
|
|
it('does not recognize Muse headless exec commands as interactive agents', () => {
|
|
expect(recognizeAgentProcessFromCommandLine('muse exec "summarize this diff"')).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('muse exec --json "review this" > result.jsonl')
|
|
).toBeNull()
|
|
// Why: a bare `exec` token dispatches as the subcommand even past `--`
|
|
// (verified: `muse -- resume` still resumes), so this errors instead of
|
|
// hosting a pane — never an interactive agent.
|
|
expect(recognizeAgentProcessFromCommandLine('muse -- exec "summarize this diff"')).toBeNull()
|
|
// Why: a whole-prompt `muse 'exec'` takes the exec missing-prompt error
|
|
// path, not a TUI, so filtering it is correct.
|
|
expect(recognizeAgentProcessFromCommandLine("muse 'exec'")).toBeNull()
|
|
// Why: `muse resume` reopens the interactive TUI, so it still hosts a live session.
|
|
expect(recognizeAgentProcessFromCommandLine('muse resume')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse'
|
|
})
|
|
// Why: `muse -- resume` still dispatches to the resume subcommand (verified
|
|
// against muse 1.0.3), which reopens the interactive TUI.
|
|
expect(recognizeAgentProcessFromCommandLine('muse -- resume')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse'
|
|
})
|
|
// Why: the prompt is one quoted argv, so it never equals the bare `exec`
|
|
// token — this is the interactive pane Orca itself launches.
|
|
expect(recognizeAgentProcessFromCommandLine('muse -- "exec the release notes"')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse'
|
|
})
|
|
})
|
|
|
|
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 Kimi Code by the kimi-code process its launcher becomes', () => {
|
|
expect(recognizeAgentProcess('/home/dev/.kimi-code/bin/kimi')).toEqual({
|
|
agent: 'kimi',
|
|
processName: 'kimi'
|
|
})
|
|
expect(recognizeAgentProcess('kimi-code')).toEqual({
|
|
agent: 'kimi',
|
|
processName: 'kimi-code'
|
|
})
|
|
expect(isExpectedAgentProcess('/home/dev/.kimi-code/bin/kimi', 'kimi')).toBe(true)
|
|
expect(isRecognizedAgentType('kimi-code')).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.each(['earendil-works', 'mariozechner'])('recognizes the @%s Pi npm entrypoint', (scope) => {
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`node.exe C:\Users\dev\AppData\Roaming\npm\node_modules\@${scope}\pi-coding-agent\dist\cli.js`
|
|
)
|
|
).toEqual({ agent: 'pi', processName: 'pi' })
|
|
})
|
|
|
|
it('recognizes Prime Agent by its binary and npm entrypoint', () => {
|
|
expect(recognizeAgentProcess('prime-agent')).toEqual({
|
|
agent: 'prime-agent',
|
|
processName: 'prime-agent'
|
|
})
|
|
expect(recognizeAgentProcess('/opt/homebrew/bin/prime-agent')).toEqual({
|
|
agent: 'prime-agent',
|
|
processName: 'prime-agent'
|
|
})
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
'node /opt/homebrew/lib/node_modules/prime-agent/dist/bundle/cli.js'
|
|
)
|
|
).toEqual({ agent: 'prime-agent', processName: 'prime-agent' })
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`node.exe C:\Users\dev\AppData\Roaming\npm\node_modules\prime-agent\dist\bundle\cli.js`
|
|
)
|
|
).toEqual({ agent: 'prime-agent', processName: 'prime-agent' })
|
|
})
|
|
|
|
it('does not recognize Prime Agent headless one-shot commands as interactive agents', () => {
|
|
expect(recognizeAgentProcessFromCommandLine('prime-agent -p "summarize this diff"')).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('prime-agent --print "review this"')).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('prime-agent --resume abc123')).toEqual({
|
|
agent: 'prime-agent',
|
|
processName: 'prime-agent'
|
|
})
|
|
// Why: past `--` nothing is a flag, so this is the interactive pane Orca itself launches.
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('prime-agent -- "--print the release notes"')
|
|
).toEqual({ agent: 'prime-agent', processName: 'prime-agent' })
|
|
})
|
|
|
|
it('does not recognize Prime Agent non-interactive --mode runs as interactive agents', () => {
|
|
for (const mode of ['json', 'rpc', 'acp', 'daemon']) {
|
|
expect(recognizeAgentProcessFromCommandLine(`prime-agent --mode ${mode}`)).toBeNull()
|
|
}
|
|
// Why: `text` is the interactive TUI mode Orca hosts.
|
|
expect(recognizeAgentProcessFromCommandLine('prime-agent --mode text')).toEqual({
|
|
agent: 'prime-agent',
|
|
processName: 'prime-agent'
|
|
})
|
|
// Why: the CLI only parses `--mode <value>` as separate tokens, so `--mode=json`
|
|
// is ignored by it and still starts the interactive mode.
|
|
expect(recognizeAgentProcessFromCommandLine('prime-agent --mode=json')).toEqual({
|
|
agent: 'prime-agent',
|
|
processName: 'prime-agent'
|
|
})
|
|
expect(recognizeAgentProcessFromCommandLine('prime-agent -- --mode rpc')).toEqual({
|
|
agent: 'prime-agent',
|
|
processName: 'prime-agent'
|
|
})
|
|
})
|
|
|
|
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('recognizes the versioned Cursor Node wrapper without accepting generic agent processes', () => {
|
|
const cursorEntrypoint = String.raw`C:\Users\dev\AppData\Local\cursor-agent\versions\2026.07.09-a3815c0\index.js`
|
|
|
|
expect(recognizeAgentProcessFromCommandLine(`node.exe ${cursorEntrypoint}`)).toEqual({
|
|
agent: 'cursor',
|
|
processName: 'cursor-agent'
|
|
})
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(`node.exe ${cursorEntrypoint} worker-server`)
|
|
).toEqual({ agent: 'cursor', processName: 'cursor-agent' })
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(String.raw`node.exe C:\repo\cursor-agent\index.js`)
|
|
).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(String.raw`C:\Users\dev\.grok\bin\agent.exe`)
|
|
).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`node C:\repo\node_modules\@example\pi-coding-agent\dist\cli.js`
|
|
)
|
|
).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 the Antigravity CLI from bare, POSIX and Windows command lines', () => {
|
|
const agy = { agent: 'antigravity', processName: 'agy' }
|
|
|
|
expect(recognizeAgentProcess('agy')).toEqual(agy)
|
|
expect(recognizeAgentProcess('/Users/dev/.local/bin/agy')).toEqual(agy)
|
|
expect(recognizeAgentProcess(String.raw`C:\Users\dev\AppData\Local\agy\bin\agy.exe`)).toEqual(
|
|
agy
|
|
)
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
String.raw`"C:\Users\dev\AppData\Local\agy\bin\agy.exe" --dangerously-skip-permissions`
|
|
)
|
|
).toEqual(agy)
|
|
expect(recognizeAgentProcessFromCommandLine('agy --dangerously-skip-permissions')).toEqual(agy)
|
|
})
|
|
|
|
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'
|
|
})
|
|
})
|
|
|
|
it('recognizes the versioned Muse binary execed by the launcher', () => {
|
|
expect(recognizeAgentProcess('muse-bin-1.3.0-r3401.1')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse-bin-1.3.0-r3401.1'
|
|
})
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine('/Users/dev/.local/bin/muse-bin-1.3.0-R3401.1')
|
|
).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse-bin-1.3.0-r3401.1'
|
|
})
|
|
// Why: Linux truncates comm to 15 chars, so `muse-bin-1.0.3-R…` rows still match.
|
|
expect(recognizeAgentProcess('muse-bin-1.0.3-R')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse-bin-1.0.3-r'
|
|
})
|
|
expect(recognizeAgentProcess('muse-workbench')).toBeNull()
|
|
expect(isRecognizedAgentType('muse-bin-1.3.0-R3401.1')).toBe(true)
|
|
expect(isExpectedAgentProcess('muse', 'muse')).toBe(true)
|
|
expect(isExpectedAgentProcess('muse-bin-1.3.0-R3401.1', 'muse')).toBe(true)
|
|
expect(isExpectedAgentProcess('/Users/dev/.local/bin/muse-bin-1.3.0-R3401.1', 'muse')).toBe(
|
|
true
|
|
)
|
|
expect(isExpectedAgentProcess('not-muse', 'muse')).toBe(false)
|
|
expect(isExpectedAgentProcess('muse-workbench', 'muse')).toBe(false)
|
|
})
|
|
|
|
it('does not recognize Muse headless exec runs as interactive agents', () => {
|
|
expect(recognizeAgentProcessFromCommandLine('muse exec "summarize this diff"')).toBeNull()
|
|
expect(
|
|
recognizeAgentProcessFromCommandLine(
|
|
'/Users/dev/.local/bin/muse-bin-1.3.0-R3401.1 exec --session-id abc "hi"'
|
|
)
|
|
).toBeNull()
|
|
// Why: `exec` past any position is never the TUI — `muse exec` dispatches headless
|
|
// while `muse <flags> exec` fails fast with an arg error (verified on Muse 1.3.0).
|
|
expect(recognizeAgentProcessFromCommandLine('muse -- exec "summarize this diff"')).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('muse --yolo exec "hi"')).toBeNull()
|
|
expect(recognizeAgentProcessFromCommandLine('muse -- yolo')).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse'
|
|
})
|
|
// Why: subcommand dispatch is case-sensitive, so an uppercase prompt is the TUI.
|
|
expect(recognizeAgentProcessFromCommandLine("muse 'EXEC'")).toEqual({
|
|
agent: 'muse',
|
|
processName: 'muse'
|
|
})
|
|
})
|
|
})
|