mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 02:27:43 +00:00
* feat(acp): open Antigravity in the Chat UI over its ACP server With the experimental chat setting on, Antigravity chats run Google's Antigravity ACP server, which Orca installs only when the person clicks Install: a pinned 1.3.0 release whose archive and both executables are verified by hash and size (macOS arm64/x64, Linux x64/arm64). A start never downloads it. Each host keeps one private sign-in folder for these chats under Orca's data folder, made owner-only before every start; the server keeps its sign-in there as a file. A chat signs in with the server's Google method through the existing auth-required retry; with no saved sign-in the server's sign-in prompt ends the chat signed out. The Agent Permissions setting maps to the server's own mode, and a question the server asks shows as a question that takes one of its choices, never auto-answered. A chat reopens its session with session/resume. * fix(acp): separate Antigravity chat command and require pinned host * test(acp): assert Antigravity launch arguments across hosts * feat(acp): Antigravity chats offer Ask, Accept edits and Full access per chat Each mode maps to the server's own (default, auto_edit, yolo). No Approve for me: the server has no automatic reviewer. Accept edits gets its own description, since auto_edit approves file edit tools only. * feat(acp): Antigravity installs from Settings and Accounts, and says its download size The Antigravity chat server installs only when the person clicks Install. Besides the chat's own Install card, Settings > Chat gains a Providers section and the Antigravity accounts section gains the same install row for its host (never a WSL distro), both following the host's install status. A host now reports its pinned release's download size, so an idle row reads "111.5 MB download." instead of "Not installed.". * test(acp): the launch parity install puts the Antigravity harness beside its server * fix(acp): Antigravity chats also drop the gcloud project variables * fix(acp): Antigravity command rows, a refused /logout, the risky Allow Always warning, and an install that proves the server runs - A command's line and output now show in Orca's command row: the dialect maps the server's command_line/combinedOutput/exitCode to the shared shape and drops its duplicate output copy. - The server's own /logout is hidden from the command list, and a message that is only /logout is refused before it is sent: it would sign out every Antigravity chat on this host outside Orca's sign-in gate. Dialects can now name withheld commands; the refusal has its own plain sentence. - The server's prompt-injection warning on "Allow Always (risky)" is shown on the approval card. - Install starts the unpacked server once under a throwaway home, asks initialize only, and keeps the copy only if it answers as the pinned release and its process is proven stopped. - The server runs with PYTHONUNBUFFERED=1; its environment lives beside its runtime. - The permission mode option is found by its category, so the spec no longer names it. * fix(antigravity): the install check accepts only the protocol version Orca's connection speaks * fix(acp): keep the structured session create support within its line limit Main's file already sat at the limit, so the account-home services pass inline instead of through a variable. * test(acp): Antigravity's launch snapshots follow main's per-chat permission launches Main now starts ACP chats without full access and applies the chat's own mode after start, as the Grok and OpenCode snapshots already record.
207 lines
8.8 KiB
TypeScript
207 lines
8.8 KiB
TypeScript
// Why a submission is `rejected`: the message provably did not happen.
|
|
//
|
|
// The host writes each rejection twice: `reason`, a sentence (or, for the cases older clients
|
|
// already recognise, one of the legacy markers below) that released clients print as it is, and
|
|
// `rejection`, the typed fact newer clients read — both from `agentSessionFailureWords`.
|
|
// `classifyDispatchRejection` is the only place a reader judges either.
|
|
//
|
|
// Every rejection makes the one claim that state exists to make: this message did not reach the
|
|
// provider. None is ever re-delivered under its own id — `rejected` is terminal in the reducer — so
|
|
// a retry rotates the client message id, which is a new message and cannot duplicate.
|
|
|
|
import {
|
|
isSubmissionRejectionKind,
|
|
readAgentSessionFailureFact,
|
|
type SubmissionRejectionKind
|
|
} from './agent-session-failure'
|
|
import type { AgentJournalSubmission } from './agent-session-journal-types'
|
|
|
|
/** Orca could not hand the message over. Written bare: released clients hide it, and the error
|
|
* that once followed it belongs in the log. Rows from older hosts carry `: <error>` after it. */
|
|
export const DISPATCH_REJECTED_WRITE_FAILED = 'provider_write_failed'
|
|
|
|
/** Local admission refused the frame before any transport was involved. Two
|
|
* strings rather than one provider-neutral marker because both are already
|
|
* durable journal reasons; rewording either would relabel rows on disk. */
|
|
export const DISPATCH_REJECTED_QUEUE_FULL = 'claude structured dispatch queue is full'
|
|
export const DISPATCH_REJECTED_CODEX_QUEUE_FULL = 'codex structured dispatch queue is full'
|
|
|
|
/** The provider confirmed a queued frame was withdrawn before execution. */
|
|
export const DISPATCH_REJECTED_CANCELLED = 'provider_cancelled_before_start'
|
|
|
|
/** Legacy marker: accepted by a host process that ended before handing it to any provider.
|
|
* Read only; released clients print it raw, so new rows carry a sentence. */
|
|
export const DISPATCH_REJECTED_HOST_RESTARTED = 'host_restarted_before_delivery'
|
|
|
|
/** Legacy marker: accepted, then the provider was closed before the message was handed to it.
|
|
* Read only, like the one above. */
|
|
export const DISPATCH_REJECTED_PROVIDER_CLOSED = 'provider_closed_before_delivery'
|
|
|
|
/** Restart reconciliation found the send absent from a provider history it could trust. Rows
|
|
* written before rows carried a fact hold it as their reason; released clients printed it. */
|
|
export const DISPATCH_REJECTED_NOT_DELIVERED = 'not_delivered'
|
|
|
|
/** True for the internal transport marker, false for a provider's own words. Legacy-reason half
|
|
* of `isWriteFailureSubmission`; readers go through that or the classifier. */
|
|
function dispatchRejectionWasTransportWriteFailure(reason: string | null | undefined): boolean {
|
|
return (
|
|
reason === DISPATCH_REJECTED_WRITE_FAILED ||
|
|
reason?.startsWith(`${DISPATCH_REJECTED_WRITE_FAILED}: `) === true
|
|
)
|
|
}
|
|
|
|
/**
|
|
* What a rejection means for the chat, whatever wrote it:
|
|
* - `withdrawn`: the user's Stop took it back; nothing failed.
|
|
* - `undelivered`: accepted, then never handed over (Orca restarted, the chat closed, the provider
|
|
* never took it or stopped first).
|
|
* - `startFailed`: the agent it waited on did not start.
|
|
* - `content`: the provider, or Orca's check of the message, refused this payload.
|
|
* - `transport`: Orca could not hand it over.
|
|
*/
|
|
export type DispatchRejectionCategory =
|
|
| 'withdrawn'
|
|
| 'undelivered'
|
|
| 'startFailed'
|
|
| 'content'
|
|
| 'transport'
|
|
|
|
export type DispatchRejectionClassification = {
|
|
category: DispatchRejectionCategory
|
|
/** `failure` when the chat reads Failed; null only when no one failed the user: a withdrawal, a
|
|
* host restart, a chat close, or a send the provider never received after a restart. */
|
|
verdict: 'failure' | null
|
|
/** The situation, when the row carried one or a legacy marker names it; absent for a legacy
|
|
* sentence, whose words are all a reader has, and for a fact this build cannot place. */
|
|
kind?: SubmissionRejectionKind
|
|
}
|
|
|
|
const KIND_CATEGORY = {
|
|
cancelled: 'withdrawn',
|
|
hostRestarted: 'undelivered',
|
|
chatClosed: 'undelivered',
|
|
notDelivered: 'undelivered',
|
|
providerExited: 'undelivered',
|
|
hostStopped: 'undelivered',
|
|
providerStartFailed: 'startFailed',
|
|
startFailed: 'startFailed',
|
|
notSignedIn: 'startFailed',
|
|
cliMissing: 'startFailed',
|
|
historyTooLarge: 'startFailed',
|
|
managedAccountEnvOverride: 'startFailed',
|
|
accountSwitchInProgress: 'startFailed',
|
|
managedAccountUnsupported: 'startFailed',
|
|
launchFolderMissing: 'startFailed',
|
|
historyInOtherAccount: 'startFailed',
|
|
claudeAccountFolderMissing: 'startFailed',
|
|
claudeAccountSetupFailed: 'startFailed',
|
|
agentCommandNotRunnable: 'startFailed',
|
|
runtimeNotInstalled: 'startFailed',
|
|
restartFailed: 'startFailed',
|
|
providerRejected: 'content',
|
|
attachmentInvalid: 'content',
|
|
attachmentUnreadable: 'content',
|
|
emptyMessage: 'content',
|
|
commandUnavailable: 'content',
|
|
commandRefused: 'content',
|
|
queueFull: 'transport',
|
|
writeFailed: 'transport',
|
|
hostFault: 'transport'
|
|
} satisfies Record<SubmissionRejectionKind, DispatchRejectionCategory>
|
|
|
|
/** Null only where no one failed the user; a new kind does not compile until it is placed here. */
|
|
const KIND_VERDICT = {
|
|
cancelled: null,
|
|
hostRestarted: null,
|
|
chatClosed: null,
|
|
notDelivered: null,
|
|
providerExited: 'failure',
|
|
providerStartFailed: 'failure',
|
|
startFailed: 'failure',
|
|
notSignedIn: 'failure',
|
|
cliMissing: 'failure',
|
|
historyTooLarge: 'failure',
|
|
managedAccountEnvOverride: 'failure',
|
|
accountSwitchInProgress: 'failure',
|
|
managedAccountUnsupported: 'failure',
|
|
launchFolderMissing: 'failure',
|
|
historyInOtherAccount: 'failure',
|
|
claudeAccountFolderMissing: 'failure',
|
|
claudeAccountSetupFailed: 'failure',
|
|
agentCommandNotRunnable: 'failure',
|
|
runtimeNotInstalled: 'failure',
|
|
restartFailed: 'failure',
|
|
providerRejected: 'failure',
|
|
attachmentInvalid: 'failure',
|
|
attachmentUnreadable: 'failure',
|
|
emptyMessage: 'failure',
|
|
commandUnavailable: 'failure',
|
|
commandRefused: 'failure',
|
|
queueFull: 'failure',
|
|
writeFailed: 'failure',
|
|
hostFault: 'failure',
|
|
// Orca stopped a start that hung: the agent failed.
|
|
hostStopped: 'failure'
|
|
} satisfies Record<SubmissionRejectionKind, 'failure' | null>
|
|
|
|
/** The legacy markers, by the kind each stands for. */
|
|
const LEGACY_MARKER_KINDS: ReadonlyMap<string, SubmissionRejectionKind> = new Map([
|
|
[DISPATCH_REJECTED_CANCELLED, 'cancelled'],
|
|
[DISPATCH_REJECTED_HOST_RESTARTED, 'hostRestarted'],
|
|
[DISPATCH_REJECTED_PROVIDER_CLOSED, 'chatClosed'],
|
|
[DISPATCH_REJECTED_QUEUE_FULL, 'queueFull'],
|
|
[DISPATCH_REJECTED_CODEX_QUEUE_FULL, 'queueFull'],
|
|
[DISPATCH_REJECTED_NOT_DELIVERED, 'notDelivered']
|
|
])
|
|
|
|
/** The kind a legacy marker stands for; undefined for any other reason, which is a sentence. */
|
|
function legacyMarkerKind(reason: string | null): SubmissionRejectionKind | undefined {
|
|
if (dispatchRejectionWasTransportWriteFailure(reason)) {
|
|
return 'writeFailed'
|
|
}
|
|
return reason === null ? undefined : LEGACY_MARKER_KINDS.get(reason)
|
|
}
|
|
|
|
/**
|
|
* The one reader of why a submission was rejected. The typed fact decides when the row carries
|
|
* one; a fact this build cannot place says only that the message was not sent. A row with no
|
|
* fact, from an older host, is read by its legacy marker, and any other reason is a sentence — a
|
|
* provider's, or Orca's before rows were typed — which reads as a content failure.
|
|
*/
|
|
export function classifyDispatchRejection(
|
|
submission: Pick<AgentJournalSubmission, 'reason'> & { rejection?: unknown }
|
|
): DispatchRejectionClassification {
|
|
const { rejection } = submission
|
|
const factKind = readAgentSessionFailureFact(rejection)?.kind
|
|
if (typeof rejection === 'object' && rejection !== null && !isSubmissionRejectionKind(factKind)) {
|
|
// Undelivered, not content: all a fact this build can't place proves is the message didn't happen.
|
|
return { category: 'undelivered', verdict: null }
|
|
}
|
|
const kind = isSubmissionRejectionKind(factKind) ? factKind : legacyMarkerKind(submission.reason)
|
|
if (!kind) {
|
|
return { category: 'content', verdict: 'failure' }
|
|
}
|
|
return { category: KIND_CATEGORY[kind], verdict: KIND_VERDICT[kind], kind }
|
|
}
|
|
|
|
/** A Stop withdrew it before it ran: it will not land, and only a person can send it again. */
|
|
export function dispatchWasWithdrawn(
|
|
submission: Pick<AgentJournalSubmission, 'dispatchState' | 'reason' | 'rejection'> | undefined
|
|
): boolean {
|
|
return (
|
|
submission?.dispatchState === 'rejected' &&
|
|
classifyDispatchRejection(submission).category === 'withdrawn'
|
|
)
|
|
}
|
|
|
|
/** A submission that says Orca never handed it over, in any dispatch state: journals written
|
|
* before this state moved hold it as `unknown` carrying the marker. */
|
|
export function isWriteFailureSubmission(
|
|
submission: Pick<AgentJournalSubmission, 'reason' | 'rejection'>
|
|
): boolean {
|
|
return (
|
|
readAgentSessionFailureFact(submission.rejection)?.kind === 'writeFailed' ||
|
|
dispatchRejectionWasTransportWriteFailure(submission.reason)
|
|
)
|
|
}
|