@mariozechner/pi-coding-agent
Version:
Coding agent CLI with read, bash, edit, write tools and session management
141 lines • 5.28 kB
JavaScript
/**
* Print mode (single-shot): Send prompts, output result, exit.
*
* Used for:
* - `pi -p "prompt"` - text output
* - `pi --mode json "prompt"` - JSON event stream
*/
import { flushRawStdout, writeRawStdout } from "../core/output-guard.js";
import { killTrackedDetachedChildren } from "../utils/shell.js";
/**
* Run in print (single-shot) mode.
* Sends prompts to the agent and outputs the result.
*/
export async function runPrintMode(runtimeHost, options) {
const { mode, messages = [], initialMessage, initialImages } = options;
let exitCode = 0;
let session = runtimeHost.session;
let unsubscribe;
let disposed = false;
const signalCleanupHandlers = [];
const disposeRuntime = async () => {
if (disposed)
return;
disposed = true;
unsubscribe?.();
await runtimeHost.dispose();
};
const registerSignalHandlers = () => {
const signals = ["SIGTERM"];
if (process.platform !== "win32") {
signals.push("SIGHUP");
}
for (const signal of signals) {
const handler = () => {
killTrackedDetachedChildren();
void disposeRuntime().finally(() => {
process.exit(signal === "SIGHUP" ? 129 : 143);
});
};
process.on(signal, handler);
signalCleanupHandlers.push(() => process.off(signal, handler));
}
};
registerSignalHandlers();
const rebindSession = async () => {
session = runtimeHost.session;
await session.bindExtensions({
commandContextActions: {
waitForIdle: () => session.agent.waitForIdle(),
newSession: async (newSessionOptions) => {
const result = await runtimeHost.newSession(newSessionOptions);
if (!result.cancelled) {
await rebindSession();
}
return result;
},
fork: async (entryId, forkOptions) => {
const result = await runtimeHost.fork(entryId, forkOptions);
if (!result.cancelled) {
await rebindSession();
}
return { cancelled: result.cancelled };
},
navigateTree: async (targetId, navigateOptions) => {
const result = await session.navigateTree(targetId, {
summarize: navigateOptions?.summarize,
customInstructions: navigateOptions?.customInstructions,
replaceInstructions: navigateOptions?.replaceInstructions,
label: navigateOptions?.label,
});
return { cancelled: result.cancelled };
},
switchSession: async (sessionPath) => {
const result = await runtimeHost.switchSession(sessionPath);
if (!result.cancelled) {
await rebindSession();
}
return result;
},
reload: async () => {
await session.reload();
},
},
onError: (err) => {
console.error(`Extension error (${err.extensionPath}): ${err.error}`);
},
});
unsubscribe?.();
unsubscribe = session.subscribe((event) => {
if (mode === "json") {
writeRawStdout(`${JSON.stringify(event)}\n`);
}
});
};
try {
if (mode === "json") {
const header = session.sessionManager.getHeader();
if (header) {
writeRawStdout(`${JSON.stringify(header)}\n`);
}
}
await rebindSession();
if (initialMessage) {
await session.prompt(initialMessage, { images: initialImages });
}
for (const message of messages) {
await session.prompt(message);
}
if (mode === "text") {
const state = session.state;
const lastMessage = state.messages[state.messages.length - 1];
if (lastMessage?.role === "assistant") {
const assistantMsg = lastMessage;
if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
console.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
exitCode = 1;
}
else {
for (const content of assistantMsg.content) {
if (content.type === "text") {
writeRawStdout(`${content.text}\n`);
}
}
}
}
}
return exitCode;
}
catch (error) {
console.error(error instanceof Error ? error.message : String(error));
return 1;
}
finally {
for (const cleanup of signalCleanupHandlers) {
cleanup();
}
await disposeRuntime();
await flushRawStdout();
}
}
//# sourceMappingURL=print-mode.js.map