feat: add adopt API for externally spawned child processes - #98
Conversation
|
Hey @masonc15 ! Thanks for this PR! Note that I'm moving some stuff around (see the open prs/stack) and I think this will get some conflicts. Could you explain in your words + with some real time examples how this would work? I'm not yet sure of the use case where this would be needed, in most of my use, Pi uses either bash or processes and rarely needs to have its process adopted? However, something I'm investigating is doing something like Codex and Claude Code (see #56 and #81) where it's a unified tool that always runs the process in the bg (codex) or an override on the Bash tool that optionally can be started in the background (Claude). Would either match what you have in mind? |
|
Hi @aliou, sorry if this created any noise for you - I was brainstorming a sort of extension of pi-processes that allowed for similar behavior to Claude Code where you can press ctrl-b to background any currently running job. I had left it in draft mode to sort of iterate on, sorry if it's in a bit of a "slop"py state as a PR. Even with pi-processes, I've run into a bunch of cases using pi where the first thing it does is a grep, find, mdfind, or something along those lines over a large root that blocks pi and takes over 200 seconds sometimes when the task itself should be finished much quicker. My ideal use case for this would be noticing a job like that, being able to press a key to have pi-processes adopt it, and then pi being able to continue and not be blocked. I think the Claude-style Bash override mentioned in #81 would probably cover this, but I would want to make sure it handles the case where a command is unpredictably slow and can be backgrounded. Having a I validated this branch with a small bash override extension I built. Press Looking at #81, adopt shouldn't be needed for that path since the manager should be aware of every command/process already. I'm happy to close this and contribute promotion logic to #81 if that would make more sense. Or if this adopt primitive still makes sense, seeing the conflicts with your #88 stack, I can wait until that lands and rebase after that. Just let me know, thanks! |
no worries, not at all!
I should have everything shipped this weekend, i'll ping you and you can rebase, and i'll play with your branch a bit to see how it behaves day to day :) Could you also share your bash tool override so I can see the full "workflow"? Thanks! |
|
@masonc15 Merged the full stack so feel free to go for it! |
6eef545 to
ebc09c8
Compare
|
@aliou Rebased onto the newly merged stack! I also added the full Bash tool override example. It keeps Bash in the foreground, shows the
|
|
@masonc15 thanks for this! Taking a deeper look today or tomorrow but looks good to me so far! |
Deduplicate the private copy from process-log-store and process-output into src/utils/buffer.ts. Add clampToTail and unit tests.
Add ProcessManager.adopt() so extensions can hand an already-running child to the manager. The child must be in a detached process group with piped stdio. Pre-handover output is split into initialStdout and initialStderr, each routed to the correct log file and combined.log stream tag, and clamped to MAX_TAIL_READ_BYTES (tail kept). The new processes:command:adopt event-bus channel exposes adopt cross-extension. Adopted processes get the full managed lifecycle: log capture, liveness watching, kill/stop, notifications, and dock visibility. Co-authored-by: Kaiabot <kaiabot@378labs.dev> Co-authored-by: Aliou Diallo <code@aliou.me>
Example extension that overrides the bash tool to move long-running commands to pi-processes via adopt. Uses separate OutputBuffer instances per stream and resolves the shell via getShellConfig instead of hardcoding /bin/bash (fixes NixOS). Co-authored-by: Kaiabot <kaiabot@378labs.dev> Co-authored-by: Aliou Diallo <code@aliou.me>
ebc09c8 to
53f8923
Compare
|
@masonc15 had the agents do some changes after some of my testing, just need to redo a last manual pass before releasing this, should be tonight! Thanks again for this! |
|
@masonc15 This should be available in a few minutes in v0.12.0! |

Motivation
Extensions that run foreground child processes (most notably bash-tool overrides that let a user move a slow command to the background instead of killing it, Claude Code ctrl+b style) need somewhere to put the still-running child. pi-processes already has everything such a process needs — log capture, liveness watching, kill/stop, notifications, dock and
/psvisibility — butProcessRuntimeController.start()is the only entry point, and it insists on spawning the child itself.This PR adds an adopt path so an already-running child can be handed over to the manager.
What's included
ProcessManager.adopt(name, command, cwd, child, opts?)— registers an externally spawnedChildProcessas a managed process.start()is refactored into spawn + sharedregister(); adopted and started processes go through identical registration, stdio wiring, and lifecycle handling.opts.initialOutput— output the adopter captured before handover, appended to the logs ahead of any future stdio so nothing is lost across the transition.opts.startTime— backdates the record to when the command actually began.processes:command:adopt(CommandAdoptPayload/CommandAdoptResult) so other extensions can adopt cross-extension. Payloads cross the bus by reference in-process, so the liveChildProcesshandle arrives intact. The handler registers default notification config, matching started processes.closeevent fired before adoption (streams destroyed), the record is finalized immediately with the correct exit classification; if the child exited but streams are still open, the pendingclosefinalizes it through the normal path.Contract
The child must be spawned the way
spawnCommandspawns: detached process group, piped stdio. This keeps group kill (killProcessGroup) and liveness polling (isProcessGroupAlive) working identically for adopted processes. Documented on the payload type and both adopt methods.Testing
src/manager/index.test.ts(running adoption, backdated startTime,process_startedemission, initialOutput ordering, exit detection, both already-exited variants, kill, missing-pid) and 1 inextensions/processes/handlers/commands.test.tsfor default notification registration.tests/e2e/adopt.e2e.tswith real detached children: output captured across handover (pre-handover marker viainitialOutput, post-handover marker via wired stdio), and process-group kill of an adopted child.pnpm typecheck,pnpm lint,pnpm test(650),pnpm test:e2e(12) all green.processes:command:adopt: user-triggered and timeout-triggered backgrounding both landed in the dock//ps, output was preserved across the handover, and exit notifications reached the agent.Notes