fix: support print/JSON mode in cli.js so subagents don't hang
cli.ts unconditionally entered InteractiveMode, ignoring --mode, -p, --no-session and other flags the subagent extension passes to child processes. The child would wait for TTY input that never arrives (stdin is "ignore"), causing the parent to hang forever on "working". Parse CLI args to detect print/subagent mode and route to runPrintMode() with proper session, model, extension, and system prompt handling. Closes #45 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
aea1f8a51b
commit
d4a46beef7
1 changed files with 118 additions and 3 deletions
121
src/cli.ts
121
src/cli.ts
|
|
@ -1,17 +1,64 @@
|
|||
import {
|
||||
AuthStorage,
|
||||
DefaultResourceLoader,
|
||||
ModelRegistry,
|
||||
SettingsManager,
|
||||
SessionManager,
|
||||
createAgentSession,
|
||||
InteractiveMode,
|
||||
runPrintMode,
|
||||
} from '@mariozechner/pi-coding-agent'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { agentDir, sessionsDir, authFilePath } from './app-paths.js'
|
||||
import { buildResourceLoader, initResources } from './resource-loader.js'
|
||||
import { initResources } from './resource-loader.js'
|
||||
import { ensureManagedTools } from './tool-bootstrap.js'
|
||||
import { loadStoredEnvKeys, runWizardIfNeeded } from './wizard.js'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Minimal CLI arg parser — detects print/subagent mode flags
|
||||
// ---------------------------------------------------------------------------
|
||||
interface CliFlags {
|
||||
mode?: 'text' | 'json' | 'rpc'
|
||||
print?: boolean
|
||||
noSession?: boolean
|
||||
model?: string
|
||||
extensions: string[]
|
||||
appendSystemPrompt?: string
|
||||
tools?: string[]
|
||||
messages: string[]
|
||||
}
|
||||
|
||||
function parseCliArgs(argv: string[]): CliFlags {
|
||||
const flags: CliFlags = { extensions: [], messages: [] }
|
||||
const args = argv.slice(2) // skip node + script
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg === '--mode' && i + 1 < args.length) {
|
||||
const m = args[++i]
|
||||
if (m === 'text' || m === 'json' || m === 'rpc') flags.mode = m
|
||||
} else if (arg === '--print' || arg === '-p') {
|
||||
flags.print = true
|
||||
} else if (arg === '--no-session') {
|
||||
flags.noSession = true
|
||||
} else if (arg === '--model' && i + 1 < args.length) {
|
||||
flags.model = args[++i]
|
||||
} else if (arg === '--extension' && i + 1 < args.length) {
|
||||
flags.extensions.push(args[++i])
|
||||
} else if (arg === '--append-system-prompt' && i + 1 < args.length) {
|
||||
flags.appendSystemPrompt = args[++i]
|
||||
} else if (arg === '--tools' && i + 1 < args.length) {
|
||||
flags.tools = args[++i].split(',')
|
||||
} else if (!arg.startsWith('--') && !arg.startsWith('-')) {
|
||||
flags.messages.push(arg)
|
||||
}
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
const cliFlags = parseCliArgs(process.argv)
|
||||
const isPrintMode = cliFlags.print || cliFlags.mode !== undefined
|
||||
|
||||
// Pi's tool bootstrap can mis-detect already-installed fd/rg on some systems
|
||||
// because spawnSync(..., ["--version"]) returns EPERM despite a zero exit code.
|
||||
// Provision local managed binaries first so Pi sees them without probing PATH.
|
||||
|
|
@ -19,7 +66,11 @@ ensureManagedTools(join(agentDir, 'bin'))
|
|||
|
||||
const authStorage = AuthStorage.create(authFilePath)
|
||||
loadStoredEnvKeys(authStorage)
|
||||
await runWizardIfNeeded(authStorage)
|
||||
|
||||
// Skip the setup wizard in print mode — it requires TTY interaction
|
||||
if (!isPrintMode) {
|
||||
await runWizardIfNeeded(authStorage)
|
||||
}
|
||||
|
||||
const modelRegistry = new ModelRegistry(authStorage)
|
||||
const settingsManager = SettingsManager.create(agentDir)
|
||||
|
|
@ -60,6 +111,70 @@ if (!settingsManager.getCollapseChangelog()) {
|
|||
settingsManager.setCollapseChangelog(true)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Print / subagent mode — single-shot execution, no TTY required
|
||||
// ---------------------------------------------------------------------------
|
||||
if (isPrintMode) {
|
||||
const sessionManager = cliFlags.noSession
|
||||
? SessionManager.inMemory()
|
||||
: SessionManager.create(process.cwd())
|
||||
|
||||
// Read --append-system-prompt file content (subagent writes agent system prompts to temp files)
|
||||
let appendSystemPrompt: string | undefined
|
||||
if (cliFlags.appendSystemPrompt) {
|
||||
try {
|
||||
appendSystemPrompt = readFileSync(cliFlags.appendSystemPrompt, 'utf-8')
|
||||
} catch {
|
||||
// If it's not a file path, treat it as literal text
|
||||
appendSystemPrompt = cliFlags.appendSystemPrompt
|
||||
}
|
||||
}
|
||||
|
||||
initResources(agentDir)
|
||||
const resourceLoader = new DefaultResourceLoader({
|
||||
agentDir,
|
||||
additionalExtensionPaths: cliFlags.extensions.length > 0 ? cliFlags.extensions : undefined,
|
||||
appendSystemPrompt,
|
||||
})
|
||||
await resourceLoader.reload()
|
||||
|
||||
const { session, extensionsResult } = await createAgentSession({
|
||||
authStorage,
|
||||
modelRegistry,
|
||||
settingsManager,
|
||||
sessionManager,
|
||||
resourceLoader,
|
||||
})
|
||||
|
||||
if (extensionsResult.errors.length > 0) {
|
||||
for (const err of extensionsResult.errors) {
|
||||
process.stderr.write(`[gsd] Extension load error: ${err.error}\n`)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply --model override if specified
|
||||
if (cliFlags.model) {
|
||||
const available = modelRegistry.getAvailable()
|
||||
const match =
|
||||
available.find((m) => m.id === cliFlags.model) ||
|
||||
available.find((m) => `${m.provider}/${m.id}` === cliFlags.model)
|
||||
if (match) {
|
||||
session.setModel(match)
|
||||
}
|
||||
}
|
||||
|
||||
const mode = cliFlags.mode || 'text'
|
||||
await runPrintMode(session, {
|
||||
mode: mode === 'rpc' ? 'json' : mode,
|
||||
messages: cliFlags.messages,
|
||||
})
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interactive mode — normal TTY session
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Per-directory session storage — same encoding as the upstream SDK so that
|
||||
// /resume only shows sessions from the current working directory.
|
||||
const cwd = process.cwd()
|
||||
|
|
@ -68,7 +183,7 @@ const projectSessionsDir = join(sessionsDir, safePath)
|
|||
const sessionManager = SessionManager.create(cwd, projectSessionsDir)
|
||||
|
||||
initResources(agentDir)
|
||||
const resourceLoader = buildResourceLoader(agentDir)
|
||||
const resourceLoader = new DefaultResourceLoader({ agentDir })
|
||||
await resourceLoader.reload()
|
||||
|
||||
const { session, extensionsResult } = await createAgentSession({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue