fix: follow up Codex launcher and input handling

This commit is contained in:
vp
2026-04-01 19:15:37 +03:00
parent b8ea6f8a6e
commit c8a780a9bd
2 changed files with 57 additions and 23 deletions

View File

@@ -97,12 +97,15 @@ async function resolveOllamaDefaultModel(
} }
function runCommand(command: string, env: NodeJS.ProcessEnv): Promise<number> { function runCommand(command: string, env: NodeJS.ProcessEnv): Promise<number> {
return runProcess(command, [], env)
}
function runProcess(command: string, args: string[], env: NodeJS.ProcessEnv): Promise<number> {
return new Promise(resolve => { return new Promise(resolve => {
const child = spawn(command, { const child = spawn(command, args, {
cwd: process.cwd(), cwd: process.cwd(),
env, env,
stdio: 'inherit', stdio: 'inherit',
shell: true,
}) })
child.on('close', code => resolve(code ?? 1)) child.on('close', code => resolve(code ?? 1))
@@ -120,11 +123,6 @@ function applyFastFlags(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
return env return env
} }
function quoteArg(arg: string): string {
if (!arg.includes(' ') && !arg.includes('"')) return arg
return `"${arg.replace(/"/g, '\\"')}"`
}
function printSummary(profile: ProviderProfile, env: NodeJS.ProcessEnv): void { function printSummary(profile: ProviderProfile, env: NodeJS.ProcessEnv): void {
console.log(`Launching profile: ${profile}`) console.log(`Launching profile: ${profile}`)
if (profile === 'gemini') { if (profile === 'gemini') {
@@ -216,15 +214,18 @@ async function main(): Promise<void> {
printSummary(profile, env) printSummary(profile, env)
const doctorCode = await runCommand('bun run scripts/system-check.ts', env) const doctorCode = await runProcess('bun', ['run', 'scripts/system-check.ts'], env)
if (doctorCode !== 0) { if (doctorCode !== 0) {
console.error('Runtime doctor failed. Fix configuration before launching.') console.error('Runtime doctor failed. Fix configuration before launching.')
process.exit(doctorCode) process.exit(doctorCode)
} }
const cliArgs = options.passthroughArgs.map(quoteArg).join(' ') const buildCode = await runProcess('bun', ['run', 'build'], env)
const devCommand = cliArgs ? `bun run dev -- ${cliArgs}` : 'bun run dev' if (buildCode !== 0) {
const devCode = await runCommand(devCommand, env) process.exit(buildCode)
}
const devCode = await runProcess('node', ['dist/cli.mjs', ...options.passthroughArgs], env)
process.exit(devCode) process.exit(devCode)
} }

View File

@@ -1,6 +1,6 @@
import indentString from 'indent-string' import indentString from 'indent-string'
import { applyTextStyles } from './colorize.js' import { applyTextStyles } from './colorize.js'
import type { DOMElement } from './dom.js' import type { DOMElement, DOMNode } from './dom.js'
import getMaxWidth from './get-max-width.js' import getMaxWidth from './get-max-width.js'
import type { Rectangle } from './layout/geometry.js' import type { Rectangle } from './layout/geometry.js'
import { LayoutDisplay, LayoutEdge, type LayoutNode } from './layout/node.js' import { LayoutDisplay, LayoutEdge, type LayoutNode } from './layout/node.js'
@@ -383,6 +383,23 @@ function applyPaddingToText(
return text return text
} }
function isElementNode(node: DOMNode | undefined): node is DOMElement {
return Boolean(node && node.nodeName !== '#text')
}
function isRenderableElementNode(node: unknown): node is DOMElement {
if (!node || typeof node !== 'object') return false
const candidate = node as Partial<DOMElement> & { nodeName?: string }
return (
candidate.nodeName !== undefined &&
candidate.nodeName !== '#text' &&
candidate.style !== undefined &&
typeof candidate.style === 'object' &&
typeof candidate.dirty === 'boolean' &&
Array.isArray(candidate.childNodes)
)
}
// After nodes are laid out, render each to output object, which later gets rendered to terminal // After nodes are laid out, render each to output object, which later gets rendered to terminal
function renderNodeToOutput( function renderNodeToOutput(
node: DOMElement, node: DOMElement,
@@ -701,9 +718,9 @@ function renderNodeToOutput(
yogaNode.getComputedPadding(LayoutEdge.Bottom), yogaNode.getComputedPadding(LayoutEdge.Bottom),
) )
const content = node.childNodes.find(c => (c as DOMElement).yogaNode) as const content = node.childNodes.find(
| DOMElement (c): c is DOMElement => isElementNode(c) && c.yogaNode !== undefined,
| undefined )
const contentYoga = content?.yogaNode const contentYoga = content?.yogaNode
// scrollHeight is the intrinsic height of the content wrapper. // scrollHeight is the intrinsic height of the content wrapper.
// Do NOT add getComputedTop() — that's the wrapper's offset // Do NOT add getComputedTop() — that's the wrapper's offset
@@ -937,8 +954,13 @@ function renderNodeToOutput(
// Snapshot dirty children before the first pass — the first // Snapshot dirty children before the first pass — the first
// pass clears dirty flags, and edge-spanning children would be // pass clears dirty flags, and edge-spanning children would be
// missed by the second pass without this snapshot. // missed by the second pass without this snapshot.
const dirtyChildren = content.dirty const dirtyChildren =
? new Set(content.childNodes.filter(c => (c as DOMElement).dirty)) content.dirty
? new Set(
content.childNodes.filter(
(c): c is DOMElement => isElementNode(c) && c.dirty,
),
)
: null : null
renderScrolledChildren( renderScrolledChildren(
content, content,
@@ -989,7 +1011,8 @@ function renderNodeToOutput(
// preserving the ghost-box fix. // preserving the ghost-box fix.
let cumHeightShift = 0 let cumHeightShift = 0
for (const childNode of content.childNodes) { for (const childNode of content.childNodes) {
const childElem = childNode as DOMElement if (!isElementNode(childNode)) continue
const childElem = childNode
const isDirty = dirtyChildren.has(childNode) const isDirty = dirtyChildren.has(childNode)
if (!isDirty && cumHeightShift === 0) { if (!isDirty && cumHeightShift === 0) {
if (nodeCache.has(childElem)) continue if (nodeCache.has(childElem)) continue
@@ -1266,7 +1289,10 @@ function renderChildren(
let seenDirtyChild = false let seenDirtyChild = false
let seenDirtyClipped = false let seenDirtyClipped = false
for (const childNode of node.childNodes) { for (const childNode of node.childNodes) {
const childElem = childNode as DOMElement if (!isRenderableElementNode(childNode)) {
continue
}
const childElem = childNode
// Capture dirty before rendering — renderNodeToOutput clears the flag // Capture dirty before rendering — renderNodeToOutput clears the flag
const wasDirty = childElem.dirty const wasDirty = childElem.dirty
const isAbsolute = childElem.style.position === 'absolute' const isAbsolute = childElem.style.position === 'absolute'
@@ -1313,14 +1339,18 @@ function siblingSharesY(node: DOMElement, yogaNode: LayoutNode): boolean {
const siblings = parent.childNodes const siblings = parent.childNodes
const idx = siblings.indexOf(node) const idx = siblings.indexOf(node)
for (let i = idx + 1; i < siblings.length; i++) { for (let i = idx + 1; i < siblings.length; i++) {
const sib = (siblings[i] as DOMElement).yogaNode const sibling = siblings[i]
if (!isElementNode(sibling)) continue
const sib = sibling.yogaNode
if (!sib) continue if (!sib) continue
return sib.getComputedTop() === myTop return sib.getComputedTop() === myTop
} }
// No next sibling with a yoga node — check previous. A run of h=0 boxes // No next sibling with a yoga node — check previous. A run of h=0 boxes
// at the tail would all share y with each other. // at the tail would all share y with each other.
for (let i = idx - 1; i >= 0; i--) { for (let i = idx - 1; i >= 0; i--) {
const sib = (siblings[i] as DOMElement).yogaNode const sibling = siblings[i]
if (!isElementNode(sibling)) continue
const sib = sibling.yogaNode
if (!sib) continue if (!sib) continue
return sib.getComputedTop() === myTop return sib.getComputedTop() === myTop
} }
@@ -1399,7 +1429,10 @@ function renderScrolledChildren(
// culling since their yogaTop shifted). // culling since their yogaTop shifted).
let cumHeightShift = 0 let cumHeightShift = 0
for (const childNode of node.childNodes) { for (const childNode of node.childNodes) {
const childElem = childNode as DOMElement if (!isRenderableElementNode(childNode)) {
continue
}
const childElem = childNode
const cy = childElem.yogaNode const cy = childElem.yogaNode
if (cy) { if (cy) {
const cached = nodeCache.get(childElem) const cached = nodeCache.get(childElem)