feat: background process monitoring — watch_patterns for real-time output alerts

* feat: add watch_patterns to background processes for output monitoring

Adds a new 'watch_patterns' parameter to terminal(background=true) that
lets the agent specify strings to watch for in process output. When a
matching line appears, a notification is queued and injected as a
synthetic message — triggering a new agent turn, similar to
notify_on_complete but mid-process.

Implementation:
- ProcessSession gets watch_patterns field + rate-limit state
- _check_watch_patterns() in ProcessRegistry scans new output chunks
  from all three reader threads (local, PTY, env-poller)
- Rate limited: max 8 notifications per 10s window
- Sustained overload (45s) permanently disables watching for that process
- watch_queue alongside completion_queue, same consumption pattern
- CLI drains watch_queue in both idle loop and post-turn drain
- Gateway drains after agent runs via _inject_watch_notification()
- Checkpoint persistence + crash recovery includes watch_patterns
- Blocked in execute_code sandbox (like other bg params)
- 20 new tests covering matching, rate limiting, overload kill,
  checkpoint persistence, schema, and handler passthrough

Usage:
  terminal(
      command='npm run dev',
      background=true,
      watch_patterns=['ERROR', 'WARN', 'listening on port']
  )

* refactor: merge watch_queue into completion_queue

Unified queue with 'type' field distinguishing 'completion',
'watch_match', and 'watch_disabled' events. Extracted
_format_process_notification() in CLI and gateway to handle
all event types in a single drain loop. Removes duplication
across both CLI drain sites and the gateway.
This commit is contained in:
Teknium 2026-04-11 03:13:23 -07:00 committed by GitHub
parent a2f9f04c06
commit f459214010
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 556 additions and 35 deletions

View file

@ -42,7 +42,7 @@ import atexit
import shutil
import subprocess
from pathlib import Path
from typing import Optional, Dict, Any
from typing import Optional, Dict, Any, List
logger = logging.getLogger(__name__)
@ -1140,6 +1140,7 @@ def terminal_tool(
check_interval: Optional[int] = None,
pty: bool = False,
notify_on_complete: bool = False,
watch_patterns: Optional[List[str]] = None,
) -> str:
"""
Execute a command in the configured terminal environment.
@ -1154,6 +1155,7 @@ def terminal_tool(
check_interval: Seconds between auto-checks for background processes (gateway only, min 30)
pty: If True, use pseudo-terminal for interactive CLI tools (local backend only)
notify_on_complete: If True and background=True, auto-notify the agent when the process exits
watch_patterns: List of strings to watch for in background output; triggers notification on match
Returns:
str: JSON string with output, exit_code, and error fields
@ -1439,6 +1441,11 @@ def terminal_tool(
"notify_on_complete": True,
})
# Set watch patterns for output monitoring
if watch_patterns and background:
proc_session.watch_patterns = list(watch_patterns)
result_data["watch_patterns"] = proc_session.watch_patterns
# Register check_interval watcher (gateway picks this up after agent run)
if check_interval and background:
effective_interval = max(30, check_interval)
@ -1762,6 +1769,11 @@ TERMINAL_SCHEMA = {
"type": "boolean",
"description": "When true (and background=true), you'll be automatically notified when the process finishes — no polling needed. Use this for tasks that take a while (tests, builds, deployments) so you can keep working on other things in the meantime.",
"default": False
},
"watch_patterns": {
"type": "array",
"items": {"type": "string"},
"description": "List of strings to watch for in background process output. When any pattern matches a line of output, you'll be notified with the matching text — like notify_on_complete but triggers mid-process on specific output. Use for monitoring logs, watching for errors, or waiting for specific events (e.g. [\"ERROR\", \"FAIL\", \"listening on port\"])."
}
},
"required": ["command"]
@ -1779,6 +1791,7 @@ def _handle_terminal(args, **kw):
check_interval=args.get("check_interval"),
pty=args.get("pty", False),
notify_on_complete=args.get("notify_on_complete", False),
watch_patterns=args.get("watch_patterns"),
)