handling files when stages and dirty at once
This commit is contained in:
@@ -2,6 +2,7 @@ import { createRequire } from 'node:module'
|
||||
import type { WebContents } from 'electron'
|
||||
import { getRoot } from './project'
|
||||
import { getConfig } from './config'
|
||||
import { logger } from './logger'
|
||||
|
||||
/**
|
||||
* Real PTYs. The agent pane is a shell that auto-launches the `claude` CLI; the
|
||||
@@ -19,10 +20,14 @@ let pty: PtyModule | null = null
|
||||
try {
|
||||
pty = require('node-pty') as PtyModule
|
||||
} catch (e) {
|
||||
console.error('[helder] node-pty unavailable — run `npm run rebuild`:', (e as Error).message)
|
||||
logger.error('pty', 'node-pty unavailable — run `npm run rebuild`', e)
|
||||
}
|
||||
|
||||
const terms = new Map<number, import('node-pty').IPty>()
|
||||
/** Ids we killed on purpose (pane closed, window quitting, StrictMode remount).
|
||||
* Their exit is expected, so it must NOT be logged as a warning — a log full of
|
||||
* false alarms is a log nobody reads. */
|
||||
const killing = new Set<number>()
|
||||
let seq = 0
|
||||
|
||||
/**
|
||||
@@ -45,6 +50,13 @@ function ptyEnv(): { [key: string]: string } {
|
||||
if (process.platform !== 'win32' && !env.LC_ALL && !env.LC_CTYPE && !env.LANG) {
|
||||
env.LANG = 'en_US.UTF-8'
|
||||
}
|
||||
// Same inheritance gap as locale, but for color: a terminal launch leaks
|
||||
// COLORTERM=truecolor so `claude` renders its UI backgrounds as exact 24-bit
|
||||
// colors; the GUI-launched packaged app has none, so claude falls back to a
|
||||
// 256/16-color approximation and the same backgrounds shift shade. Match both.
|
||||
if (process.platform !== 'win32' && !env.COLORTERM) {
|
||||
env.COLORTERM = 'truecolor'
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
@@ -60,7 +72,10 @@ export function ptyAvailable(): boolean {
|
||||
}
|
||||
|
||||
export function createPty(sender: WebContents, kind: 'agent' | 'shell', cols: number, rows: number): number {
|
||||
if (!pty) return -1
|
||||
if (!pty) {
|
||||
logger.warn('pty', `create(${kind}) refused — node-pty never loaded`)
|
||||
return -1
|
||||
}
|
||||
const cwd = getRoot() || process.env.HOME || process.cwd()
|
||||
const shell = defaultShell()
|
||||
const ai = getConfig().ai
|
||||
@@ -71,7 +86,7 @@ export function createPty(sender: WebContents, kind: 'agent' | 'shell', cols: nu
|
||||
// echoed command cluttering the pane; claude takes over a clean terminal.
|
||||
const args = launchAgent ? ['-i', '-c', ai.command] : []
|
||||
const proc = pty.spawn(shell, args, {
|
||||
name: 'xterm-color',
|
||||
name: 'xterm-256color',
|
||||
cols: cols || 80,
|
||||
rows: rows || 24,
|
||||
cwd,
|
||||
@@ -79,9 +94,21 @@ export function createPty(sender: WebContents, kind: 'agent' | 'shell', cols: nu
|
||||
})
|
||||
const id = ++seq
|
||||
terms.set(id, proc)
|
||||
logger.info('pty', `spawned ${kind}`, { id, pid: proc.pid, shell, args, cwd })
|
||||
|
||||
proc.onData((data) => { if (!sender.isDestroyed()) sender.send('pty:data', { id, data }) })
|
||||
proc.onExit(() => { terms.delete(id); if (!sender.isDestroyed()) sender.send('pty:exit', { id }) })
|
||||
proc.onExit(({ exitCode, signal }) => {
|
||||
terms.delete(id)
|
||||
// The agent pane dying on its own (`claude` not on PATH, OOM-killed,
|
||||
// segfault) looks from the UI like "the terminal just went blank" — the exit
|
||||
// code and signal are the only evidence of what actually happened. An exit we
|
||||
// asked for is routine, so only an unrequested one is a warning.
|
||||
const expected = killing.delete(id)
|
||||
const abnormal = !expected && (exitCode !== 0 || (signal != null && signal !== 0))
|
||||
if (abnormal) logger.warn('pty', `${kind} exited unexpectedly`, { id, exitCode, signal })
|
||||
else logger.info('pty', `${kind} exited`, { id, exitCode, expected })
|
||||
if (!sender.isDestroyed()) sender.send('pty:exit', { id })
|
||||
})
|
||||
|
||||
// Windows path keeps the type-into-shell launch (no `-i -c` semantics there).
|
||||
if (kind === 'agent' && ai.autoLaunch && process.platform === 'win32') {
|
||||
@@ -100,10 +127,10 @@ export function resizePty(id: number, cols: number, rows: number): void {
|
||||
|
||||
export function killPty(id: number): void {
|
||||
const p = terms.get(id)
|
||||
if (p) { try { p.kill() } catch { /* already gone */ } terms.delete(id) }
|
||||
if (p) { killing.add(id); try { p.kill() } catch { /* already gone */ } terms.delete(id) }
|
||||
}
|
||||
|
||||
export function killAllPtys(): void {
|
||||
for (const p of terms.values()) { try { p.kill() } catch { /* noop */ } }
|
||||
for (const [id, p] of terms) { killing.add(id); try { p.kill() } catch { /* noop */ } }
|
||||
terms.clear()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user