diff --git a/apps/cli/bin/app.js b/apps/cli/bin/app.js index 457f2ab7..cdc12ed6 100755 --- a/apps/cli/bin/app.js +++ b/apps/cli/bin/app.js @@ -4,28 +4,29 @@ import { hideBin } from 'yargs/helpers'; import { app } from '../dist/app.js'; yargs(hideBin(process.argv)) + .command( "$0", "Run rowboatx", (y) => y - .option("agent", { - type: "string", - description: "The agent to run", - default: "copilot", - }) - .option("run_id", { - type: "string", - description: "Continue an existing run", - }) - .option("input", { - type: "string", - description: "The input to the agent", - }) - .option("no-interactive", { - type: "boolean", - description: "Do not interact with the user", - default: false, - }), + .option("agent", { + type: "string", + description: "The agent to run", + default: "copilot", + }) + .option("run_id", { + type: "string", + description: "Continue an existing run", + }) + .option("input", { + type: "string", + description: "The input to the agent", + }) + .option("no-interactive", { + type: "boolean", + description: "Do not interact with the user", + default: false, + }), (argv) => { app({ agent: argv.agent, @@ -35,4 +36,20 @@ yargs(hideBin(process.argv)) }); } ) + .command( + "update-state ", + "Update state for a run", + (y) => y + .positional("agent", { + type: "string", + description: "The agent to run", + }) + .positional("run_id", { + type: "string", + description: "The run id to update", + }), + (argv) => { + updateState(argv.agent, argv.run_id); + } + ) .parse(); \ No newline at end of file diff --git a/apps/cli/src/app.ts b/apps/cli/src/app.ts index 5d2be860..ece4c792 100644 --- a/apps/cli/src/app.ts +++ b/apps/cli/src/app.ts @@ -9,6 +9,27 @@ import { createInterface, Interface } from "node:readline/promises"; import { ToolCallPart } from "./application/entities/message.js"; import { z } from "zod"; +export async function updateState(agent: string, runId: string) { + const state = new AgentState(agent, runId); + // If running in a TTY, read run events from stdin line-by-line + if (!input.isTTY) { + return; + } + + const rl = createInterface({ input, crlfDelay: Infinity }); + try { + for await (const line of rl) { + if (line.trim() === "") { + continue; + } + const event = RunEvent.parse(JSON.parse(line)); + state.ingestAndLog(event); + } + } finally { + rl.close(); + } +} + export async function app(opts: { agent: string; runId?: string; @@ -16,7 +37,7 @@ export async function app(opts: { noInteractive?: boolean; }) { const renderer = new StreamRenderer(); - const state = new AgentState(opts.agent); + const state = new AgentState(opts.agent, opts.runId); // load existing and assemble state if required let runId = opts.runId; @@ -45,11 +66,15 @@ export async function app(opts: { if (!opts.noInteractive) { rl = createInterface({ input, output }); } + let inputConsumed = false; try { while (true) { // ask for pending tool permissions for (const perm of Object.values(state.getPendingPermissions())) { + if (opts.noInteractive) { + return; + } const response = await getToolCallPermission(perm.toolCall, rl!); state.ingestAndLog({ type: "tool-permission-response", @@ -61,6 +86,9 @@ export async function app(opts: { // ask for pending human input for (const ask of Object.values(state.getPendingAskHumans())) { + if (opts.noInteractive) { + return; + } const response = await getAskHumanResponse(ask.query, rl!); state.ingestAndLog({ type: "ask-human-response", @@ -80,6 +108,21 @@ export async function app(opts: { // if nothing pending, get user input if (state.getPendingPermissions().length === 0 && state.getPendingAskHumans().length === 0) { + if (opts.input && !inputConsumed) { + state.ingestAndLog({ + type: "message", + message: { + role: "user", + content: opts.input, + }, + subflow: [], + }); + inputConsumed = true; + continue; + } + if (opts.noInteractive) { + return; + } const response = await getUserInput(rl!); state.ingestAndLog({ type: "message",