From 3f8b85cbafba365b67c7d32fe444d8cdd1922e77 Mon Sep 17 00:00:00 2001 From: Waishnav <86405648+Waishnav@users.noreply.github.com> Date: Mon, 31 Aug 2026 03:46:18 +0530 Subject: [PATCH] feat(agents): emit compact XML fragments by default --- src/cli.test.ts | 35 +++++++++++++++- src/cli.ts | 71 ++++++++++++++++++++++----------- src/local-agent-presentation.ts | 57 +++++++++++++++++++------- 3 files changed, 124 insertions(+), 39 deletions(-) diff --git a/src/cli.test.ts b/src/cli.test.ts index 0983417c3..2caa5ae2a 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -150,7 +150,10 @@ try { }, }); - assert.equal(output.trim(), `${current.id} completed reviewer`); + assert.equal( + output.trim(), + ``, + ); const { stdout: jsonOutput } = await execFileAsync( "node", @@ -217,6 +220,31 @@ try { assert.equal(payload.error.retryable, false); assert.equal(payload.error.target, "missing"); + let xmlCommandFailure: unknown; + try { + await execFileAsync( + "node", + ["--import", "tsx", "src/cli.ts", "agents", "run", "missing", "inspect"], + { + cwd: process.cwd(), + encoding: "utf8", + env: { + ...process.env, + ...cliConfigEnv, + DEVSPACE_WORKSPACE_ID: "ws_current", + DEVSPACE_WORKSPACE_ROOT: projectRoot, + }, + }, + ); + } catch (error) { + xmlCommandFailure = error; + } + assert.ok(xmlCommandFailure, "XML CLI errors should exit non-zero"); + assert.equal( + (xmlCommandFailure as { stderr?: string }).stderr, + 'Unknown subagent profile or provider: missing.\n', + ); + await assert.rejects( execFileAsync( "node", @@ -243,7 +271,10 @@ try { }, ), (error: unknown) => { - assert.match((error as { stderr?: string }).stderr ?? "", /Unknown option: --unknown/); + assert.equal( + (error as { stderr?: string }).stderr, + 'Unknown option: --unknown. Use -- before prompt text that starts with a dash.\n', + ); return true; }, ); diff --git a/src/cli.ts b/src/cli.ts index b521556a3..d18b488cc 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -25,6 +25,7 @@ import { import { createLocalAgentClient } from "./local-agent-client.js"; import { toAgentErrorPayload, type LocalAgentError } from "./local-agent-errors.js"; import { + formatAgentCommandError, formatAgentObservation, formatAgentReceipt, formatAgentSummary, @@ -447,19 +448,19 @@ async function runAgentsCommand(args: string[]): Promise { switch (subcommand) { case "ls": case "list": - await runAgentsList(commandArgs, json); + await runAgentWorkflowCommand(json, () => runAgentsList(commandArgs, json)); return; case "run": - await runAgentsRun(commandArgs, json); + await runAgentWorkflowCommand(json, () => runAgentsRun(commandArgs, json)); return; case "continue": - await runAgentsContinue(commandArgs, json); + await runAgentWorkflowCommand(json, () => runAgentsContinue(commandArgs, json)); return; case "show": - await runAgentsShow(commandArgs, json); + await runAgentWorkflowCommand(json, () => runAgentsShow(commandArgs, json)); return; case "targets": - await runAgentsTargets(commandArgs, json); + await runAgentWorkflowCommand(json, () => runAgentsTargets(commandArgs, json)); return; case "daemon": await runAgentsDaemon(commandArgs, json); @@ -471,7 +472,7 @@ async function runAgentsCommand(args: string[]): Promise { printAgentsHelp(); return; default: - throw new Error(`Unknown agents command: ${subcommand}`); + writeAgentWorkflowError(`Unknown agents command: ${subcommand}`, json); } } @@ -487,7 +488,7 @@ async function runAgentsTargets(args: string[], json: boolean): Promise { const catalog = buildLocalAgentCatalog(config.subagents, profiles, providers); const output = presentAgentTargetCatalog(catalog); if (json) printJson(output); - else console.log(formatAgentTargetCatalog(output)); + else printAgentXml(formatAgentTargetCatalog(output)); } async function runAgentsList(args: string[], json: boolean): Promise { @@ -495,7 +496,7 @@ async function runAgentsList(args: string[], json: boolean): Promise { const config = loadConfig(); const client = createLocalAgentClient(config); const result = await client.list(resolveCliWorkspaceContext(config.allowedRoots)); - const agents = presentAgentResult(result, json); + const agents = presentAgentWorkflowResult(result, json); if (!agents) return; const summaries = agents.map(presentAgentSummary); @@ -504,14 +505,7 @@ async function runAgentsList(args: string[], json: boolean): Promise { return; } - if (agents.length === 0) { - console.log("No subagent sessions found for this workspace."); - return; - } - - for (const summary of summaries) { - console.log(formatAgentSummary(summary)); - } + printAgentXml(summaries.map(formatAgentSummary).join("\n")); } async function runAgentsRun(args: string[], json: boolean): Promise { @@ -527,14 +521,14 @@ async function runAgentsRun(args: string[], json: boolean): Promise { model: parsed.model, effort: parsed.effort, }); - const record = presentAgentResult(result, json); + const record = presentAgentWorkflowResult(result, json); if (!record) return; const receipt = presentAgentReceipt(record); if (json) { printJson(receipt); return; } - console.log(formatAgentReceipt(receipt)); + printAgentXml(formatAgentReceipt(receipt)); } async function runAgentsContinue(args: string[], json: boolean): Promise { @@ -546,14 +540,14 @@ async function runAgentsContinue(args: string[], json: boolean): Promise { model: parsed.model, effort: parsed.effort, }, scope); - const record = presentAgentResult(result, json); + const record = presentAgentWorkflowResult(result, json); if (!record) return; const receipt = presentAgentReceipt(record); if (json) { printJson(receipt); return; } - console.log(formatAgentReceipt(receipt)); + printAgentXml(formatAgentReceipt(receipt)); } async function runAgentsShow(args: string[], json: boolean): Promise { @@ -564,20 +558,20 @@ async function runAgentsShow(args: string[], json: boolean): Promise { const client = createLocalAgentClient(config); const scope = resolveCliWorkspaceContext(config.allowedRoots); const initial = await client.get(id, scope); - let record = presentAgentResult(initial, json); + let record = presentAgentWorkflowResult(initial, json); if (!record) return; const deadline = Date.now() + 15_000; while ((record.status === "starting" || record.status === "running") && Date.now() < deadline) { await sleep(500); - const refreshed = presentAgentResult(await client.get(id, scope), json); + const refreshed = presentAgentWorkflowResult(await client.get(id, scope), json); if (!refreshed) return; record = refreshed; } const observation = presentAgentObservation(record); if (json) printJson(observation); - else console.log(formatAgentObservation(observation)); + else printAgentXml(formatAgentObservation(observation)); } async function runAgentsDaemon(args: string[], json: boolean): Promise { @@ -643,6 +637,37 @@ function presentAgentResult( throw new Error(result.error.message); } +function presentAgentWorkflowResult( + result: BetterResult, + json: boolean, +): T | undefined { + if (result.isOk()) return result.value; + const error = toAgentErrorPayload(result.error); + if (json) printJson({ error }); + else console.error(formatAgentCommandError(error)); + process.exitCode = 1; + return undefined; +} + +async function runAgentWorkflowCommand(json: boolean, command: () => Promise): Promise { + try { + await command(); + } catch (error) { + writeAgentWorkflowError(error instanceof Error ? error.message : String(error), json); + } +} + +function writeAgentWorkflowError(message: string, json: boolean): void { + const error = { code: "AGENT_COMMAND_ERROR", message, retryable: false }; + if (json) printJson({ error }); + else console.error(formatAgentCommandError(error)); + process.exitCode = 1; +} + +function printAgentXml(fragment: string): void { + if (fragment) console.log(fragment); +} + function printJson(value: unknown): void { console.log(JSON.stringify(value)); } diff --git a/src/local-agent-presentation.ts b/src/local-agent-presentation.ts index 21916afa5..9def4d39f 100644 --- a/src/local-agent-presentation.ts +++ b/src/local-agent-presentation.ts @@ -38,6 +38,13 @@ export interface AgentFailureOutput { retryable: boolean; } +export interface AgentCommandErrorOutput { + code: string; + message: string; + retryable?: boolean; + agentId?: string; +} + export type AgentObservationOutput = | { id: string; status: "running" } | { id: string; status: "completed"; response?: string } @@ -98,37 +105,59 @@ export function presentAgentObservation(record: LocalAgentRecord): AgentObservat } export function formatAgentTargetCatalog(catalog: AgentTargetCatalogOutput): string { - if (catalog.targets.length === 0) return "No usable subagent targets."; return catalog.targets.map((target) => { - const settings = [ - target.model ? `model=${target.model}` : undefined, - target.effort ? `effort=${target.effort}` : undefined, - ].filter(Boolean).join(" "); + const settings = xmlAttributes({ model: target.model, effort: target.effort }); if (target.kind === "provider") { - return `${target.name} [provider]${settings ? ` ${settings}` : ""}`; + return ``; } - return `${target.name} [profile, ${target.provider}]${settings ? ` ${settings}` : ""} - ${target.description}`; + return `${escapeXmlText(target.description)}`; }).join("\n"); } export function formatAgentReceipt(receipt: AgentReceiptOutput): string { - return `${receipt.id} ${receipt.status}`; + return ``; } export function formatAgentSummary(summary: AgentSummaryOutput): string { - return `${formatAgentReceipt(summary)} ${summary.target}`; + return ``; } export function formatAgentObservation(observation: AgentObservationOutput): string { - const line = formatAgentReceipt(observation); if (observation.status === "completed" && observation.response !== undefined) { - return `${line}\n\n${observation.response}`; + return `${escapeXmlText(observation.response)}`; } if ((observation.status === "failed" || observation.status === "stopped") && observation.error) { - const retryable = observation.error.retryable ? " [retryable]" : ""; - return `${line} ${observation.error.code}: ${observation.error.message}${retryable}`; + return `${escapeXmlText(observation.error.message)}`; } - return line; + return formatAgentReceipt(observation); +} + +export function formatAgentCommandError(error: AgentCommandErrorOutput): string { + const agentId = error.agentId ? ` agent-id="${escapeXmlAttribute(error.agentId)}"` : ""; + return `${escapeXmlText(error.message)}`; +} + +function xmlAttributes(values: Record): string { + return Object.entries(values) + .filter((entry): entry is [string, string] => entry[1] !== undefined) + .map(([name, value]) => ` ${name}="${escapeXmlAttribute(value)}"`) + .join(""); +} + +function escapeXmlAttribute(value: string): string { + return escapeXml(value).replaceAll('"', """).replaceAll("'", "'"); +} + +function escapeXmlText(value: string): string { + return escapeXml(value); +} + +function escapeXml(value: string): string { + return value + .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]/g, "\uFFFD") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">"); } function presentAgentStatus(status: LocalAgentStatus): AgentCommandStatus {