feat: implement Monitor tool for streaming shell output#649
Conversation
Add the Monitor tool that executes shell commands in the background and streams stdout line-by-line as notifications to the model. This enables real-time monitoring of logs, builds, and long-running processes. Implementation: - MonitorTool (src/tools/MonitorTool/) — spawns LocalShellTask with kind='monitor', returns immediately with task ID - MonitorMcpTask (src/tasks/MonitorMcpTask/) — task lifecycle management and agent cleanup via killMonitorMcpTasksForAgent() - MonitorPermissionRequest — permission dialog component The codebase already had all integration points wired (tools.ts, tasks.ts, PermissionRequest.tsx, LocalShellTask kind='monitor', BashTool prompt). This PR provides the missing implementations.
- MonitorPermissionRequest: "don't ask again" now creates a command-prefix rule (like BashTool) instead of a blanket tool-name-only rule that would auto-allow all Monitor commands - MonitorMcpTask: clarify architecture comments explaining why monitor_mcp type exists as a registry stub while actual tasks are local_bash with kind='monitor'
There was a problem hiding this comment.
Pull request overview
Adds a new Monitor tool to run shell commands asynchronously while streaming stdout back to the model via periodic task-output notifications, filling the gap between blocking Bash and completion-only background tasks.
Changes:
- Introduces
MonitorToolthat spawnsLocalShellTaskjobs withkind: 'monitor'and returns{ taskId, outputFile }immediately. - Registers a forward-compatible
monitor_mcptask type and adds agent-exit cleanup for monitor-related tasks. - Adds a dedicated permission dialog for Monitor and enables the
MONITOR_TOOLfeature flag in the open build.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| src/tools/MonitorTool/MonitorTool.ts | Implements the Monitor tool entrypoint that spawns background shell tasks in monitor mode and maps the immediate result payload. |
| src/tasks/MonitorMcpTask/MonitorMcpTask.ts | Adds the monitor_mcp task registry entry plus agent-scoped cleanup for monitor-related tasks. |
| src/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.tsx | Adds the Monitor-specific permission prompt UI and “don’t ask again” flow. |
| scripts/build.ts | Enables the MONITOR_TOOL feature flag for the open build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Fix permission rule field: expression → ruleContent (Copilot #1) - Handle empty command prefix: skip rule creation (Copilot Gitlawb#2) - Remove unused useTheme() import (Copilot Gitlawb#3) - Save permission rules under 'Bash' toolName so bashToolHasPermission can match them — Monitor delegates to Bash permission system (Copilot Gitlawb#4) - Remove unused logError import from MonitorMcpTask (Copilot Gitlawb#6) - Copilot Gitlawb#5 (getAppState throws): same pattern as BashTool:915, not a bug
With KAIROS=true, MONITOR_TOOL=true, and COORDINATOR_MODE=true, the build system's auto-generated noop stubs are truthy — causing runtime crashes when included in tool/task arrays. Add proper null-export stubs so conditional checks (?:) correctly exclude them. Stubs added: - MonitorTool, MonitorMcpTask, MonitorMcpDetailDialog, MonitorPermissionRequest (will be replaced by PR Gitlawb#649's full implementation) - SendUserFileTool, PushNotificationTool (internal tools, no open-build impl) - coordinator/workerAgent (will be replaced by PR Gitlawb#647's implementation)
…nt (Gitlawb#647) Replace null stubs with full implementations from PRs Gitlawb#649 and Gitlawb#647: - MonitorTool: streaming shell output tool with bash permission delegation, background task spawning, and 30min timeout (PR Gitlawb#649) - MonitorMcpTask: task registry entry with agent-scoped cleanup for monitor-kind shell tasks (PR Gitlawb#649) - MonitorPermissionRequest: permission UI with "don't ask again" support, delegates rules to Bash toolName for correct matching (PR Gitlawb#649) - workerAgent: WORKER_AGENT definition + getCoordinatorAgents() returning worker, general-purpose, explore, and plan agents (PR Gitlawb#647) MonitorMcpDetailDialog remains a null stub (not in PR Gitlawb#649's scope).
Vasanthdev2004
left a comment
There was a problem hiding this comment.
Review: Monitor tool for streaming shell output
Reviewed on head a9c7206. CI green ✅. 4 files, +471/-1.
This is a clean implementation of the Monitor tool — fills the last missing tool slot from the source snapshot. The architecture is sound: it correctly reuses the existing LocalShellTask infrastructure with kind: 'monitor' for streaming notifications, delegates permissions to bashToolHasPermission, and registers the forward-compatible monitor_mcp task type.
✅ Architecture
The tool sits correctly between synchronous Bash and fire-and-forget run_in_background:
| Mode | Blocking | Notifications |
|---|---|---|
Bash |
Yes | Immediate result |
Bash(run_in_background) |
No | 1 notification at completion |
Monitor |
No | Streaming (~1s polling) |
All pre-existing integration points are properly filled: tools.ts import, tasks.ts import, PermissionRequest.tsx mapping, LocalShellTask monitor-kind handling, BashTool/prompt.ts conditional guidance, Task.ts type definition.
✅ Permission system
checkPermissionscorrectly delegates tobashToolHasPermission— Monitor runs shell commands, so the same rules apply- "Don't ask again" correctly saves rules under
toolName: 'Bash'(not'Monitor'), becausebashToolHasPermissionchecks rules againstBashTool. Saving under'Monitor'would create a dead rule that never matches. The code comment explains this clearly. - Prefix extraction uses first 2 tokens →
${prefix}:*format, matching BashTool'sshellRuleMatchingpattern - Empty/whitespace commands skip rule creation entirely (empty array passed to
onAllow) — prevents wildcard-everything rules
✅ Self-review fixes (commit 2)
All 6 Copilot issues addressed in a9c7206:
expression→ruleContent(correct schema field) ✅- Empty command guard (skip rule creation) ✅
toolName: 'Bash'for permission rules ✅- Removed unused
useTheme()✅ - Removed unused
logErrorimport ✅ getAppStatethrowing stub — kept for consistency with BashTool pattern ✅
✅ Task cleanup
killMonitorMcpTasksForAgent provides belt-and-suspenders cleanup: kills both monitor_mcp-typed tasks AND local_bash tasks with kind='monitor'. The existing killShellTasksForAgent already handles the latter, but being explicit guards against ordering issues. dequeueAllMatching purges queued notifications for the dead agent.
✅ Monitor-specific behavior in LocalShellTask
- Stall watchdog disabled for
kind: 'monitor'— correct, monitor tasks are expected to run indefinitely - Distinct notification summaries:
"Monitor X stream ended"vs"background command completed"— prevents confusion - 30-minute timeout is reasonable for monitor tasks
🟡 Non-blocking observation: test coverage
The PR has no test files. The tool is mostly wiring (delegating to existing infrastructure), so the blast radius of bugs is limited. The permission logic and task cleanup paths are the most testable. This is acceptable for a tool that fills a pre-wired slot, but a follow-up adding tests for MonitorPermissionRequest's rule creation and killMonitorMcpTasksForAgent's cleanup would be valuable.
Verdict: Approve-ready ✅
Clean implementation, addresses all Copilot review feedback, follows established patterns, security properties correct. Kevin already approved. No blockers.
* feat: implement Monitor tool for streaming shell output Add the Monitor tool that executes shell commands in the background and streams stdout line-by-line as notifications to the model. This enables real-time monitoring of logs, builds, and long-running processes. Implementation: - MonitorTool (src/tools/MonitorTool/) — spawns LocalShellTask with kind='monitor', returns immediately with task ID - MonitorMcpTask (src/tasks/MonitorMcpTask/) — task lifecycle management and agent cleanup via killMonitorMcpTasksForAgent() - MonitorPermissionRequest — permission dialog component The codebase already had all integration points wired (tools.ts, tasks.ts, PermissionRequest.tsx, LocalShellTask kind='monitor', BashTool prompt). This PR provides the missing implementations. * fix: command-specific permission rule + architecture docs - MonitorPermissionRequest: "don't ask again" now creates a command-prefix rule (like BashTool) instead of a blanket tool-name-only rule that would auto-allow all Monitor commands - MonitorMcpTask: clarify architecture comments explaining why monitor_mcp type exists as a registry stub while actual tasks are local_bash with kind='monitor' * fix: address Copilot review feedback - Fix permission rule field: expression → ruleContent (Copilot #1) - Handle empty command prefix: skip rule creation (Copilot #2) - Remove unused useTheme() import (Copilot #3) - Save permission rules under 'Bash' toolName so bashToolHasPermission can match them — Monitor delegates to Bash permission system (Copilot #4) - Remove unused logError import from MonitorMcpTask (Copilot Gitlawb#6) - Copilot #5 (getAppState throws): same pattern as BashTool:915, not a bug
Summary
Implement the Monitor tool — a new tool that executes shell commands in the background and streams stdout line-by-line as notifications to the model. This is the last missing tool implementation from the source snapshot.
What Monitor does
Monitor fills the gap between synchronous Bash and fire-and-forget
run_in_background:BashBash(run_in_background)MonitorUse cases: watching logs (
tail -f), monitoring builds, observing dev server startup.Implementation (3 new files + 1 flag flip)
src/tools/MonitorTool/MonitorTool.ts(195 lines){ command, description }LocalShellTaskwithkind: 'monitor'viaexec()+spawnShellTask(){ taskId, outputFile }bashToolHasPermissionsrc/tasks/MonitorMcpTask/MonitorMcpTask.ts(99 lines)monitor_mcptask type in the task registry (satisfies import fromtasks.ts)killMonitorMcpTasksForAgent()for agent cleanup (called fromrunAgent.ts:866)local_bashtasks withkind='monitor'; themonitor_mcptype exists for forward-compatibility with MCP-based monitoringsrc/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.tsx(170 lines)scripts/build.ts—MONITOR_TOOL: truePre-existing infrastructure used
The codebase already had all integration points wired:
src/tools.ts:39-41— MonitorTool import slotsrc/tasks.ts:12-14— MonitorMcpTask import slotsrc/components/permissions/PermissionRequest.tsx:40-41,73-74— permission mappingsrc/tasks/LocalShellTask/LocalShellTask.tsx:47—kind: 'monitor'(stall watchdog disabled)src/tasks/LocalShellTask/LocalShellTask.tsx:129-143— monitor-specific notificationssrc/tools/BashTool/prompt.ts— conditional Monitor guidance textsrc/Task.ts:12—'monitor_mcp'in TaskTypeSelf-review fixes (commit 2)
Test plan
bun run build— compiles successfullybun run smoke— smoke tests passMonitor({ command: "echo hello && sleep 1 && echo world", description: "test" })— verify streaming notificationsTaskStopcan kill a running monitor