diff --git a/src/headless-ui.ts b/src/headless-ui.ts index 7beea6bef..e99d94755 100644 --- a/src/headless-ui.ts +++ b/src/headless-ui.ts @@ -82,9 +82,37 @@ export function formatProgress(event: Record, verbose: boolean) const type = String(event.type ?? '') switch (type) { - case 'tool_execution_start': - if (verbose) return ` [tool] ${event.toolName ?? 'unknown'}` + case 'tool_execution_start': { + const name = String(event.toolName ?? 'unknown') + const summary = summarizeToolArgs(name, event.args as Record | undefined) + return summary ? ` [tool] ${name} ${summary}` : ` [tool] ${name}` + } + + case 'tool_execution_end': { + if (verbose) { + const name = String(event.toolName ?? 'unknown') + const isError = Boolean(event.isError) + return isError ? ` [tool] ${name} ✗ error` : null + } + // In non-verbose, only surface errors + if (event.isError) { + const name = String(event.toolName ?? 'unknown') + return ` [tool] ${name} ✗ error` + } return null + } + + case 'cost_update': { + const cumCost = event.cumulativeCost as Record | undefined + const costUsd = Number(cumCost?.costUsd ?? 0) + if (costUsd > 0) { + const tokens = event.tokens as Record | undefined + const inK = tokens ? (tokens.input / 1000).toFixed(1) : '?' + const outK = tokens ? (tokens.output / 1000).toFixed(1) : '?' + return ` [cost] $${costUsd.toFixed(4)} (${inK}k in / ${outK}k out)` + } + return null + } case 'agent_start': return '[agent] Session started' @@ -94,11 +122,10 @@ export function formatProgress(event: Record, verbose: boolean) case 'extension_ui_request': if (event.method === 'notify') { - return `[gsd] ${event.message ?? ''}` - } - if (event.method === 'setStatus') { - return `[status] ${event.message ?? ''}` + const msg = String(event.message ?? '') + return msg ? `[gsd] ${msg}` : null } + // setStatus / setWidget are TUI-specific — suppress in text mode return null default: @@ -106,6 +133,43 @@ export function formatProgress(event: Record, verbose: boolean) } } +/** + * Extract a short summary from tool arguments for display. + * Returns null if nothing useful can be summarized. + */ +function summarizeToolArgs(toolName: string, args: Record | undefined): string | null { + if (!args) return null + + switch (toolName) { + case 'Read': + case 'read': + return args.path ? String(args.path) : null + case 'Write': + case 'write': + return args.path ? String(args.path) : null + case 'Edit': + case 'edit': + return args.path ? String(args.path) : null + case 'Bash': + case 'bash': { + const cmd = String(args.command ?? '') + return cmd.length > 80 ? cmd.slice(0, 77) + '...' : cmd || null + } + case 'Grep': + case 'grep': + return args.pattern ? `/${args.pattern}/` + (args.path ? ` in ${args.path}` : '') : null + case 'find': + return args.pattern ? String(args.pattern) + (args.path ? ` in ${args.path}` : '') : null + case 'lsp': + return args.action ? String(args.action) + (args.symbol ? ` ${args.symbol}` : '') : null + default: { + // For GSD tools, show the first string arg that looks like an ID or path + const first = Object.values(args).find(v => typeof v === 'string' && String(v).length < 80) + return first ? String(first) : null + } + } +} + // --------------------------------------------------------------------------- // Supervised Stdin Reader // --------------------------------------------------------------------------- diff --git a/src/headless.ts b/src/headless.ts index 4fe480501..5e54cac64 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -344,6 +344,8 @@ async function runHeadlessOnce(options: HeadlessOptions, restartCount: number): if (injector) { clientOptions.env = injector.getSecretEnvVars() } + // Signal headless mode to the GSD extension (skips UAT human pause, etc.) + clientOptions.env = { ...(clientOptions.env as Record || {}), GSD_HEADLESS: '1' } // Propagate --bare to the child process if (options.bare) { clientOptions.args = [...((clientOptions.args as string[]) || []), '--bare'] diff --git a/src/resources/extensions/gsd/auto-dispatch.ts b/src/resources/extensions/gsd/auto-dispatch.ts index d6ba2424f..59fb2ac19 100644 --- a/src/resources/extensions/gsd/auto-dispatch.ts +++ b/src/resources/extensions/gsd/auto-dispatch.ts @@ -200,7 +200,7 @@ export const DISPATCH_RULES: DispatchRule[] = [ uatContent ?? "", basePath, ), - pauseAfterDispatch: uatType !== "artifact-driven" && uatType !== "browser-executable" && uatType !== "runtime-executable", + pauseAfterDispatch: !process.env.GSD_HEADLESS && uatType !== "artifact-driven" && uatType !== "browser-executable" && uatType !== "runtime-executable", }; }, },