mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 17:00:59 +00:00
Add bash mode for executing shell commands
- Add ! prefix in TUI editor to execute shell commands directly
- Output streams in real-time and is added to LLM context
- Supports multiline commands, cancellation (Escape), truncation
- Preview mode shows last 20 lines, Ctrl+O expands full output
- Commands persist in session history as bashExecution messages
- Add bash command to RPC mode via {type:'bash',command:'...'}
- Add RPC tests for bash command execution and context inclusion
- Update docs: rpc.md, session.md, README.md, CHANGELOG.md
Closes #112
Co-authored-by: Markus Ylisiurunen <markus.ylisiurunen@gmail.com>
This commit is contained in:
parent
1608da8770
commit
bd0d0676d4
13 changed files with 917 additions and 126 deletions
|
|
@ -54,6 +54,19 @@ Compact the conversation context to reduce token usage:
|
|||
|
||||
The `customInstructions` field is optional and allows you to guide what the summary should focus on.
|
||||
|
||||
#### Bash Message
|
||||
|
||||
Execute a shell command and add output to the LLM context (without triggering a prompt):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "bash",
|
||||
"command": "ls -la"
|
||||
}
|
||||
```
|
||||
|
||||
On success, emits a `bash_end` event with the `BashExecutionMessage`. The command output is automatically added to the conversation context, allowing subsequent prompts to reference it.
|
||||
|
||||
## Output Protocol
|
||||
|
||||
The agent emits JSON events to stdout, one per line. Events follow the `AgentEvent` type hierarchy.
|
||||
|
|
@ -72,6 +85,7 @@ The agent emits JSON events to stdout, one per line. Events follow the `AgentEve
|
|||
| `tool_execution_start` | Tool execution begins |
|
||||
| `tool_execution_end` | Tool execution completes |
|
||||
| `compaction` | Context was compacted (manual or auto) |
|
||||
| `bash_end` | User-initiated bash command completed |
|
||||
| `error` | An error occurred |
|
||||
|
||||
### Event Schemas
|
||||
|
|
@ -192,6 +206,28 @@ The `result` field contains either:
|
|||
- An `AgentToolResult` object with `content` and `details` fields
|
||||
- A string error message if `isError` is true
|
||||
|
||||
#### bash_end
|
||||
|
||||
Emitted when a user-initiated bash command (via `bash` input message) completes.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "bash_end",
|
||||
"message": {
|
||||
"role": "bashExecution",
|
||||
"command": "ls -la",
|
||||
"output": "total 48\ndrwxr-xr-x ...",
|
||||
"exitCode": 0,
|
||||
"cancelled": false,
|
||||
"truncated": false,
|
||||
"fullOutputPath": "/tmp/pi-bash-abc123.log", // Only present if output was truncated
|
||||
"timestamp": 1733234567890
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `message` is a `BashExecutionMessage` that has been added to the conversation context. See [BashExecutionMessage](#bashexecutionmessage) for the full schema.
|
||||
|
||||
#### error
|
||||
|
||||
Emitted when an error occurs during input processing.
|
||||
|
|
@ -307,6 +343,33 @@ type AppMessage =
|
|||
| CustomMessages[keyof CustomMessages];
|
||||
```
|
||||
|
||||
#### BashExecutionMessage
|
||||
|
||||
Defined in [`packages/coding-agent/src/messages.ts`](../src/messages.ts)
|
||||
|
||||
Custom message type for user-executed bash commands (via `!` in TUI or `bash` RPC command):
|
||||
|
||||
```typescript
|
||||
interface BashExecutionMessage {
|
||||
role: "bashExecution";
|
||||
command: string; // The command that was executed
|
||||
output: string; // Command output (truncated if large)
|
||||
exitCode: number | null; // Exit code, null if killed
|
||||
cancelled: boolean; // True if user cancelled with Escape
|
||||
truncated: boolean; // True if output was truncated
|
||||
fullOutputPath?: string; // Path to temp file with full output (if truncated)
|
||||
timestamp: number; // Unix timestamp in milliseconds
|
||||
}
|
||||
```
|
||||
|
||||
When sent to the LLM, this message is transformed into a user message with the format:
|
||||
```
|
||||
Ran `<command>`
|
||||
\`\`\`
|
||||
<output>
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### Content Types
|
||||
|
||||
#### TextContent
|
||||
|
|
@ -456,7 +519,7 @@ function handleEvent(event: any) {
|
|||
args: event.args
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (event.type === "tool_execution_end") {
|
||||
const toolCall = pendingTools.get(event.toolCallId);
|
||||
if (toolCall) {
|
||||
|
|
@ -467,7 +530,7 @@ function handleEvent(event: any) {
|
|||
result: event.result,
|
||||
isError: event.isError
|
||||
};
|
||||
|
||||
|
||||
// Format for display
|
||||
displayToolExecution(merged);
|
||||
pendingTools.delete(event.toolCallId);
|
||||
|
|
@ -497,16 +560,16 @@ function displayToolExecution(tool: {
|
|||
switch (tool.name) {
|
||||
case "bash":
|
||||
return `$ ${tool.args.command}\n${resultText}`;
|
||||
|
||||
|
||||
case "read":
|
||||
return `📄 ${tool.args.path}\n${resultText.slice(0, 500)}...`;
|
||||
|
||||
|
||||
case "write":
|
||||
return `✏️ Wrote ${tool.args.path}`;
|
||||
|
||||
|
||||
case "edit":
|
||||
return `✏️ Edited ${tool.args.path}`;
|
||||
|
||||
|
||||
default:
|
||||
return `🔧 ${tool.name}: ${resultText.slice(0, 200)}`;
|
||||
}
|
||||
|
|
@ -520,10 +583,10 @@ The `turn_end` event provides the assistant message and all tool results togethe
|
|||
```typescript
|
||||
if (event.type === "turn_end") {
|
||||
const { message, toolResults } = event;
|
||||
|
||||
|
||||
// Extract tool calls from assistant message
|
||||
const toolCalls = message.content.filter(c => c.type === "toolCall");
|
||||
|
||||
|
||||
// Match each tool call with its result by toolCallId
|
||||
for (const call of toolCalls) {
|
||||
const result = toolResults.find(r => r.toolCallId === call.id);
|
||||
|
|
@ -586,14 +649,14 @@ const agent = spawn("pi", ["--mode", "rpc", "--no-session"]);
|
|||
// Parse output events
|
||||
readline.createInterface({ input: agent.stdout }).on("line", (line) => {
|
||||
const event = JSON.parse(line);
|
||||
|
||||
|
||||
if (event.type === "message_update") {
|
||||
const { assistantMessageEvent } = event;
|
||||
if (assistantMessageEvent.type === "text_delta") {
|
||||
process.stdout.write(assistantMessageEvent.delta);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (event.type === "tool_execution_start") {
|
||||
console.log(`\n[Tool: ${event.toolName}]`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,11 @@ A message in the conversation. The `message` field contains an `AppMessage` (see
|
|||
{"type":"message","timestamp":"2024-12-03T14:00:01.000Z","message":{"role":"user","content":"Hello","timestamp":1733234567890}}
|
||||
{"type":"message","timestamp":"2024-12-03T14:00:02.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}],"api":"anthropic-messages","provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop","timestamp":1733234567891}}
|
||||
{"type":"message","timestamp":"2024-12-03T14:00:03.000Z","message":{"role":"toolResult","toolCallId":"call_123","toolName":"bash","content":[{"type":"text","text":"output"}],"isError":false,"timestamp":1733234567900}}
|
||||
{"type":"message","timestamp":"2024-12-03T14:00:04.000Z","message":{"role":"bashExecution","command":"ls -la","output":"total 48\n...","exitCode":0,"cancelled":false,"truncated":false,"timestamp":1733234567950}}
|
||||
```
|
||||
|
||||
The `bashExecution` role is a custom message type for user-executed bash commands (via `!` in TUI or `bash` RPC command). See [rpc.md](./rpc.md#bashexecutionmessage) for the full schema.
|
||||
|
||||
### ModelChangeEntry
|
||||
|
||||
Emitted when the user switches models mid-session.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue