Adds real-time subprocess monitoring to the IDE via the callback-chain and subscribe/broadcast socket patterns: Shared types (packages/api): - New subprocess.ts message types: SubProcessStat, RequestProcessStatsMessage, SubscribeDroneMessage, UnsubscribeDroneMessage - New socket events in socket.ts: requestProcessStats, subscribeDrone, unsubscribeDrone (ClientToServer); drone:log, drone:status (ServerToClient) Drone (gadget-drone): - onRequestProcessStats handler calls SubProcessService.ps() + summarize(), returns typed SubProcessStat[] with status detection (exitCode/killed) Backend routing (gadget-code): - SocketService: getDroneSessionByRegistrationId(), droneMonitorIndex map, addDroneMonitor()/removeDroneMonitor()/broadcastToMonitors() - CodeSession: requestProcessStats proxy, subscribeDrone/unsubscribeDrone - DroneSession: broadcast drone:log + drone:status to monitor subscribers in addition to existing chat-session routing Frontend socket layer: - SocketClient: requestProcessStats(), subscribeDrone(), unsubscribeDrone() - drone:log + drone:status events forwarded to event bus Frontend components: - LogRenderer — reusable log rendering core extracted from LogPanel - SubProcessTable — btop-style process table with status dots - DroneMonitorGauge — canvas oscilloscope waveform (studio-equipment aesthetic) - DroneMonitor — 3-gauge container (CPU/red, NETWORK/cyan, FILE I/O/green) Frontend pages: - DroneManager: live log streaming, SubProcessTable, DroneMonitor gauges, 1-second polling, auto-scroll log with pause-on-scroll-up - Home/DroneInspector: process count summary, mini LogRenderer, navigate-to-manager link Documentation: - Updated gadget-drone/docs/subprocess.md Phase 2 section - Updated gadget-code/docs/ui-design-guide.md Phase 2 section - Updated docs/socket-protocol.md with new events, sequences, and patterns
9.4 KiB
Gadget Drone Sub-Processes
The Gadget Code agent manages child processes through gadget-drone's SubProcess system. In the Gadget ecosystem, we refer to a managed child process spawned by gadget-drone as a SubProcess.
Phase Status
- Phase 1 (Complete): SubProcessService + SubprocessTool internal to drone.
- Phase 2 (Complete): SubProcess observability in DroneManager and DroneInspector. Live log streaming via subscribe/broadcast pattern. Canvas-based resource gauges (CPU, Network, File I/O — mocked). See Phase 2 Details below.
SubProcessService
SubProcessService is a singleton service that manages child processes on behalf of Gadget Drone. It is started and stopped as part of the standard service lifecycle in gadget-drone.ts. On stop, all managed subprocesses are killed (SIGINT with graceful timeout, falling back to SIGKILL) and their log files are removed.
API
| Method | Signature | Description |
|---|---|---|
create |
(project: IProject, cmd: string, args?: string[]) => Promise<SubProcess> |
Spawns a child process, starts writing stdout/stderr to log files under <gadgetDir>/subprocess-logs/. |
ps |
() => SubProcess[] |
Returns an array of all managed subprocesses. |
summarize |
(sp: SubProcess) => SubProcessSummary |
Returns a serializable summary (without ChildProcess/WriteStream refs). |
killPid |
(pid: number) => Promise<boolean> |
Kills a specific subprocess by PID and removes its log files. |
killProject |
(projectId: GadgetId) => Promise<void> |
Kills all subprocesses for a given project. |
killSubProcess |
(sp: SubProcess) => Promise<boolean> |
Kills a subprocess and removes its log files. |
killAll |
() => Promise<boolean> |
Kills all managed subprocesses and removes all log files. |
haltPid |
(pid: number) => Promise<boolean> |
Stops a subprocess by PID but retains its log files for inspection. |
haltSubProcess |
(sp: SubProcess) => Promise<boolean> |
Stops a subprocess but retains its log files. |
getLog |
(pid: number, stream: "stdout" | "stderr") => Promise<string> |
Reads the full content of a subprocess's stdout or stderr log file. |
Lifecycle
The service is registered in GadgetDrone.startServices() / stopServices(). During stop(), killAll() is called, ensuring no orphaned processes remain when the drone shuts down.
Process Termination
Termination follows a graceful escalation:
- Send
SIGINTto the process. - Wait up to 5 seconds for the process to exit.
- If still alive, send
SIGKILL. - Wait up to another 5 seconds.
- Remove stdout/stderr listeners.
- Close log file streams.
For kill, log files are deleted after termination. For halt, log files are preserved.
SubprocessTool
The subprocess tool is registered in the agent's toolbox for modes: Build, Test, Ship, Develop. It provides a single function with a cmd parameter that dispatches to the appropriate operation.
Tool Definition
Name: subprocess
Category: system
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
cmd |
string (enum) | yes | One of: create, list, kill, killAll, halt, log |
command |
string | for create |
The executable to spawn (e.g., node, npm, python) |
args |
string[] | for create |
Command-line arguments for the spawned process |
pid |
number | for kill, halt, log |
The managed process ID |
which |
string (enum) | for log |
"stdout" or "stderr" |
Commands
create
Spawns a new child process in the project directory. Logs stdout and stderr to <gadgetDir>/subprocess-logs/.
Response:
SUBPROCESS CREATED
pid: 12345
stdout: /path/to/.gadget/subprocess-logs/subproc-12345.stdout.log
stderr: /path/to/.gadget/subprocess-logs/subproc-12345.stderr.log
list
Returns all currently managed subprocesses with their PIDs, project slugs, and timestamps.
Response:
SUBPROCESS LIST
pid: 12345 | project: my-project | created: ... | updated: ...
kill
Terminates a subprocess and removes its log files. Uses SIGINT with graceful escalation.
Response:
SUBPROCESS KILLED
pid: 12345
killAll
Terminates all managed subprocesses and removes all log files.
Response:
SUBPROCESS KILLALL
All managed subprocesses have been terminated and their logs removed.
halt
Terminates a subprocess but retains its log files. Useful for inspecting logs after process exit.
Response:
SUBPROCESS HALTED
pid: 12345
The process has been stopped. Log files are retained for inspection.
log
Reads stdout or stderr output from a subprocess's log file.
Response:
SUBPROCESS LOG (stdout)
pid: 12345
---
<log content>
Integration Points
System Prompts
All agent modes include a Process Management block in their system prompt. The block is loaded from process-management-block.md and rendered via {{process_management_block}} in the mode templates. This tells the agent about the subprocess tool and how to use it.
Shutdown
gadget-drone.ts calls SubProcessService.stop() during shutdown, which triggers killAll() to clean up any remaining child processes.
Tests
| File | What it covers |
|---|---|
src/services/subprocess.test.ts |
SubProcessService: create, ps, killPid, killAll, haltPid, getLog, error cases |
src/tools/system/subprocess.test.ts |
SubprocessTool: all 6 commands, parameter validation, service error handling |
Phase 2: SubProcess Observability
Architecture
Phase 2 exposes the drone's managed subprocesses to the frontend via a callback-chain pattern over Socket.IO, identical to the existing fileTreeRequest/fileReadRequest flow:
IDE (DroneManager.tsx)
──emit("requestProcessStats", registrationId, cb)──▶
│
CodeSession.onRequestProcessStats()
──droneSession.socket.emit("requestProcessStats", cb)──▶
│
GadgetDrone.onRequestProcessStats()
│ SubProcessService.ps()
│ .summarize() each
│ → SubProcessStat[]
│
◀──cb(true, { processes })──
◀──ack callback fires──
◀──cb(success, data)──
Socket Protocol
| Direction | Event | Signature |
|---|---|---|
| IDE → Backend | requestProcessStats |
(registrationId: string, cb: RequestProcessStatsCallback) => void |
| Backend → Drone | requestProcessStats |
(cb: RequestProcessStatsCallback) => void (no registrationId — drone identifies itself) |
Callback type:
type RequestProcessStatsCallback = (
success: boolean,
data?: { processes?: SubProcessStat[]; message?: string }
) => void;
SubProcessStat Type
Defined in packages/api/src/messages/subprocess.ts:
interface SubProcessStat {
pid: number;
command: string;
args: string[];
projectId: GadgetId;
projectSlug: string;
projectName: string;
status: "running" | "stopped" | "error";
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
stdoutFileName: string;
stderrFileName: string;
}
Handler Behavior
The onRequestProcessStats handler in gadget-drone.ts:
- Calls
SubProcessService.ps()to get all tracked subprocesses - For each, calls
summarize()for serializable fields - Determines status: checks
sp.process.exitCode !== null || sp.process.killed - Extracts
commandandargsfromsp.process.spawnargs - Returns
cb(true, { processes })on success,cb(false, { message })on error
Live Log Streaming (Subscribe/Broadcast)
In addition to request/response stats, the drone continuously emits log events through its GadgetLogTransportSocket. Phase 2 adds a monitor broadcast path:
- IDE sends
subscribeDrone(registrationId)→ Backend adds IDE's socket to a monitor set - IDE sends
unsubscribeDrone(registrationId)→ Backend removes from monitor set - Drone emits
log→DroneSession.onLog()forwards to chat session AND broadcastsdrone:logto all monitoring sockets - Frontend receives
drone:logevents and renders viaLogRenderer
The monitor tracking lives in SocketService.droneMonitorIndex — a Map<registrationId, Set<socketId>>. Cleanup happens automatically on socket disconnect.
Polling Interval
- DroneManager: 1-second interval polling
requestProcessStats - DroneInspector (dashboard): 1-second interval polling
- Resource gauges (CPU, Network, File I/O): 200ms internal animation (mocked data)
- All timers are killed on unmount or drone deselection