mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(coding-agent): emit bash_execution_update events (#6971)
This commit is contained in:
parent
7af8533c6f
commit
fc85bdd88b
5 changed files with 68 additions and 9 deletions
|
|
@ -23,7 +23,7 @@ Common options:
|
|||
- **Responses**: JSON objects with `type: "response"` indicating command success/failure
|
||||
- **Events**: Agent events streamed to stdout as JSON lines
|
||||
|
||||
All commands support an optional `id` field for request/response correlation. If provided, the corresponding response will include the same `id`.
|
||||
All commands support an optional `id` field for request/response correlation. If provided, the corresponding response will include the same `id`. `bash_execution_update` events also include the `id` of their originating `bash` command.
|
||||
|
||||
### Framing
|
||||
|
||||
|
|
@ -455,15 +455,18 @@ Response:
|
|||
|
||||
#### bash
|
||||
|
||||
Execute a shell command and add output to conversation context.
|
||||
Execute a shell command and add output to conversation context. Output streams as `bash_execution_update` events while the command runs; the response contains the final result.
|
||||
|
||||
```json
|
||||
{"type": "bash", "command": "ls -la"}
|
||||
{"id": "req-1", "type": "bash", "command": "ls -la"}
|
||||
```
|
||||
|
||||
Include an `id` to associate streamed `bash_execution_update` events with this command.
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"id": "req-1",
|
||||
"type": "response",
|
||||
"command": "bash",
|
||||
"success": true,
|
||||
|
|
@ -494,7 +497,7 @@ If output was truncated, includes `fullOutputPath`:
|
|||
|
||||
**How bash results reach the LLM:**
|
||||
|
||||
The `bash` command executes immediately and returns a `BashResult`. Internally, a `BashExecutionMessage` is created and stored in the agent's message state. This message does NOT emit an event.
|
||||
The `bash` command executes immediately and returns a `BashResult`. Internally, a `BashExecutionMessage` is created and stored in the agent's message state.
|
||||
|
||||
When the next `prompt` command is sent, all messages (including `BashExecutionMessage`) are transformed before being sent to the LLM. The `BashExecutionMessage` is converted to a `UserMessage` with this format:
|
||||
|
||||
|
|
@ -509,7 +512,6 @@ drwxr-xr-x ...
|
|||
This means:
|
||||
1. Bash output is included in the LLM context on the **next prompt**, not immediately
|
||||
2. Multiple bash commands can be executed before a prompt; all outputs will be included
|
||||
3. No event is emitted for the `BashExecutionMessage` itself
|
||||
|
||||
#### abort_bash
|
||||
|
||||
|
|
@ -829,7 +831,7 @@ Each command has:
|
|||
|
||||
## Events
|
||||
|
||||
Events are streamed to stdout as JSON lines during agent operation. Events do NOT include an `id` field (only responses do).
|
||||
Events are streamed to stdout as JSON lines during agent operation. Events do not generally include an `id` field; `bash_execution_update` includes the `id` of its originating `bash` command when one was provided.
|
||||
|
||||
### Event Types
|
||||
|
||||
|
|
@ -843,6 +845,7 @@ Events are streamed to stdout as JSON lines during agent operation. Events do NO
|
|||
| `message_start` | Message begins |
|
||||
| `message_update` | Streaming update (text/thinking/toolcall deltas) |
|
||||
| `message_end` | Message completes |
|
||||
| `bash_execution_update` | Direct RPC bash command output chunk |
|
||||
| `tool_execution_start` | Tool begins execution |
|
||||
| `tool_execution_update` | Tool execution progress (streaming output) |
|
||||
| `tool_execution_end` | Tool completes |
|
||||
|
|
@ -951,6 +954,20 @@ Example streaming a text response:
|
|||
{"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Hello world","partial":{...}}}
|
||||
```
|
||||
|
||||
### bash_execution_update
|
||||
|
||||
Emitted once for each output chunk from a direct `bash` command. `id` matches the command's `id`, allowing clients to associate output with the correct command.
|
||||
|
||||
Events stream all output while the command runs, even if the final `bash` response's `output` is truncated.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "bash_execution_update",
|
||||
"id": "req-1",
|
||||
"delta": "total 48\n"
|
||||
}
|
||||
```
|
||||
|
||||
### tool_execution_start / tool_execution_update / tool_execution_end
|
||||
|
||||
Emitted when a tool begins, streams progress, and completes execution.
|
||||
|
|
|
|||
|
|
@ -176,7 +176,9 @@ export type AgentSessionEvent =
|
|||
source: "compaction";
|
||||
reason: "manual" | "threshold" | "overflow";
|
||||
}
|
||||
| { type: "summarization_retry_finished" };
|
||||
| { type: "summarization_retry_finished" }
|
||||
| { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
|
||||
| { type: "bash_execution_update"; id?: string; delta: string };
|
||||
|
||||
/** Listener function for agent session events */
|
||||
export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
|
||||
|
|
@ -2760,12 +2762,13 @@ export class AgentSession {
|
|||
* @param command The bash command to execute
|
||||
* @param onChunk Optional streaming callback for output
|
||||
* @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)
|
||||
* @param options.id Optional identifier included in bash execution update events
|
||||
* @param options.operations Custom BashOperations for remote execution
|
||||
*/
|
||||
async executeBash(
|
||||
command: string,
|
||||
onChunk?: (chunk: string) => void,
|
||||
options?: { excludeFromContext?: boolean; operations?: BashOperations },
|
||||
options?: { excludeFromContext?: boolean; id?: string; operations?: BashOperations },
|
||||
): Promise<BashResult> {
|
||||
this._bashAbortController = new AbortController();
|
||||
|
||||
|
|
@ -2780,7 +2783,10 @@ export class AgentSession {
|
|||
this.sessionManager.getCwd(),
|
||||
options?.operations ?? createLocalBashOperations({ shellPath }),
|
||||
{
|
||||
onChunk,
|
||||
onChunk: (delta) => {
|
||||
onChunk?.(delta);
|
||||
this._emit({ type: "bash_execution_update", id: options?.id, delta });
|
||||
},
|
||||
signal: this._bashAbortController.signal,
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2985,6 +2985,10 @@ export class InteractiveMode {
|
|||
this.ui.requestRender();
|
||||
break;
|
||||
|
||||
case "bash_execution_update":
|
||||
// The bash execution callback handles TUI output rendering.
|
||||
break;
|
||||
|
||||
case "tool_execution_start": {
|
||||
let component = this.pendingTools.get(event.toolCallId);
|
||||
if (!component) {
|
||||
|
|
|
|||
|
|
@ -558,6 +558,7 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
|
|||
case "bash": {
|
||||
const result = await session.executeBash(command.command, undefined, {
|
||||
excludeFromContext: command.excludeFromContext,
|
||||
id,
|
||||
});
|
||||
return success(id, "bash", result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,4 +239,35 @@ describe("AgentSession bash and persistence characterization", () => {
|
|||
expect(result.output).toContain("hello from custom ops");
|
||||
expect(harness.session.messages[harness.session.messages.length - 1]?.role).toBe("bashExecution");
|
||||
});
|
||||
|
||||
it("streams bash output to the callback and session events", async () => {
|
||||
const harness = await createHarness();
|
||||
harnesses.push(harness);
|
||||
const callbackDeltas: string[] = [];
|
||||
const eventUpdates: Array<{ id: string | undefined; delta: string }> = [];
|
||||
const unsubscribe = harness.session.subscribe((event) => {
|
||||
if (event.type === "bash_execution_update") {
|
||||
eventUpdates.push({ id: event.id, delta: event.delta });
|
||||
}
|
||||
});
|
||||
const operations: BashOperations = {
|
||||
exec: async (_command, _cwd, options) => {
|
||||
options.onData(Buffer.from("hello "));
|
||||
options.onData(Buffer.from("world"));
|
||||
return { exitCode: 0 };
|
||||
},
|
||||
};
|
||||
|
||||
await harness.session.executeBash("custom", (delta) => callbackDeltas.push(delta), {
|
||||
id: "bash-1",
|
||||
operations,
|
||||
});
|
||||
unsubscribe();
|
||||
|
||||
expect(callbackDeltas).toEqual(["hello ", "world"]);
|
||||
expect(eventUpdates).toEqual([
|
||||
{ id: "bash-1", delta: "hello " },
|
||||
{ id: "bash-1", delta: "world" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue