# 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](#phase-2-subprocess-observability) below. ## SubProcessService [Source](../src/services/subprocess.ts) `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` | Spawns a child process, starts writing stdout/stderr to log files under `/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` | Kills a specific subprocess by PID and removes its log files. | | `killProject` | `(projectId: GadgetId) => Promise` | Kills all subprocesses for a given project. | | `killSubProcess` | `(sp: SubProcess) => Promise` | Kills a subprocess and removes its log files. | | `killAll` | `() => Promise` | Kills all managed subprocesses and removes all log files. | | `haltPid` | `(pid: number) => Promise` | Stops a subprocess by PID but **retains its log files** for inspection. | | `haltSubProcess` | `(sp: SubProcess) => Promise` | Stops a subprocess but retains its log files. | | `getLog` | `(pid: number, stream: "stdout" \| "stderr") => Promise` | 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: 1. Send `SIGINT` to the process. 2. Wait up to 5 seconds for the process to exit. 3. If still alive, send `SIGKILL`. 4. Wait up to another 5 seconds. 5. Remove stdout/stderr listeners. 6. Close log file streams. For `kill`, log files are deleted after termination. For `halt`, log files are preserved. ## SubprocessTool [Source](../src/tools/system/subprocess.ts) 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 `/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 --- ``` ## 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](../../gadget-code/data/prompts/common/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:** ```typescript type RequestProcessStatsCallback = ( success: boolean, data?: { processes?: SubProcessStat[]; message?: string } ) => void; ``` ### `SubProcessStat` Type Defined in `packages/api/src/messages/subprocess.ts`: ```typescript 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`: 1. Calls `SubProcessService.ps()` to get all tracked subprocesses 2. For each, calls `summarize()` for serializable fields 3. Determines status: checks `sp.process.exitCode !== null || sp.process.killed` 4. Extracts `command` and `args` from `sp.process.spawnargs` 5. 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: 1. **IDE sends** `subscribeDrone(registrationId)` → Backend adds IDE's socket to a monitor set 2. **IDE sends** `unsubscribeDrone(registrationId)` → Backend removes from monitor set 3. **Drone emits** `log` → `DroneSession.onLog()` forwards to chat session AND broadcasts `drone:log` to all monitoring sockets 4. **Frontend** receives `drone:log` events and renders via `LogRenderer` The monitor tracking lives in `SocketService.droneMonitorIndex` — a `Map>`. 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