shell

package
v0.0.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package shell implements adapters.HarnessAdapter for plain shell / exec jobs that do not embed an agent harness. It is the fall-through adapter the runner-inner-child uses when JobSpec.HarnessType resolves to "shell" — non-agent hook scripts (e2e fixture kits, custom user hooks without an `agent:` declaration) and `boid exec`-style argv launches.

Phase 3-d (session 概念 + shell adapter 1 等市民化) introduced this adapter so every job — agent or not — flows through the same Run() pipeline in the runner-inner-child. The legacy `runExecArgv` branch was retired in the same change; HarnessType is invariant non-empty from PR1 onward.

shell adapter is intentionally minimal:

  • no session resolution (session-id resume is gone repo-wide)
  • no payload_patch.json writes (the hook script is responsible if it wants one — broker job-done still flows through PayloadPatchPath)
  • no token accounting (Usage() returns zero — shell jobs are not billable in Phase 4)
  • no Bindings() (Phase 3-c claude / codex / opencode each declared their own CLI bindings; shell relies on the base mount set the dispatcher applies for every sandbox).

Signal handling is shared with the agent adapters via sigutil.ForwardAndWait: SIGUSR1 → child SIGTERM (out-of-band daemon stop), SIGWINCH passthrough (PTY resize for `boid agent shell` interactive sessions), and stop-signal exit normalisation (143 → 0 with StoppedByDaemon=true). Hook / exec callers observe no behaviour change because the daemon never sends SIGUSR1 to those runtimes — the forwarding loop simply idles until cmd.Wait() returns.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Adapter

type Adapter struct{}

Adapter implements adapters.HarnessAdapter for plain shell jobs.

func New

func New() *Adapter

New returns a new Adapter.

func (*Adapter) Bindings

func (a *Adapter) Bindings(_ string) []adapters.BindMount

Bindings returns nil — shell adapter inherits the base mount set without adding any harness-specific binds.

func (*Adapter) Run

Run forks the argv supplied via RunContext.Argv and drives it through the shared sigutil.ForwardAndWait loop so every harness adapter (claude / codex / opencode / shell) reaches the daemon's "stop this job" out-of-band signal the same way.

I/O resolution precedence (StdinBytes / StdoutCaptureFile / Stdin / Stdout) mirrors the retired runExecArgv path the shell adapter replaced in PR1:

  1. StdinBytes non-empty → pipe a *bytes.Reader as child stdin (host file descriptor is not exposed to the child).
  2. StdoutCaptureFile non-empty → create that host path and route stdout into it (broker job-done reads from this file).
  3. Otherwise → pass RunContext.Stdin / Stdout verbatim.

Stderr always flows through RunContext.Stderr.

Signal handling: we run sigutil.ForwardAndWait but deliberately do NOT set Setsid. The claude / codex / opencode adapters do set Setsid because they need to intercept the daemon's runtime-pgrp SIGUSR1 broadcast and translate it into a SIGTERM the agent CLI can drain (jsonl flush, etc). The shell adapter cannot follow suit: PTY-attached hook scripts in the E2E suite (hook-attach-smoke) open /dev/tty directly, and Setsid detaches the child from the controlling terminal — the open() then fails with ENXIO and breaks the hook before it can speak. The shrunken byte-equivalent runExecArgv path PR1 retired never set Setsid for the same reason.

Trade-off without Setsid: if `boid agent shell` ever exposes a SIGUSR1- based daemon stop to an interactive bash session, the SIGUSR1 will also reach bash directly (bash's default disposition is "terminate"). The race is harmless in practice because sigutil's exit-code normalisation maps any stop-signal exit to 0 with StoppedByDaemon=true regardless of whether bash died from our forwarded SIGTERM or from the racing SIGUSR1; the boid side reads "session paused" the same way. Hook / exec runtimes observe no behaviour change either way — the daemon never sends SIGUSR1 to them so the forwarding loop simply idles until cmd.Wait() returns.

func (*Adapter) Usage

func (a *Adapter) Usage(_ context.Context, _ string) (adapters.Usage, error)

Usage returns a zero Usage; shell jobs are not accounted in Phase 4.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL