Implement background command support for bash tool

- Added `background` option to `bash` tool to allow long-lived commands to run without blocking the turn.
- Introduced `bash_status` tool to check the status and output of background commands.
- Added `bash_stop` tool to explicitly stop running background commands.
- Updated command parsing to handle `/bash` commands for listing, stopping individual, and stopping all background commands.
- Enhanced session management to reap stale background commands on agent shutdown.
- Updated UI to reflect background command status and allow user interruption via ctrl-c.
- Added tests for background command functionality and command parsing.
- Documented new features and usage in background-bash.md.
This commit is contained in:
asepharyana committed 2026-09-11 18:52:50 +07:00
1 parent deeb138cd9
commit 76043aeb38
12 files changed
+724 -18

No files matched your search

+14
View File
@@ -29,6 +29,7 @@ export type CommandAction =
| { type: 'undo' }
| { type: 'redo' }
| { type: 'changes' }
| { type: 'bash'; action: 'list' | 'stop' | 'stop-all'; arg?: string }
| { type: 'search'; query: string }
| { type: 'fork' }
| { type: 'workflow' }
@@ -70,6 +71,7 @@ export const COMMANDS: CommandSpec[] = [
{ name: 'undo', summary: 'undo the last turn — restores files and conversation (bash effects are not snapshotted)' },
{ name: 'redo', summary: 'redo the last undone turn' },
{ name: 'changes', summary: 'show what the last turn changed on disk' },
{ name: 'bash', arg: '[list|stop <id>|stop all]', summary: 'list or stop background commands started with bash background: true' },
{ name: 'search', arg: '<query>', summary: 'search saved sessions for a phrase' },
{ name: 'fork', summary: 'fork the session at the last turn boundary (keeps the original)' },
{ name: 'workflow', summary: 'show project workflow state: TODO/ROADMAP tracking, docs, nudges' },
@@ -243,6 +245,18 @@ export function parseCommand(raw: string, custom: readonly CustomCommand[] = [])
return { type: 'redo' };
case 'changes':
return { type: 'changes' };
case 'bash': {
const [verb = '', ...rest] = arg.split(/\s+/);
if (verb === 'stop') {
if (rest.join(' ').trim().toLowerCase() === 'all') return { type: 'bash', action: 'stop-all' };
const id = Number(rest[0]);
return Number.isInteger(id) && id > 0
? { type: 'bash', action: 'stop', arg: String(id) }
: { type: 'info', text: 'usage: /bash stop <id> or /bash stop all' };
}
if (verb && verb !== 'list') return { type: 'info', text: 'usage: /bash [list|stop <id>|stop all]' };
return { type: 'bash', action: 'list' };
}
case 'search':
return arg ? { type: 'search', query: arg } : { type: 'info', text: 'usage: /search <query>' };
case 'fork':