feat: add thinking option support and autonomous run handling

This commit is contained in:
Mohamed Boudra
2026-02-21 22:09:48 +07:00
parent c3c8103563
commit d7e2ab4437
35 changed files with 4701 additions and 593 deletions
@@ -0,0 +1,131 @@
import { spawnSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import path from 'node:path'
export type NodePathSource = 'daemon_pid' | 'current_process'
export interface NodePathResolution {
nodePath: string
source: NodePathSource
note?: string
}
export interface NodePathFromPidResult {
nodePath: string | null
error?: string
}
export interface NpmInvocation {
nodePath: string
npmPath: string
command: string
argsPrefix: string[]
}
function normalizeError(error: unknown): string {
if (error instanceof Error) {
return error.message
}
return String(error)
}
export function resolveNodePathFromPid(pid: number): NodePathFromPidResult {
const result = spawnSync('ps', ['-o', 'comm=', '-p', String(pid)], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
})
if (result.error) {
return {
nodePath: null,
error: `ps failed: ${normalizeError(result.error)}`,
}
}
if ((result.status ?? 1) !== 0) {
const details = result.stderr?.trim()
return {
nodePath: null,
error: details ? `ps failed: ${details}` : `ps exited with code ${result.status ?? 1}`,
}
}
const resolved = result.stdout.trim()
if (!resolved) {
return {
nodePath: null,
error: 'ps returned an empty command path',
}
}
return { nodePath: resolved }
}
export function resolvePreferredNodePath(args: {
daemonPid?: number | null
fallbackNodePath?: string
}): NodePathResolution {
const fallback = args.fallbackNodePath ?? process.execPath
const daemonPid = args.daemonPid
if (typeof daemonPid === 'number' && Number.isInteger(daemonPid) && daemonPid > 0) {
const fromPid = resolveNodePathFromPid(daemonPid)
if (fromPid.nodePath) {
return {
nodePath: fromPid.nodePath,
source: 'daemon_pid',
}
}
return {
nodePath: fallback,
source: 'current_process',
note: `Could not resolve node from daemon PID ${daemonPid}; using current process node (${fromPid.error ?? 'unknown error'})`,
}
}
return {
nodePath: fallback,
source: 'current_process',
}
}
export function resolveNpmInvocationFromNode(nodePath: string): NpmInvocation {
const binDir = path.dirname(nodePath)
const prefix = path.dirname(binDir)
const npmBinary = path.join(binDir, process.platform === 'win32' ? 'npm.cmd' : 'npm')
if (existsSync(npmBinary)) {
return {
nodePath,
npmPath: npmBinary,
command: npmBinary,
argsPrefix: [],
}
}
const npmCliCandidates = [
path.join(prefix, 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
path.join(prefix, 'node_modules', 'npm', 'bin', 'npm-cli.js'),
]
for (const candidate of npmCliCandidates) {
if (existsSync(candidate)) {
return {
nodePath,
npmPath: candidate,
command: nodePath,
argsPrefix: [candidate],
}
}
}
throw new Error(`Unable to resolve npm for node executable: ${nodePath}`)
}
export function formatNpmInvocation(invocation: NpmInvocation): string {
if (invocation.argsPrefix.length === 0) {
return invocation.command
}
return `${invocation.command} ${invocation.argsPrefix.join(' ')}`
}