mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(terminal): frame CLI prompts for every recognized agent, not just Claude and Codex (#33571)
`orca terminal send --text … --enter` now uses the bracketed paste + Enter agent-prompt path for every recognized agent (not just Claude/Codex), so Pi's #/@ completion and Grok's y/n trust question no longer swallow or answer typed prompts. Agents whose process names collide with ordinary tools (orca, acli, cn, vibe) still need title/hook evidence. A send into a permission/trust menu is refused with a hint to send --text and --enter separately. Refs #20695 Refs #18399
This commit is contained in:
@@ -225,6 +225,7 @@ Terminal rules:
|
||||
- Use `terminal read` before `terminal send` unless the next input is obvious.
|
||||
- Use `terminal send` only for direct terminal input or one-off prompts where no task state, inbox, or reply tracking is needed.
|
||||
- `accepted: true` proves input acceptance, not a started turn. Use the receipt's `turn_started` stage when submission proof is needed; never resend on silence.
|
||||
- A text-plus-Enter agent prompt is refused with `agent_prompt_blocked` while the agent shows a permission or trust menu. To answer the menu, send `--text` and `--enter` as separate commands.
|
||||
- A text-plus-Enter agent prompt returns a durable request ID and additive stages: `input_accepted`, then `turn_started` once the agent's turn is proven. Raw text-only, bare Enter, interrupt, and terminal query replies keep their existing direct-input behavior.
|
||||
- A default send observes for 0 seconds, so a receipt that stops at `input_accepted` is expected and its warning means "unproven", not "failed". Pass `--wait-submit` when you need proof of submission.
|
||||
- `--wait-submit <seconds>` only observes the same accepted prompt. A timeout returns queued/input-accepted truth without resending; after an ambiguous transport failure, repeat the exact command with the reported `--retry-request <id>`. Both text and `--json` receipts carry the same `warnings`.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -12,7 +12,12 @@ export function attachDurableMutationRecovery(
|
||||
if (method !== 'terminal.send' || !requestId || !(error instanceof RuntimeClientError)) {
|
||||
return attachMutationRecovery(error, requestId, originalCommand)
|
||||
}
|
||||
const message = `${error.message} Terminal prompt request ID: ${requestId}. Re-issue the exact command with --retry-request ${requestId} --wait-submit <seconds>; do not retry it without that ID.`
|
||||
// Why: a text-plus-Enter prompt refuses a menu; text-only and bare-Enter sends stay direct input.
|
||||
const menuHint =
|
||||
error.code === 'agent_prompt_blocked'
|
||||
? ' The agent is showing a permission or trust prompt; to answer it, send --text and --enter as separate commands.'
|
||||
: ''
|
||||
const message = `${error.message}${menuHint} Terminal prompt request ID: ${requestId}. Re-issue the exact command with --retry-request ${requestId} --wait-submit <seconds>; do not retry it without that ID.`
|
||||
const data = {
|
||||
...(error.data && typeof error.data === 'object' ? error.data : {}),
|
||||
orchestrationRequestId: requestId,
|
||||
|
||||
+17
-13
@@ -304,7 +304,7 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(getForegroundProcess).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it.each(['claude', 'codex'] as const)(
|
||||
it.each(['claude', 'codex', 'pi', 'grok', 'gemini'] as const)(
|
||||
'authorizes settled CLI prompts only after positive %s foreground identity',
|
||||
async (agent) => {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
@@ -320,19 +320,23 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('keeps a recognized non-target agent on legacy CLI prompt delivery', async () => {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess: async () => 'gemini'
|
||||
})
|
||||
syncSinglePty(runtime, 'pty-1', { paneTitle: 'bash' })
|
||||
const [terminal] = (await runtime.listTerminals()).terminals
|
||||
it.each(['orca', 'acli', 'cn', 'vibe'])(
|
||||
'keeps direct input for a bare %s foreground that may be an ordinary tool',
|
||||
async (processName) => {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess: async () => processName
|
||||
})
|
||||
syncSinglePty(runtime, 'pty-1', { paneTitle: 'bash' })
|
||||
const [terminal] = (await runtime.listTerminals()).terminals
|
||||
|
||||
await expect(runtime.isTerminalRunningAgent(terminal.handle)).resolves.toBe(true)
|
||||
await expect(runtime.isTerminalRunningSettledPromptAgent(terminal.handle)).resolves.toBe(false)
|
||||
})
|
||||
await expect(runtime.isTerminalRunningSettledPromptAgent(terminal.handle)).resolves.toBe(
|
||||
false
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it('keeps stale Codex launch identity on legacy delivery after the shell returns', async () => {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
detectAgentStatusFromTitle,
|
||||
isClaudeManagementTitle
|
||||
} from '../../../shared/agent-detection'
|
||||
import { recognizeAgentProcess } from '../../../shared/agent-process-recognition'
|
||||
import {
|
||||
GENERIC_PROCESS_NAME_AGENTS,
|
||||
recognizeAgentProcess
|
||||
} from '../../../shared/agent-process-recognition'
|
||||
import {
|
||||
resolveStructuredWorkerAuthority,
|
||||
resolveStructuredWorkerIdentityForSession
|
||||
@@ -135,11 +138,11 @@ export abstract class OrcaRuntimeWithGetPtyRecordForPaneKey extends OrcaRuntimeW
|
||||
foregroundProcess = await this.ptyController.confirmForegroundProcess(ptyId)
|
||||
agent = recognizeAgentProcess(foregroundProcess)?.agent
|
||||
}
|
||||
if (agent !== 'claude' && agent !== 'codex') {
|
||||
return false
|
||||
}
|
||||
const presence = { retryForegroundWrappers: false, foregroundProcess }
|
||||
if (!(await this.isTerminalRunningAgent(handle, presence))) {
|
||||
// Why every agent: unframed keystrokes can open Pi's `#` completion or answer a trust menu; Dispatch already frames them all.
|
||||
// A generic process name alone could be an ordinary tool, so those agents need title or hook evidence.
|
||||
const proof = agent && GENERIC_PROCESS_NAME_AGENTS.has(agent) ? null : foregroundProcess
|
||||
const presence = { retryForegroundWrappers: false, foregroundProcess: proof }
|
||||
if (agent === undefined || !(await this.isTerminalRunningAgent(handle, presence))) {
|
||||
return false
|
||||
}
|
||||
trackedPty.foregroundAgent = agent
|
||||
|
||||
@@ -169,6 +169,14 @@ export function isExpectedAgentProcess(
|
||||
)
|
||||
}
|
||||
|
||||
/** Agents whose process names (orca, acli, cn, vibe) also belong to ordinary tools. */
|
||||
export const GENERIC_PROCESS_NAME_AGENTS: ReadonlySet<TerminalAgent> = new Set([
|
||||
'claude-agent-teams',
|
||||
'rovo',
|
||||
'continue',
|
||||
'mistral-vibe'
|
||||
])
|
||||
|
||||
export function recognizeAgentProcess(
|
||||
processName: string | null | undefined
|
||||
): RecognizedAgentProcess | null {
|
||||
|
||||
Reference in New Issue
Block a user