| 1 | package builtin |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "fmt" |
| 6 | "time" |
| 7 | ) |
| 8 | |
| 9 | // FileOverlay lets a host transport (an ACP client editor, say) serve file |
| 10 | // content instead of the local disk, so tools see unsaved editor buffers. A |
| 11 | // nil overlay or an ok=false answer falls back to direct disk I/O; the overlay |
| 12 | // is consulted only after the tool's own path resolution and confinement |
| 13 | // checks, so it never widens what a tool may touch. |
| 14 | type FileOverlay interface { |
| 15 | // ReadTextFile returns the current text of path as the host sees it |
| 16 | // (including unsaved changes). ok=false means the host cannot serve this |
| 17 | // path and the caller should read the local disk instead. |
| 18 | ReadTextFile(ctx context.Context, path string) (content string, ok bool) |
| 19 | // WriteTextFile asks the host to write content to path (updating any open |
| 20 | // buffer as well as the file). ok=false means the host cannot handle the |
| 21 | // write and the caller should write the local disk instead; err is only |
| 22 | // meaningful when ok is true. |
| 23 | WriteTextFile(ctx context.Context, path, content string) (ok bool, err error) |
| 24 | } |
| 25 | |
| 26 | // TerminalRunner lets a host transport run a foreground shell command in a |
| 27 | // host-owned terminal (the ACP terminal/* methods, say) so the user watches it |
| 28 | // live. ok=false means the host cannot run it and the caller should execute |
| 29 | // locally; err is only meaningful when ok is true. Runners are only consulted |
| 30 | // when the local OS sandbox is not enforcing — a host terminal cannot honor |
| 31 | // the local confinement configuration. |
| 32 | // |
| 33 | // envOverrides, when non-nil, is a small map of environment variables the host |
| 34 | // terminal should set for the command (typically TMPDIR/TMP/TEMP for the |
| 35 | // session-private temporary directory). Callers must not pass a full host |
| 36 | // environment dump — only the overrides Reasonix owns. |
| 37 | // |
| 38 | // Prefer typed outcomes when possible: |
| 39 | // - TerminalExitError for a non-zero process exit (Code is the real exit code) |
| 40 | // - TerminalTimeoutError when the host-enforced timeout fired |
| 41 | // - context.Canceled / context.DeadlineExceeded for parent cancellation |
| 42 | // |
| 43 | // Plain fmt.Errorf strings remain accepted for older host runners. |
| 44 | type TerminalRunner interface { |
| 45 | RunCommand(ctx context.Context, command, cwd string, timeout time.Duration, envOverrides map[string]string) (output string, ok bool, err error) |
| 46 | } |
| 47 | |
| 48 | // TerminalExitError is returned by a host TerminalRunner when the command ran |
| 49 | // and produced a non-zero exit status. bash.ExecuteDetailed preserves Code on |
| 50 | // ShellExecution.ExitCode. |
| 51 | type TerminalExitError struct { |
| 52 | Code int |
| 53 | } |
| 54 | |
| 55 | func (e TerminalExitError) Error() string { |
| 56 | return fmt.Sprintf("exit status %d", e.Code) |
| 57 | } |
| 58 | |
| 59 | // TerminalTimeoutError is returned when the host terminal killed the command |
| 60 | // after the tool-local timeout. bash.ExecuteDetailed maps this to |
| 61 | // state=timed_out / failurePhase=timeout. |
| 62 | type TerminalTimeoutError struct { |
| 63 | Timeout time.Duration |
| 64 | } |
| 65 | |
| 66 | func (e TerminalTimeoutError) Error() string { |
| 67 | if e.Timeout > 0 { |
| 68 | return fmt.Sprintf("command timed out after %s (terminal killed)", e.Timeout) |
| 69 | } |
| 70 | return "command timed out (terminal killed)" |
| 71 | } |
| 72 |