Files
orca/src/shared/agent-session-visible-failures.ts
T
Brennan Benson c0e2b73395 feat(native-chat): require Codex 0.136.0 for chats (#26684)
* Require Codex 0.136.0 for native chats

* Add host-owned Codex install and update actions

* Bind Codex checks and maintenance to the selected installation

* Resolve Windows updater cleanup through system helpers

* Use one child environment for Codex checks and launch

* Expire Codex readiness and reconcile current host jobs

* Resolve Codex checks against the child command search path

* Restrict global npm repair to the selected Codex install layout

* Pin the host updater directory through Windows supervision

* Synchronize Codex maintenance fixtures on readiness

* Unify Codex notices and recover version-refused chats

* Route Codex recovery to the operation that remains

* Preserve Codex setup execution errors in the job log

* Preserve UTF-8 in Codex setup logs

* Remove unused Codex host installation detector

* Require host administration for Codex maintenance

* Show a missing Codex explanation once

* test(native-chat): give scripted Codex runtimes their installation fact; keep the registry out of the relay

Runtime tests that drive a scripted Codex app-server still ran the new
version check against whatever codex was on the machine's PATH: refused
on CI (no codex), and on a dev machine it ran the real codex --version.
A scripted transport now must state the Codex it stands for
(readCodexInstallation), asserted at install, and the tests get it from
one scriptedCodexTransport fixture.

The relay's Codex maintenance path reached @orca/windows-registry through
resolvePathEnvKey's re-export in windows-environment-path; import it from
its registry-free module instead.

Also update the catalog-probe test for the shared child environment.

* refactor(native-chat): drop the Update Codex action; keep Install for a missing Codex

A too-old Codex now gets only the plain message ("Codex X is too old for
chats. Update to 0.136.0 or newer.") with no button and no job. The
existing rechecks (window focus, the <=30 s expiry of a saved result)
retry the refused chat once the user has updated Codex themselves.

A missing Codex keeps the Install Codex button: `npm install -g
@openai/codex` with the npm on the chat's PATH, as a host-run job with
its log, then a recheck and recovery. A configured Command that is
missing gets the message only, since a global npm install would not
land at that path.

Removed: the `codex update` command and its 0.126 floor, manual
instructions, npm layout and launcher identity detection, the action
descriptor (install|update) and its titles, the legacy capability, and
the SSH relay maintenance path (structured chat never runs over SSH or
WSL), so the relay preflight handler is back to main's.

* refactor(native-chat): keep Install Codex in Settings only

When Codex isn't installed, main already hides Codex from the New tab menu,
so the in-chat Install button was almost never reachable. Settings → Agents
already shows "Codex isn't installed." with Install Codex.

- Chats and the new-workspace composer now only state "Codex isn't
  installed." (no button, no install job from the chat).
- The Install action, its busy spinner and the host-busy subscription move
  into the Settings Codex row, the only place that starts the job.
- The store no longer re-checks every other workspace context when a job
  finishes. A refused chat recovers through the same re-check a too-old
  Codex uses (window focus, or the <=30 s expiry of a saved result).
- The chat recovery key no longer includes the install job id.
- Tests: Settings drives the install; a new test proves a chat refused for
  a missing Codex recovers through focus after a Settings install, not
  through the job; the expiry recovery test covers missing and too old.

* refactor(codex): check chat compatibility when acquiring the child

* Restore workspace submit guard and simplify Codex maintenance

* Keep only the Codex chat minimum version
2026-10-12 14:30:27 -07:00

62 lines
2.3 KiB
TypeScript

import {
readWholeAgentSessionFailureFact,
type AgentSessionFailureFact
} from './agent-session-failure'
import type { AgentJournalRenderItem } from './agent-session-journal-types'
import { isStructuredAgentSessionStartFailureRow } from './structured-agent-session-start-failure-row-key'
/** Loaded startup causes and authentication explanations already visible in the transcript. */
export function agentSessionVisibleFailureFacts(
items: readonly AgentJournalRenderItem[]
): AgentSessionFailureFact[] {
const facts: AgentSessionFailureFact[] = []
for (const item of items) {
if (item.body.kind === 'status') {
const fact = readWholeAgentSessionFailureFact(item.body.failure)
if (
fact &&
(isStructuredAgentSessionStartFailureRow(item.itemId) || fact.kind === 'notSignedIn')
) {
facts.push(fact)
}
}
}
return facts
}
/** Whether two facts are one failure: a start's row and the messages it rejected share one. */
export function sameAgentSessionFailureFact(
a: AgentSessionFailureFact,
b: AgentSessionFailureFact
): boolean {
return (
a.kind === b.kind &&
a.account === b.account &&
a.detail?.text === b.detail?.text &&
a.detail?.audience === b.detail?.audience &&
a.refusal?.code === b.refusal?.code &&
a.refusal?.details?.reason === b.refusal?.details?.reason &&
(a.refusal?.code !== 'agent_session_operation_invalid' ||
b.refusal?.code !== 'agent_session_operation_invalid' ||
(a.refusal.details?.codexInstallation?.installedVersion ===
b.refusal.details?.codexInstallation?.installedVersion &&
a.refusal.details?.codexInstallation?.minimumVersion ===
b.refusal.details?.codexInstallation?.minimumVersion)) &&
a.attachment?.reason === b.attachment?.reason &&
a.attachment?.limit === b.attachment?.limit &&
a.retry?.error === b.retry?.error &&
a.retry?.status === b.retry?.status
)
}
/** Whether a loaded row already states this failure, comparing facts rather than wording. */
export function agentSessionFailureStatedByRow(
failure: unknown,
statedFailures: readonly AgentSessionFailureFact[]
): boolean {
const fact = readWholeAgentSessionFailureFact(failure)
return (
fact !== undefined && statedFailures.some((stated) => sameAgentSessionFailureFact(stated, fact))
)
}