mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 08:02:43 +00:00
fix(runtime): suppress on the composer REGION, and separate code from questions
Re-verification found the previous fix incomplete: 18 refusals survived, one root cause. The composer suppression fired only when the caret was the literal bottom row, and no agent draws it there -- Codex puts a model footer under it, OpenCode a status bar, droid a key-hint row. So the ternary false positive survived for Codex and OpenCode, both gating agents, and cursor-agent and antigravity passed only because four chrome rows pushed the trigger outside the window. Position alone cannot separate these: `? How would you like to authenticate?` above its option rows and `? prevAssignees.filter(...)` above a caret and a status bar sit at the same offset from the bottom. So two rules: - suppression scans the last three non-blank rows for a caret, not the bottom row; - a `?` row that is code-shaped (quotes, braces, brackets, `=>`, `!==`, a `foo.bar` access) is not a question. Parentheses stay legal, since real prompts write `(Use arrow keys)`. Also: the noun lookbehind now spans an identifier to its opening dot, because `candidate.personal_access_token` still corroborated when only one character was checked -- the dot precedes `personal`, not `access_token`. Mined-corpus refusals: 16,297 lines x 13 real agent tails = 0. Controls hold.
This commit is contained in:
@@ -614,6 +614,37 @@ const LEGITIMATE_AGENT_SCREENS: readonly (readonly [string, string[]])[] = [
|
||||
'source rows where a .credential access is the only would-be credential noun',
|
||||
[' const owner = candidate.credential', ' Enter the code below to finish linking']
|
||||
],
|
||||
[
|
||||
'bare identifier ternary above a codex caret and its model footer',
|
||||
[' ? login', '', '\u203a Ask Codex to do anything', '', ' gpt-6 medium \u00b7 ~/repo']
|
||||
],
|
||||
[
|
||||
'bare identifier ternary above an opencode caret and status bar',
|
||||
[' ? authorization', '', '\u276f ', 'opencode anthropic/claude-opus-4 ~/repo']
|
||||
],
|
||||
[
|
||||
'ternary above a codex caret and its model footer',
|
||||
[
|
||||
" ? 'Update desktop Orca and sign in to connect from anywhere'",
|
||||
'',
|
||||
'\u203a Ask Codex to do anything',
|
||||
'',
|
||||
' gpt-6 medium \u00b7 ~/repo'
|
||||
]
|
||||
],
|
||||
[
|
||||
'login access above an opencode caret and status bar',
|
||||
[
|
||||
' ? prevAssignees.filter((user) => user.login.toLowerCase() !== lowerLogin)',
|
||||
'',
|
||||
'\u276f ',
|
||||
'opencode anthropic/claude-opus-4 ~/repo'
|
||||
]
|
||||
],
|
||||
[
|
||||
'personal_access_token identifier cannot corroborate',
|
||||
[' const owner = candidate.personal_access_token', ' Enter the code below to finish']
|
||||
],
|
||||
[
|
||||
'source rows where a .login access is the only would-be auth verb',
|
||||
[
|
||||
|
||||
@@ -47,8 +47,19 @@ export const CREDENTIAL_NOUN_SOURCE =
|
||||
// accesses. The noun is what corroborates an otherwise-inert bottom row, so an identifier reading
|
||||
// as the noun is enough on its own to refuse a screen that is only printing source. Only `.` is
|
||||
// excluded, not all of `\w` — the env-var form (`OPENAI_API_KEY`) is a real ask.
|
||||
const CREDENTIAL_NOUN_RE = new RegExp(`(?<!\\.)(?:${CREDENTIAL_NOUN_SOURCE})`, 'gi')
|
||||
const CREDENTIAL_NOUN_ANYWHERE_RE = new RegExp(`(?<!\\.)(?:${CREDENTIAL_NOUN_SOURCE})`, 'i')
|
||||
// Why the lookbehind spans identifier characters: the noun can sit mid-identifier, so a fixed
|
||||
// one-character check misses `candidate.personal_access_token` — the dot precedes `personal`, not
|
||||
// `access_token`. Anchoring on the dot at the START of the identifier catches the whole family.
|
||||
// Still only `.`: the env-var ask (`OPENAI_API_KEY`) has no dot and is a real prompt.
|
||||
const CREDENTIAL_NOUN_LOOKBEHIND = '(?<!\\.[a-z0-9_$]*)'
|
||||
const CREDENTIAL_NOUN_RE = new RegExp(
|
||||
`${CREDENTIAL_NOUN_LOOKBEHIND}(?:${CREDENTIAL_NOUN_SOURCE})`,
|
||||
'gi'
|
||||
)
|
||||
const CREDENTIAL_NOUN_ANYWHERE_RE = new RegExp(
|
||||
`${CREDENTIAL_NOUN_LOOKBEHIND}(?:${CREDENTIAL_NOUN_SOURCE})`,
|
||||
'i'
|
||||
)
|
||||
|
||||
// Why a vendor slot: real prompts read "enter your Anthropic API key" and
|
||||
// "paste your personal access token", not just "enter your API key".
|
||||
@@ -123,9 +134,19 @@ const AUTH_ACTION_FLOW_LEADS_ROW_RE = new RegExp(
|
||||
|
||||
// An inquirer-style question row. Prose never starts with a bare `?` — but FORMATTED CODE does:
|
||||
// oxfmt puts a ternary's consequent on its own row as `? someValue`, and 5,666 tracked files in
|
||||
// this repo have one. So the row rule alone is not enough; see `COMPOSER_CARET_ROW_RE`.
|
||||
// this repo have one.
|
||||
const AUTH_QUESTION_ROW_RE = /^\?\s+\S/
|
||||
|
||||
/**
|
||||
* Source code rather than a sentence. This is what actually separates an inquirer header from a
|
||||
* ternary consequent, because POSITION cannot: `? How would you like to authenticate?` above its
|
||||
* two option rows and `? prevAssignees.filter(...)` above a caret and a status bar sit at exactly
|
||||
* the same offset from the bottom.
|
||||
*
|
||||
* Parentheses are deliberately allowed — real prompts write `(Use arrow keys)`.
|
||||
*/
|
||||
const CODE_SHAPED_ROW_RE = /[{}[\]`'"]|=>|!==|===|;\s*$|\b[a-z_$][\w$]*\.[a-z_$]/i
|
||||
|
||||
/**
|
||||
* A row that proves the agent is sitting at its own composer, so nothing is asking the user
|
||||
* anything and a `?` row above it is output, not a dialog header.
|
||||
@@ -135,7 +156,13 @@ const AUTH_QUESTION_ROW_RE = /^\?\s+\S/
|
||||
* #19749 shape, whose own bottom row is `>` — matches through `isCredentialPromptLine` instead,
|
||||
* and that returns before this is consulted.
|
||||
*/
|
||||
const COMPOSER_CARET_ROW_RE = /^(?:›\s*ask\b.*|✳\s*claude code\b.*|[>❯›◇»$])$/i
|
||||
const COMPOSER_CARET_ROW_RE = /^(?:[›❯>◇»]\s*ask\b.*|✳\s*claude code\b.*|[>❯›◇»$])$/i
|
||||
|
||||
// Why a region and not the bottom row: every agent draws chrome UNDER its caret — Codex a model
|
||||
// footer, OpenCode a status bar, droid a key-hint row — so the caret is rarely the last row on a
|
||||
// real screen. Three covers the deepest captured footer without reaching the option rows of a
|
||||
// dialog, whose own caret sits further up (`terminal-live-credential-surfaces-corpus.ts`).
|
||||
const COMPOSER_REGION_ROWS = 3
|
||||
|
||||
// A finished sentence, i.e. narration. A lone `.`/`!`/`?` ends a clause; `...`
|
||||
// and `…` are progress wording ("opening browser...") and are not sentences.
|
||||
@@ -226,7 +253,9 @@ export function findCredentialPromptIndex(normalized: string): number | null {
|
||||
sawCredentialNoun = sawCredentialNoun || CREDENTIAL_NOUN_ANYWHERE_RE.test(line)
|
||||
sawAuthQuestion =
|
||||
sawAuthQuestion ||
|
||||
(AUTH_QUESTION_ROW_RE.test(line) && (AUTH_VERB_RE.test(line) || AUTH_FLOW_RE.test(line)))
|
||||
(AUTH_QUESTION_ROW_RE.test(line) &&
|
||||
!CODE_SHAPED_ROW_RE.test(line) &&
|
||||
(AUTH_VERB_RE.test(line) || AUTH_FLOW_RE.test(line)))
|
||||
}
|
||||
offset += raws[index].length + 1
|
||||
}
|
||||
@@ -245,6 +274,9 @@ export function findCredentialPromptIndex(normalized: string): number | null {
|
||||
bottomRowAsks &&
|
||||
!NARRATION_ROW_RE.test(bottomRow) &&
|
||||
(sawAuthVerb || sawCredentialNoun)
|
||||
const bottomRowIsComposerCaret = COMPOSER_CARET_ROW_RE.test(bottomRow)
|
||||
const nonBlank = lines.filter((line) => line.length > 0)
|
||||
const bottomRowIsComposerCaret = nonBlank
|
||||
.slice(-COMPOSER_REGION_ROWS)
|
||||
.some((line) => COMPOSER_CARET_ROW_RE.test(line))
|
||||
return authFlowOwnsBottom || (sawAuthQuestion && !bottomRowIsComposerCaret) ? windowStart : null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user