From c2c4185e175daea86f8fd6336fd8839a81cc616e Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 6 Sep 2026 14:59:11 -0700 Subject: [PATCH] fix(web): onboarding installs agents without needing Node or npm (#10402) Co-authored-by: Claude Fable 5.1 --- .../src/provider/providerMaintenance.test.ts | 37 +++++++++++++++++++ .../components/onboarding/WelcomeWizard.tsx | 20 +++++----- .../providerReadiness.logic.test.ts | 21 +++++++++++ .../src/onboarding/providerReadiness.logic.ts | 30 +++++++++++++++ docs/user/welcome-wizard.md | 4 +- 5 files changed, 101 insertions(+), 11 deletions(-) diff --git a/apps/server/src/provider/providerMaintenance.test.ts b/apps/server/src/provider/providerMaintenance.test.ts index 2ceaf21996bfc08c637ca5c1b8c6fe2641443348..3e0810f51b7a991928e58dd5153096b25ebc8198 100644 --- a/apps/server/src/provider/providerMaintenance.test.ts +++ b/apps/server/src/provider/providerMaintenance.test.ts @@ -25,6 +25,7 @@ import { parseHomebrewLatestVersion, ProviderVersionCache, resolveLatestProviderVersion, + resolvePackageManagedProviderMaintenance, resolveProviderMaintenanceCapabilitiesEffect, type ProviderMaintenanceCapabilities, } from "./providerMaintenance.ts"; @@ -310,6 +311,42 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { ).toBeNull(); }); + // The Codex Windows installer exposes `%LOCALAPPDATA%\\Programs\\OpenAI\\Codex\\bin` + // as a junction into `%CODEX_HOME%\\packages\\standalone\\current\\bin`. Node's + // realpath follows junctions, so the real path carries the standalone marker + // even though the visible path does not. + it.effect("recognizes a Windows standalone install through its junctioned bin dir", () => + Effect.gen(function* () { + const visiblePath = + "C:\\Users\\Theo\\AppData\\Local\\Programs\\OpenAI\\Codex\\bin\\codex.exe"; + const realPath = + "C:\\Users\\Theo\\.codex\\packages\\standalone\\releases\\0.120.0-x86_64\\bin\\codex.exe"; + const capabilities = yield* resolvePackageManagedProviderMaintenance( + { + provider: driver("codex"), + npmPackageName: "@openai/codex", + nativeUpdate: { + args: ["update"], + isCommandPath: isNativeTestCommandPath("/packages/standalone/"), + }, + }, + { + binaryPath: "codex", + resolvedCommandPath: visiblePath, + realCommandPath: realPath, + env: {}, + platform: "win32", + }, + ).pipe(Effect.provideService(HostProcessPlatform, "win32")); + + expect(capabilities.update).toMatchObject({ + executable: visiblePath, + args: ["update"], + lockKey: "codex-native", + }); + }), + ); + it.effect("proves Windows npm ownership from the package manifest beside the shim", () => Effect.gen(function* () { const tempDir = yield* makeTempDir("t3-npm-windows-capabilities"); diff --git a/apps/web/src/components/onboarding/WelcomeWizard.tsx b/apps/web/src/components/onboarding/WelcomeWizard.tsx index 62665aa86ebf0df666dd7fc66ad7a900d42fa07e..9de7d00c60ca3bab01463f8015382418879a5211 100644 --- a/apps/web/src/components/onboarding/WelcomeWizard.tsx +++ b/apps/web/src/components/onboarding/WelcomeWizard.tsx @@ -42,6 +42,7 @@ import { } from "../../onboarding/projectImport.logic"; import { getOnboardingProviderState, + resolveOnboardingProviderInstallCommand, resolveOnboardingProviderLoginCommand, selectOnboardingProvidersByDriver, } from "../../onboarding/providerReadiness.logic"; @@ -641,11 +642,6 @@ function PairDirectStep({ const PRIMARY_AGENT_DRIVERS = ["claudeAgent", "codex"] as const; type OnboardingAgentDriver = (typeof PRIMARY_AGENT_DRIVERS)[number]; -const AGENT_INSTALL_COMMANDS: Record = { - claudeAgent: "npm install -g @anthropic-ai/claude-code", - codex: "npm install -g @openai/codex", -}; - /** Setup values stay fixed while provider probes refresh the surrounding cards. */ interface AgentTerminalSession { readonly environmentId: EnvironmentId; @@ -657,10 +653,11 @@ interface AgentTerminalSession { } /** - * Claude Code and Codex use live probe status. Install opens the built-in terminal inline - * with the command pre-typed — the update RPC can't install a binary that - * isn't there yet (it infers the package manager from the installed binary's - * path), and the terminal also handles the interactive login that follows. + * Claude Code and Codex use live probe status. Install opens the built-in + * terminal inline with the vendor's standalone installer pre-typed. The update + * RPC can't install a binary that isn't there yet (it infers the installer from + * the installed binary's path), and the terminal also handles the interactive + * login that follows. */ function AgentsStep({ mode, @@ -761,7 +758,10 @@ function ConnectedAgentsStep({ serverConfig.settings, serverConfig.environment.platform.os, ) - : AGENT_INSTALL_COMMANDS[driver], + : resolveOnboardingProviderInstallCommand( + driver, + serverConfig.environment.platform.os, + ), keybindings: serverConfig.keybindings, }); }} diff --git a/apps/web/src/onboarding/providerReadiness.logic.test.ts b/apps/web/src/onboarding/providerReadiness.logic.test.ts index ab742ac51b442bf468448039608e2c7a86b69a21..b0b3a4d57515be1ad3e9b22d44d68afe68c4414b 100644 --- a/apps/web/src/onboarding/providerReadiness.logic.test.ts +++ b/apps/web/src/onboarding/providerReadiness.logic.test.ts @@ -8,6 +8,7 @@ import { describe, expect, it } from "vite-plus/test"; import { getOnboardingProviderState, + resolveOnboardingProviderInstallCommand, resolveOnboardingProviderLoginCommand, selectOnboardingProvidersByDriver, } from "./providerReadiness.logic"; @@ -315,3 +316,23 @@ describe("resolveOnboardingProviderLoginCommand", () => { ).toBe("codex login"); }); }); + +describe("resolveOnboardingProviderInstallCommand", () => { + it("uses the PowerShell installer on Windows environments", () => { + expect(resolveOnboardingProviderInstallCommand("codex", "windows")).toBe( + "irm https://chatgpt.com/codex/install.ps1 | iex", + ); + expect(resolveOnboardingProviderInstallCommand("claudeAgent", "windows")).toBe( + "irm https://claude.ai/install.ps1 | iex", + ); + }); + + it.each(["darwin", "linux", "unknown"] as const)("uses the shell installer on %s", (platform) => { + expect(resolveOnboardingProviderInstallCommand("codex", platform)).toBe( + "curl -fsSL https://chatgpt.com/codex/install.sh | sh", + ); + expect(resolveOnboardingProviderInstallCommand("claudeAgent", platform)).toBe( + "curl -fsSL https://claude.ai/install.sh | bash", + ); + }); +}); diff --git a/apps/web/src/onboarding/providerReadiness.logic.ts b/apps/web/src/onboarding/providerReadiness.logic.ts index 939b4c64cd6480d168e24442ba1206ad4f0ba043..c9d0f910ef53c97d1ec85d8a686d5dff8970bb0e 100644 --- a/apps/web/src/onboarding/providerReadiness.logic.ts +++ b/apps/web/src/onboarding/providerReadiness.logic.ts @@ -71,6 +71,36 @@ export function selectOnboardingProvidersByDriver( return providersByDriver; } +/** + * Official standalone installers. Neither needs Node or npm, and both land in + * the paths the server's provider maintenance recognizes as native, so the + * one-click updater in Settings keeps working after install. + */ +const NATIVE_INSTALL_COMMANDS = { + claudeAgent: { + windows: "irm https://claude.ai/install.ps1 | iex", + posix: "curl -fsSL https://claude.ai/install.sh | bash", + }, + codex: { + windows: "irm https://chatgpt.com/codex/install.ps1 | iex", + posix: "curl -fsSL https://chatgpt.com/codex/install.sh | sh", + }, +} as const; + +/** + * Install command for the setup terminal, keyed on the environment's platform + * (not the client's): a Windows desktop driving a WSL server gets the shell + * script. Unknown platforms get the shell script too, since the terminal there + * is a POSIX shell in practice. + */ +export function resolveOnboardingProviderInstallCommand( + driver: keyof typeof NATIVE_INSTALL_COMMANDS, + platform: ExecutionEnvironmentPlatformOs, +): string { + const commands = NATIVE_INSTALL_COMMANDS[driver]; + return platform === "windows" ? commands.windows : commands.posix; +} + /** Use the selected provider instance's binary when the setup terminal opens its login flow. */ export function resolveOnboardingProviderLoginCommand( provider: ServerProvider, diff --git a/docs/user/welcome-wizard.md b/docs/user/welcome-wizard.md index 9a2aa116670d6a3669aa7b3a89cd5aae6217431d..2534c118eddbb4f15d153e27e3a216f6b90b89d2 100644 --- a/docs/user/welcome-wizard.md +++ b/docs/user/welcome-wizard.md @@ -26,7 +26,9 @@ unreadable settings with defaults. T3 Code checks the selected computer for Claude Code and Codex. If an agent is not installed or signed in, select its action to open a terminal with the -correct command ready to run. Other providers can be enabled in Settings. +correct command ready to run. Install uses the vendor's standalone installer, +which does not need Node or npm and keeps **Update now** working in Settings. +Other providers can be enabled in Settings. The setup terminal uses the home directory and environment configured for the selected provider instance. Sensitive values remain redacted in Settings and -- 2.54.0