Files
orca/src/shared/dsh-launch-command.ts
T

101 lines
3.4 KiB
TypeScript

import { findInterpreterEntrypointToken } from './agent-command-line-entrypoint'
// DSH 0.2 accepts positional profiles; community dsh-tui/dst choose their own TUI profile.
/** Profiles that boot something other than the interactive terminal UI. */
const NON_INTERACTIVE_PROFILES = new Set([
'web',
'headless',
'sdk',
'sdk-minimal',
'acp',
'desktop'
])
/** Plugin management never boots an interactive pane. */
const SUBCOMMANDS = new Set(['plugin'])
/** Launcher flags that print a composed config and exit. */
const DUMP_FLAGS = new Set(['--dump-config', '--dump-default-config', '--dump-config-schema'])
/** Binaries that always boot `--profile dsh-tui` and forward the rest to the terminal app. */
const TUI_LAUNCHER_NAMES = new Set(['dsh-tui', 'dst'])
const PROGRAM_EXTENSION_RE = /\.(?:exe|cmd|bat|ps1|js|mjs|cjs)$/
function programBasename(token: string | undefined): string {
const unquoted = token?.trim().replace(/^["']|["']$/g, '') ?? ''
const basename = unquoted.split(/[\\/]/).pop() ?? unquoted
return basename.toLowerCase().replace(PROGRAM_EXTENSION_RE, '')
}
function readProfileName(tokens: readonly string[], index: number): string | null {
const token = tokens[index]
if (token === undefined) {
return null
}
if (token.startsWith('--profile=')) {
return token.slice('--profile='.length)
}
return token === '--profile' ? (tokens[index + 1] ?? null) : null
}
/** Launcher flags that take a value, so the token after them is never an app argument. */
const LAUNCHER_FLAGS_WITH_VALUE = new Set(['--profile', '--from-default-profile', '--patch'])
/** Valueless launcher flags. */
const LAUNCHER_FLAGS = new Set(['-V', '--version', '-h', '--help', ...DUMP_FLAGS])
/** Splits `--profile=web` down to `--profile` so both spellings match one lookup. */
function flagName(token: string): string {
return token.split('=', 1)[0]
}
function isLauncherToken(token: string): boolean {
return LAUNCHER_FLAGS.has(token) || LAUNCHER_FLAGS_WITH_VALUE.has(flagName(token))
}
/**
* Whether a `dsh` command line runs something other than the interactive agent.
*
* Only the launcher's own tokens are read. Everything after the first token the launcher
* does not recognize belongs to the booted app (`dsh --profile dsh-tui --resume <id>`),
* and a prompt or session id is free text that must never be read as a launcher flag.
*/
export function isDshNonInteractiveCommand(tokens: readonly string[]): boolean {
const firstProgram = programBasename(tokens[0])
const entrypoint = findInterpreterEntrypointToken([...tokens], firstProgram)
const programIndex = entrypoint === null ? 0 : tokens.indexOf(entrypoint)
if (TUI_LAUNCHER_NAMES.has(programBasename(tokens[programIndex]))) {
return false
}
const indexOfArgs = programIndex + 1
let index = indexOfArgs
let profile: string | null = null
const first = tokens[index]
if (first !== undefined && !first.startsWith('-')) {
if (SUBCOMMANDS.has(first)) {
return true
}
profile = first
index += 1
}
for (; index < tokens.length; index += 1) {
const token = tokens[index]
if (DUMP_FLAGS.has(token)) {
return true
}
if (!isLauncherToken(token)) {
break
}
const explicitProfile = readProfileName(tokens, index)
if (explicitProfile !== null && profile === null) {
profile = explicitProfile
}
if (LAUNCHER_FLAGS_WITH_VALUE.has(token)) {
index += 1
}
}
return profile !== null && NON_INTERACTIVE_PROFILES.has(profile)
}