Fix p10k wizard blocking terminal startup

This commit is contained in:
Neil
2026-06-26 12:22:42 -07:00
committed by GitHub
parent 55c0c74951
commit 0f26a1bb5e
8 changed files with 208 additions and 11 deletions
+60 -1
View File
@@ -55,6 +55,7 @@ const ORCA_SHELL_WRAPPER_ENV = [
] as const
const POWERSHELL_OSC133_COMMAND_ARGS = ['-NoLogo', '-NoExit', '-EncodedCommand', expect.any(String)]
const ZSH_SHELL_READY_DIR = /shell-ready[\\/]zsh/
const POWERLEVEL10K_WIZARD_DISABLE_ENV = 'POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD'
const itOnMacHost = process.platform === 'darwin' ? it : it.skip
function mockPtyProcess(pid = 12345) {
@@ -82,6 +83,7 @@ function mockPtyProcess(pid = 12345) {
describe('createPtySubprocess', () => {
const savedWrapperEnv: Partial<Record<(typeof ORCA_SHELL_WRAPPER_ENV)[number], string>> = {}
let previousUserDataPath: string | undefined
let previousPowerlevelWizardDisable: string | undefined
let userDataPath: string
beforeEach(() => {
@@ -94,8 +96,10 @@ describe('createPtySubprocess', () => {
validateWorkingDirectoryMock.mockClear()
isPwshAvailableMock.mockReturnValue(false)
previousUserDataPath = process.env.ORCA_USER_DATA_PATH
previousPowerlevelWizardDisable = process.env[POWERLEVEL10K_WIZARD_DISABLE_ENV]
userDataPath = mkdtempSync(join(tmpdir(), 'daemon-pty-subprocess-test-'))
process.env.ORCA_USER_DATA_PATH = userDataPath
delete process.env[POWERLEVEL10K_WIZARD_DISABLE_ENV]
for (const key of ORCA_SHELL_WRAPPER_ENV) {
savedWrapperEnv[key] = process.env[key]
delete process.env[key]
@@ -108,6 +112,11 @@ describe('createPtySubprocess', () => {
} else {
process.env.ORCA_USER_DATA_PATH = previousUserDataPath
}
if (previousPowerlevelWizardDisable === undefined) {
delete process.env[POWERLEVEL10K_WIZARD_DISABLE_ENV]
} else {
process.env[POWERLEVEL10K_WIZARD_DISABLE_ENV] = previousPowerlevelWizardDisable
}
rmSync(userDataPath, { recursive: true, force: true })
for (const key of ORCA_SHELL_WRAPPER_ENV) {
if (savedWrapperEnv[key] === undefined) {
@@ -150,6 +159,29 @@ describe('createPtySubprocess', () => {
)
})
it('suppresses the first-run Powerlevel10k wizard for daemon terminals', () => {
const proc = mockPtyProcess()
spawnMock.mockReturnValue(proc)
const platform = Object.getOwnPropertyDescriptor(process, 'platform')
Object.defineProperty(process, 'platform', { value: 'linux' })
try {
createPtySubprocess({
sessionId: 'test',
cols: 80,
rows: 24,
env: { SHELL: '/bin/bash' }
})
} finally {
if (platform) {
Object.defineProperty(process, 'platform', platform)
}
}
const spawnCall = spawnMock.mock.calls.at(-1)!
expect(spawnCall[2].env[POWERLEVEL10K_WIZARD_DISABLE_ENV]).toBe('true')
})
itOnMacHost('repairs a deleted macOS daemon cwd before spawning node-pty', () => {
const proc = mockPtyProcess()
spawnMock.mockReturnValue(proc)
@@ -1828,12 +1860,39 @@ describe('createPtySubprocess', () => {
expect.objectContaining({
env: expect.objectContaining({
ORCA_TERMINAL_HANDLE: 'term_wsl',
WSLENV: 'FOO/u:ORCA_TERMINAL_HANDLE/u'
WSLENV: 'FOO/u:ORCA_TERMINAL_HANDLE/u:POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD'
})
})
)
})
it('does not mark deleted Powerlevel10k wizard env for daemon WSL import', () => {
const proc = mockPtyProcess()
spawnMock.mockReturnValue(proc)
const platform = Object.getOwnPropertyDescriptor(process, 'platform')
Object.defineProperty(process, 'platform', { value: 'win32' })
try {
createPtySubprocess({
sessionId: 'test',
cols: 80,
rows: 24,
cwd: '\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo',
envToDelete: [POWERLEVEL10K_WIZARD_DISABLE_ENV]
})
} finally {
if (platform) {
Object.defineProperty(process, 'platform', platform)
}
}
const spawnCall = spawnMock.mock.calls.at(-1)!
expect(spawnCall[0]).toBe('wsl.exe')
expect(spawnCall[2].env[POWERLEVEL10K_WIZARD_DISABLE_ENV]).toBeUndefined()
expect(spawnCall[2].env.WSLENV ?? '').not.toContain(POWERLEVEL10K_WIZARD_DISABLE_ENV)
})
it('keeps daemon WSL split panes in their distro when cwd is already POSIX', () => {
const proc = mockPtyProcess()
spawnMock.mockReturnValue(proc)
+12
View File
@@ -25,6 +25,10 @@ import { parseWslPath } from '../wsl'
import { addWslEnvKeys } from '../wsl-env'
import { getWslContextFromSessionId } from './wsl-session-context'
import { addOrcaWslInteropEnv } from '../pty/wsl-orca-env'
import {
POWERLEVEL10K_WIZARD_DISABLE_ENV,
seedPowerlevel10kWizardEnv
} from '../pty/powerlevel10k-wizard-env'
import { isWindowsGitBashShellPath, resolveWindowsGitBashShellPath } from '../git-bash'
import { WINDOWS_GIT_BASH_SHELL } from '../../shared/windows-terminal-shell'
import { resolveAgentForegroundProcess } from '../providers/agent-foreground-process'
@@ -605,6 +609,14 @@ export function createPtySubprocess(opts: PtySubprocessOptions): SubprocessHandl
}
shellArgs = shellLaunch?.args ?? ['-l']
}
seedPowerlevel10kWizardEnv(env, { envToDelete: opts.envToDelete })
if (
env[POWERLEVEL10K_WIZARD_DISABLE_ENV] !== undefined &&
process.platform === 'win32' &&
pathWin32.basename(shellPath).toLowerCase() === 'wsl.exe'
) {
addWslEnvKeys(env, [POWERLEVEL10K_WIZARD_DISABLE_ENV])
}
promoteAgentTeamsShimPath(env, opts.env?.PATH)
// Why: asar packaging can strip the +x bit from node-pty's spawn-helper
+1 -1
View File
@@ -4811,7 +4811,7 @@ describe('registerPtyHandlers', () => {
const env = spawnCall[2].env as Record<string, string>
expect(spawnCall[0]).toBe('wsl.exe')
expect(env.ORCA_TERMINAL_HANDLE).toBe('term_wsl')
expect(env.WSLENV).toBe('ORCA_TERMINAL_HANDLE/u')
expect(env.WSLENV).toBe('ORCA_TERMINAL_HANDLE/u:POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD')
})
describe('Windows UTF-8 code page', () => {
+57 -1
View File
@@ -61,6 +61,7 @@ vi.mock('../wsl', () => ({
}))
import { LocalPtyProvider } from './local-pty-provider'
import { POWERLEVEL10K_WIZARD_DISABLE_ENV } from '../pty/powerlevel10k-wizard-env'
describe('LocalPtyProvider', () => {
let provider: LocalPtyProvider
@@ -75,13 +76,16 @@ describe('LocalPtyProvider', () => {
}
let exitCb: ((info: { exitCode: number }) => void) | undefined
let origShell: string | undefined
let origPowerlevelWizardDisable: string | undefined
let origPlatform: PropertyDescriptor | undefined
beforeEach(() => {
origPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' })
origShell = process.env.SHELL
origPowerlevelWizardDisable = process.env.POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD
process.env.SHELL = '/bin/zsh'
delete process.env.POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD
existsSyncMock.mockReturnValue(true)
statSyncMock.mockReturnValue({ isDirectory: () => true, mode: 0o755 })
@@ -128,6 +132,11 @@ describe('LocalPtyProvider', () => {
} else {
process.env.SHELL = origShell
}
if (origPowerlevelWizardDisable === undefined) {
delete process.env.POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD
} else {
process.env.POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD = origPowerlevelWizardDisable
}
})
describe('spawn', () => {
@@ -198,6 +207,35 @@ describe('LocalPtyProvider', () => {
expect(spawnCall[2].env.CUSTOM_VAR).toBe('custom-value')
})
it('suppresses the first-run Powerlevel10k wizard for spawned terminals', async () => {
await provider.spawn({ cols: 80, rows: 24 })
const spawnCall = spawnMock.mock.calls.at(-1)!
expect(spawnCall[2].env.POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD).toBe('true')
})
it('preserves an explicit Powerlevel10k wizard env value', async () => {
await provider.spawn({
cols: 80,
rows: 24,
env: { POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD: 'already-set' }
})
const spawnCall = spawnMock.mock.calls.at(-1)!
expect(spawnCall[2].env.POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD).toBe('already-set')
})
it('honors requests to delete the Powerlevel10k wizard env value', async () => {
await provider.spawn({
cols: 80,
rows: 24,
envToDelete: ['POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD']
})
const spawnCall = spawnMock.mock.calls.at(-1)!
expect(spawnCall[2].env.POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD).toBeUndefined()
})
it('uses fallback shell readiness when startup-command shell spawn falls back', async () => {
vi.useFakeTimers()
try {
@@ -506,7 +544,25 @@ describe('LocalPtyProvider', () => {
const spawnCall = spawnMock.mock.calls.at(-1)!
expect(spawnCall[0]).toBe('wsl.exe')
expect(spawnCall[2].env.ORCA_TERMINAL_HANDLE).toBe('term_wsl')
expect(spawnCall[2].env.WSLENV).toBe('ORCA_TERMINAL_HANDLE/u')
expect(spawnCall[2].env.WSLENV).toBe(
'ORCA_TERMINAL_HANDLE/u:POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD'
)
})
it('does not mark deleted Powerlevel10k wizard env for WSL import', async () => {
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
await provider.spawn({
cols: 80,
rows: 24,
cwd: '\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo',
envToDelete: [POWERLEVEL10K_WIZARD_DISABLE_ENV]
})
const spawnCall = spawnMock.mock.calls.at(-1)!
expect(spawnCall[0]).toBe('wsl.exe')
expect(spawnCall[2].env[POWERLEVEL10K_WIZARD_DISABLE_ENV]).toBeUndefined()
expect(spawnCall[2].env.WSLENV ?? '').not.toContain(POWERLEVEL10K_WIZARD_DISABLE_ENV)
})
it('does not inherit parent Orca pane identity when caller omits pane env', async () => {
+12
View File
@@ -35,6 +35,10 @@ import type { ShellReadySignal } from './local-pty-shell-ready'
import { removeInheritedNoColor } from '../pty/terminal-color-env'
import { isHostCodexHomeForWsl, isWslCodexHomeForHost } from '../pty/codex-home-wsl-env'
import { addWslEnvKeys } from '../wsl-env'
import {
POWERLEVEL10K_WIZARD_DISABLE_ENV,
seedPowerlevel10kWizardEnv
} from '../pty/powerlevel10k-wizard-env'
import {
isWindowsGitBashShellPath,
resolveGitBashPath,
@@ -533,6 +537,14 @@ export class LocalPtyProvider implements IPtyProvider {
delete finalEnv.ORCA_CODEX_HOME
}
}
seedPowerlevel10kWizardEnv(finalEnv, { envToDelete: args.envToDelete })
if (
finalEnv[POWERLEVEL10K_WIZARD_DISABLE_ENV] !== undefined &&
process.platform === 'win32' &&
pathWin32.basename(shellPath).toLowerCase() === 'wsl.exe'
) {
addWslEnvKeys(finalEnv, [POWERLEVEL10K_WIZARD_DISABLE_ENV])
}
if (!wslInfo && process.platform !== 'win32') {
// Why: OpenCode/Codex path restoration and OMP's typed-command status
// wrapper need shell-ready code after user startup files run.
+41 -2
View File
@@ -1,5 +1,6 @@
import { describe, expect, it, vi, beforeEach } from 'vitest'
import { SshPtyProvider } from './ssh-pty-provider'
import { POWERLEVEL10K_WIZARD_DISABLE_ENV } from '../pty/powerlevel10k-wizard-env'
type MockMultiplexer = {
request: ReturnType<typeof vi.fn>
@@ -43,7 +44,7 @@ describe('SshPtyProvider', () => {
cols: 80,
rows: 24,
cwd: undefined,
env: undefined
env: { [POWERLEVEL10K_WIZARD_DISABLE_ENV]: 'true' }
})
expect(result).toEqual({ id: scopedPty1 })
})
@@ -62,7 +63,42 @@ describe('SshPtyProvider', () => {
cols: 120,
rows: 40,
cwd: '/home/user',
env: { FOO: 'bar' }
env: { FOO: 'bar', [POWERLEVEL10K_WIZARD_DISABLE_ENV]: 'true' }
})
})
it('preserves an explicit remote Powerlevel10k wizard env value', async () => {
mux.request.mockResolvedValue({ id: 'pty-2' })
await provider.spawn({
cols: 120,
rows: 40,
env: { [POWERLEVEL10K_WIZARD_DISABLE_ENV]: 'already-set' }
})
expect(mux.request).toHaveBeenCalledWith('pty.spawn', {
cols: 120,
rows: 40,
cwd: undefined,
env: { [POWERLEVEL10K_WIZARD_DISABLE_ENV]: 'already-set' }
})
})
it('honors requests to delete the remote Powerlevel10k wizard env value', async () => {
mux.request.mockResolvedValue({ id: 'pty-2' })
await provider.spawn({
cols: 120,
rows: 40,
env: { [POWERLEVEL10K_WIZARD_DISABLE_ENV]: 'already-set' },
envToDelete: [POWERLEVEL10K_WIZARD_DISABLE_ENV]
})
expect(mux.request).toHaveBeenCalledWith('pty.spawn', {
cols: 120,
rows: 40,
cwd: undefined,
env: {}
})
})
@@ -88,6 +124,7 @@ describe('SshPtyProvider', () => {
env: {
PATH: '/home/user/.orca-relay/bin:/usr/bin',
ORCA_TERMINAL_HANDLE: 'term_ssh',
[POWERLEVEL10K_WIZARD_DISABLE_ENV]: 'true',
ORCA_REMOTE_CLI_BIN_DIR: '/home/user/.orca-relay/bin',
ORCA_RELAY_DIR: '/home/user/.orca-relay/relay-v1',
ORCA_RELAY_NODE_PATH: '/usr/bin/node',
@@ -117,6 +154,7 @@ describe('SshPtyProvider', () => {
cwd: undefined,
env: {
ORCA_TERMINAL_HANDLE: 'term_ssh',
[POWERLEVEL10K_WIZARD_DISABLE_ENV]: 'true',
ORCA_REMOTE_CLI_BIN_DIR: '/home/user/.orca-relay/bin',
ORCA_RELAY_DIR: '/home/user/.orca-relay/relay-v1',
ORCA_RELAY_NODE_PATH: '/usr/bin/node',
@@ -147,6 +185,7 @@ describe('SshPtyProvider', () => {
cwd: undefined,
env: {
Path: 'C:/Users/me/.orca-relay/bin;C:/Windows/System32;C:/Tools',
[POWERLEVEL10K_WIZARD_DISABLE_ENV]: 'true',
ORCA_REMOTE_CLI_BIN_DIR: 'C:/Users/me/.orca-relay/bin',
ORCA_RELAY_DIR: 'C:/Users/me/.orca-remote/relay-v1',
ORCA_RELAY_NODE_PATH: 'C:/Program Files/nodejs/node.exe',
+12 -6
View File
@@ -1,6 +1,7 @@
import type { SshChannelMultiplexer } from '../ssh/ssh-channel-multiplexer'
import type { IPtyProvider, PtySpawnOptions, PtySpawnResult } from './types'
import { toAppSshPtyId, toRelaySshPtyId } from './ssh-pty-id'
import { seedPowerlevel10kWizardEnv } from '../pty/powerlevel10k-wizard-env'
type DataCallback = (payload: { id: string; data: string }) => void
type ReplayCallback = (payload: { id: string; data: string }) => void
@@ -130,7 +131,7 @@ export class SshPtyProvider implements IPtyProvider {
cols: opts.cols,
rows: opts.rows,
cwd: opts.cwd,
env: this.withRemoteCliBridgeEnv(opts.env),
env: this.withRemoteCliBridgeEnv(opts.env, opts.envToDelete),
// Why: the relay's plugin-overlay env augmenter needs to know which
// Pi-compatible agent is being launched (`pi` vs `omp`) so it mirrors
// the right `~/.<kind>/agent` source dir on the remote disk. The
@@ -150,12 +151,17 @@ export class SshPtyProvider implements IPtyProvider {
}
private withRemoteCliBridgeEnv(
env: Record<string, string> | undefined
): Record<string, string> | undefined {
if (!this.remoteCliBridgeEnv) {
return env
}
env: Record<string, string> | undefined,
envToDelete?: readonly string[]
): Record<string, string> {
const merged = { ...env }
for (const key of envToDelete ?? []) {
delete merged[key]
}
seedPowerlevel10kWizardEnv(merged, { envToDelete })
if (!this.remoteCliBridgeEnv) {
return merged
}
const pathDelimiter = this.remoteCliBridgeEnv.pathDelimiter ?? ':'
const pathKey = merged.PATH !== undefined ? 'PATH' : merged.Path !== undefined ? 'Path' : null
if (pathKey) {
+13
View File
@@ -0,0 +1,13 @@
export const POWERLEVEL10K_WIZARD_DISABLE_ENV = 'POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD'
export function seedPowerlevel10kWizardEnv(
env: Record<string, string>,
options: { envToDelete?: readonly string[] } = {}
): void {
if (options.envToDelete?.includes(POWERLEVEL10K_WIZARD_DISABLE_ENV)) {
return
}
// Why: p10k's first-run wizard blocks shell startup and queued commands.
// Users can still run `p10k configure` manually inside an Orca terminal.
env[POWERLEVEL10K_WIZARD_DISABLE_ENV] ??= 'true'
}