@madarco/agentbox 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_cloud-attach-T727ZPRV.js +13 -0
- package/dist/{chunk-NW5NYTQM.js → chunk-67N47KUS.js} +359 -85
- package/dist/chunk-67N47KUS.js.map +1 -0
- package/dist/{chunk-NAVL4R34.js → chunk-6OZDFNBF.js} +1084 -516
- package/dist/chunk-6OZDFNBF.js.map +1 -0
- package/dist/chunk-BGK32PZE.js +455 -0
- package/dist/chunk-BGK32PZE.js.map +1 -0
- package/dist/{chunk-7KOEFGN2.js → chunk-FODMEHD3.js} +52 -14
- package/dist/chunk-FODMEHD3.js.map +1 -0
- package/dist/{chunk-UK72UQ5U.js → chunk-G3H2L3O2.js} +55 -4
- package/dist/chunk-G3H2L3O2.js.map +1 -0
- package/dist/{chunk-V5KZGB5V.js → chunk-LEV3KICD.js} +18 -2
- package/dist/chunk-LEV3KICD.js.map +1 -0
- package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js → cloud-poller-SUNA6ZQC-2RG5WPRN.js} +2 -2
- package/dist/{dist-R67WMLCF.js → dist-L4LCG5SJ.js} +120 -10
- package/dist/dist-L4LCG5SJ.js.map +1 -0
- package/dist/{dist-ETCFRVPA.js → dist-LOZBWMBF.js} +44 -20
- package/dist/{dist-QZGJIBT5.js → dist-ZODPD2I6.js} +142 -74
- package/dist/dist-ZODPD2I6.js.map +1 -0
- package/dist/index.js +3563 -845
- package/dist/index.js.map +1 -1
- package/dist/prepared-state-CL4CWXQA-ME4HSKDE.js +18 -0
- package/dist/prepared-state-CL4CWXQA-ME4HSKDE.js.map +1 -0
- package/package.json +4 -4
- package/runtime/daytona/custom-system-CLAUDE.md +39 -0
- package/runtime/docker/Dockerfile.box +22 -0
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +1 -1
- package/runtime/docker/packages/ctl/dist/bin.cjs +1118 -71
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +66 -35
- package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +62 -1
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +15 -4
- package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +263 -0
- package/runtime/docker/packages/sandbox-docker/scripts/git-shim +131 -0
- package/runtime/docker/packages/sandbox-docker/scripts/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/agentbox-codex-hooks.json +66 -35
- package/runtime/hetzner/agentbox-setup-skill.md +1 -1
- package/runtime/hetzner/claude-managed-settings.json +62 -1
- package/runtime/hetzner/ctl.cjs +1118 -71
- package/runtime/hetzner/custom-system-CLAUDE.md +26 -14
- package/runtime/hetzner/gh-shim +263 -0
- package/runtime/hetzner/git-shim +131 -0
- package/runtime/hetzner/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/scripts/install-box.sh +11 -2
- package/runtime/relay/bin.cjs +927 -36
- package/share/agentbox-setup/SKILL.md +1 -1
- package/share/host-skills/agentbox/SKILL.md +29 -0
- package/share/host-skills/agentbox-info/SKILL.md +211 -0
- package/share/host-skills/codex/agentbox.md +35 -0
- package/share/host-skills/opencode/agentbox.md +26 -0
- package/dist/_cloud-attach-DMVH6GWO.js +0 -12
- package/dist/chunk-7KOEFGN2.js.map +0 -1
- package/dist/chunk-NAVL4R34.js.map +0 -1
- package/dist/chunk-NW5NYTQM.js.map +0 -1
- package/dist/chunk-UK72UQ5U.js.map +0 -1
- package/dist/chunk-V5KZGB5V.js.map +0 -1
- package/dist/dist-QZGJIBT5.js.map +0 -1
- package/dist/dist-R67WMLCF.js.map +0 -1
- /package/dist/{_cloud-attach-DMVH6GWO.js.map → _cloud-attach-T727ZPRV.js.map} +0 -0
- /package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
- /package/dist/{dist-ETCFRVPA.js.map → dist-LOZBWMBF.js.map} +0 -0
|
@@ -14,7 +14,7 @@ Run `agentbox checkpoint --set-default` (similar to `docker commit`) to save any
|
|
|
14
14
|
|
|
15
15
|
Some special folders:
|
|
16
16
|
|
|
17
|
-
- **Host main repo's `.git/`** — If the box bind-mounted RW at its identical absolute host path. In-box commits land on the host's branch refs (visible to `git log` on the host immediately); the box itself carries no SSH/git creds, so `git push` goes through the host relay (`agentbox-ctl git push`). The host's **working tree is never written to** — only refs/objects under `.git/`.
|
|
17
|
+
- **Host main repo's `.git/`** — If the box bind-mounted RW at its identical absolute host path. In-box commits land on the host's branch refs (visible to `git log` on the host immediately); the box itself carries no SSH/git creds, so `git push` goes through the host relay (`agentbox-ctl git push`). The host's **working tree is never written to** — only refs/objects under `.git/`. GitHub PR ops (`agentbox-ctl git pr create|view|list|comment|review|merge|close|reopen|checkout`) flow the same way through host `gh`; write ops require host confirmation (deny → exit 10), `merge` and `checkout` have additional opt-in guards.
|
|
18
18
|
- **`~/.claude`** — and similar home folders for coding agents are seeded from the host's `~/.claude` on each create so auth, skills, and plugins persist without leaking the host's home dir.
|
|
19
19
|
- **`agentbox.yaml`** — read by `agentbox-ctl` from `/workspace`. Tasks and services declared here are what the supervisor will run.
|
|
20
20
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentbox
|
|
3
|
+
description: "Fork the current agent session into a new VM or local Docker container with all the project files, agent settings and session teleported into."
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
context: fork
|
|
6
|
+
agent: general-purpose
|
|
7
|
+
allowed-tools: Bash
|
|
8
|
+
---
|
|
9
|
+
<!-- agentbox-managed:v1 -->
|
|
10
|
+
|
|
11
|
+
Fork the current Claude Code session into a fresh AgentBox box.
|
|
12
|
+
|
|
13
|
+
1. **Resolve the provider flag from `$ARGUMENTS`:**
|
|
14
|
+
- empty → no flag (uses the default docker provider)
|
|
15
|
+
- `docker` | `daytona` | `hetzner` → pass `--provider $ARGUMENTS`
|
|
16
|
+
- anything else → stop and tell the user the valid values are `docker`, `daytona`, `hetzner`
|
|
17
|
+
|
|
18
|
+
2. **Fork.** Run, via the Bash tool, exactly one command:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
agentbox fork --session ${CLAUDE_SESSION_ID} [--provider $ARGUMENTS]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
3. **Report.** In one line, give the user the new box name (parse it from the command output) and confirm their host session is unaffected. Do not summarize the conversation — the fork already carries it.
|
|
25
|
+
|
|
26
|
+
## Troubleshooting
|
|
27
|
+
|
|
28
|
+
- If agentbox command fails, tell the user to install AgentBox by writing `! npm -g install @madarco/agentbox` in the chat.
|
|
29
|
+
- If `AGENTBOX_RELAY_URL` is set in the environment, you are running *inside* a box. This command is host-only in v1; tell the user box→box fork is not supported yet.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentbox-info
|
|
3
|
+
description: "Spin up isolated sandboxes (\"boxes\") for coding agents, run them in parallel, queue background runs with -i, and push commits safely through the host relay. Use when the user wants to run Claude Code / Codex / OpenCode in a sandbox, start more boxes, attach to a running box, or otherwise operate the `agentbox` CLI on their laptop."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
<!-- agentbox-managed:v1 -->
|
|
7
|
+
|
|
8
|
+
# AgentBox (host-side)
|
|
9
|
+
|
|
10
|
+
You are operating on the **user's host machine** (laptop / dev workstation), not inside a box. Use the `agentbox` CLI to provision isolated sandboxes for coding agents and to attach to them.
|
|
11
|
+
|
|
12
|
+
If you find yourself *inside* a box (`/workspace` exists and `AGENTBOX_RELAY_URL` is set in the env), this is the wrong skill — use the in-box `/agentbox-setup` skill instead.
|
|
13
|
+
|
|
14
|
+
## What AgentBox is, in one paragraph
|
|
15
|
+
|
|
16
|
+
AgentBox spins up one isolated sandbox per agent run — a local Docker container (default), a Daytona cloud sandbox (`--provider daytona`), or a Hetzner VPS (`--provider hetzner`). Each box has its own `/workspace`, but the host's `.git/` is shared, so commits made inside the box land on the host immediately. The agent inside the box has **no host credentials** — `git push`, opening URLs in the host browser, capturing checkpoints, and all other host-side operations flow through a small host process called the **relay** that runs alongside the CLI.
|
|
17
|
+
|
|
18
|
+
## The two starting commands
|
|
19
|
+
|
|
20
|
+
### `agentbox create`
|
|
21
|
+
|
|
22
|
+
Provision a box and stop. The box exists and is ready, but nothing is launched inside it.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
agentbox create # docker, auto-named after the workspace
|
|
26
|
+
agentbox create -n review # docker, friendly name
|
|
27
|
+
agentbox create --provider hetzner # cloud VPS (requires `agentbox prepare --provider hetzner` once)
|
|
28
|
+
agentbox create --attach # drop into a shell inside the box after create
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Useful flags: `-n <name>` (friendly box name), `--provider docker|daytona|hetzner`, `--attach`, `-w <path>` (workspace to mount; defaults to `cwd`), `--snapshot <ref>` (start from a checkpoint).
|
|
32
|
+
|
|
33
|
+
Non-docker providers require a one-time `agentbox prepare --provider <name>` to bake the base image / snapshot.
|
|
34
|
+
|
|
35
|
+
### `agentbox claude`
|
|
36
|
+
|
|
37
|
+
Provision (same as `create`) and launch **Claude Code** inside the box, in a detachable tmux session. This is the main entry point most users want.
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
agentbox claude # docker, attaches your terminal
|
|
41
|
+
agentbox claude -n review # second box, named
|
|
42
|
+
agentbox claude --provider hetzner # cloud
|
|
43
|
+
agentbox claude -- --model sonnet # extra args after `--` go to claude itself
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
While attached: **`Ctrl+a d`** detaches without killing claude. The box keeps running. Reattach with `agentbox claude attach <name|n>`.
|
|
47
|
+
|
|
48
|
+
Variants with the same shape for other agents: **`agentbox codex`**, **`agentbox opencode`**.
|
|
49
|
+
|
|
50
|
+
## `-i` / `--initial-prompt`: background queue
|
|
51
|
+
|
|
52
|
+
With `-i "<prompt>"`, `agentbox claude` (and `codex` / `opencode`) does **not** attach. It writes a job manifest to `~/.agentbox/queue/<id>.json` and exits immediately, printing the job id and log path. The host relay's queue loop drains these manifests respecting `queue.maxConcurrent` (global config; override per invocation with `--max-running <n>`).
|
|
53
|
+
|
|
54
|
+
Use this to fan out parallel agent runs:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
agentbox claude -i "fix the failing test in src/auth and open a PR"
|
|
58
|
+
agentbox claude -i "draft a CHANGELOG entry from the last 20 commits"
|
|
59
|
+
agentbox claude -i "audit our dependencies for known CVEs"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Each call returns instantly. The queue drains them concurrently up to `queue.maxConcurrent`. Inspect / attach later:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
agentbox dashboard # TUI with status + leader-key actions
|
|
66
|
+
agentbox claude attach <name|n> # reattach to a specific box
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Caveats: `-i` is currently **docker-only** (cloud sessions only start on attach, so background-mode has no place to seed the prompt). The host must have valid Claude Code credentials.
|
|
70
|
+
|
|
71
|
+
## Forking the current session into a box
|
|
72
|
+
|
|
73
|
+
From host Claude, run the **`/agentbox`** slash command (optional arg: `docker` | `daytona` | `hetzner`) to snapshot the *current* Claude Code session into a brand-new box that resumes it. With tmux or iTerm it opens in a new terminal tab; otherwise it starts in the background. The host session is unaffected — you get two parallel timelines. The underlying CLI is `agentbox fork` (`agentbox fork --help`); `/agentbox` requires `agentbox install` to have been run once. This is distinct from `-i`, which seeds a *new* prompt rather than resuming the live conversation.
|
|
74
|
+
|
|
75
|
+
## Driving one agent from another (`drive`, `agent`, `queue wait-for`)
|
|
76
|
+
|
|
77
|
+
When *you* are the host-side agent and want to orchestrate other agents running inside boxes — read what they're doing, send them a prompt, wait until they're done or need input — use these three command families. Everything is stateless / one-shot, and the human-text default switches to machine-friendly JSON with `--json`.
|
|
78
|
+
|
|
79
|
+
### `agentbox drive <box>` — terminal driving
|
|
80
|
+
|
|
81
|
+
Targets the running tmux session inside a box (auto-picks the agent session: `claude` → `codex` → `opencode` → the only running session; override with `--session <name>`). Provider-uniform — works the same on docker / daytona / hetzner.
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
agentbox drive snapshot 1 # print rendered TUI as plain text
|
|
85
|
+
agentbox drive snapshot 1 --with-cursor # JSON envelope: { session, cols, rows, cursor, screen }
|
|
86
|
+
agentbox drive snapshot 1 --ansi --rows -200:-1 # include color, walk into scrollback
|
|
87
|
+
agentbox drive keypress 1 "<C-c>" # DSL: <Enter>, <C-x>, <Tab>, <F5>, <Up>, etc.
|
|
88
|
+
agentbox drive send-text 1 "hello" # literal text, no DSL parsing, no trailing Enter
|
|
89
|
+
agentbox drive prompt 1 "summarize /workspace/README" # type + Enter (the convenience action)
|
|
90
|
+
agentbox drive wait 1 --text "✓" --timeout 60000 # block until <text> appears on screen
|
|
91
|
+
agentbox drive resize 1 200 60
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`keypress` uses a small DSL: `<Enter>`, `<Tab>`, `<Esc>`, `<Space>`, `<BS>`, `<Del>`, `<Up>/<Down>/<Left>/<Right>`, `<Home>/<End>/<PageUp>/<PageDown>`, `<F1>`–`<F12>`, `<C-a>`..`<C-z>`. Use `<<` for a literal `<`. Multiple args concatenate with no spaces (`"ls" "<Enter>"` → `ls\r`).
|
|
95
|
+
|
|
96
|
+
### `agentbox agent <box>` — agent state introspection (Claude / Codex / OpenCode)
|
|
97
|
+
|
|
98
|
+
Sub-second latency. State source by agent:
|
|
99
|
+
|
|
100
|
+
- **Claude Code**: lifecycle hooks (`UserPromptSubmit`, `PreToolUse`, `Stop`, `Notification`, `ExitPlanMode`, `AskUserQuestion`, `PreCompact`/`PostCompact`, `StopFailure`, `SubagentStart`/`SubagentStop`).
|
|
101
|
+
- **Codex**: tmux-pane scraper inside the box (codex 0.134.0's own hook firing is unreliable; staged hooks remain for the day that's fixed upstream).
|
|
102
|
+
- **OpenCode**: a plugin (`agentbox-state.js`) seeded into the OpenCode config volume, subscribing to OpenCode's event bus.
|
|
103
|
+
|
|
104
|
+
All three feed the same status pipeline; `agent state` / `agent wait-for` work the same regardless of which agent runs inside the box. Reports come from `~/.agentbox/boxes/<id>/status.json` and the relay event stream.
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
agentbox agent state 1 # → working | idle | waiting | end-plan | question | prompt | compacting | error
|
|
108
|
+
agentbox agent state 1 --json # full BoxStatusClaude (state, updatedAt, sessionTitle, plan?, question?)
|
|
109
|
+
|
|
110
|
+
agentbox agent wait-for prompt 1 --timeout 600000 # block until Claude is at the input box, no pending plan/question
|
|
111
|
+
agentbox agent wait-for end-plan 1 # Claude just called ExitPlanMode; user has to approve
|
|
112
|
+
agentbox agent wait-for question 1 # AskUserQuestion picker is up
|
|
113
|
+
agentbox agent wait-for idle 1 # Stop hook fired (turn complete)
|
|
114
|
+
agentbox agent wait-for compacting 1 # Claude is summarizing context (PreCompact fired)
|
|
115
|
+
agentbox agent wait-for error 1 # Claude's turn ended with a failure (StopFailure)
|
|
116
|
+
|
|
117
|
+
agentbox agent get-plan-question 1 # print the plan body OR question + options (human)
|
|
118
|
+
agentbox agent get-plan-question 1 --json # structured payload
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The `prompt` state is derived: `idle` AND tmux session alive AND no pending plan/question — i.e. "ready for the next user message". Use it as the natural sync point after sending a new prompt.
|
|
122
|
+
|
|
123
|
+
The `end-plan` and `question` matchers tolerate the race where Claude's `Notification:permission_prompt` hook flips the raw state to `waiting` immediately after the matcher hook fires — both states still match while the plan/question payload is pending, and only the matching `PostToolUse` (handled internally with `--clear-pending`) resets them.
|
|
124
|
+
|
|
125
|
+
### `agentbox queue wait-for <event>` — queue + box lifecycle
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
agentbox queue wait-for new-box # any new box gets registered
|
|
129
|
+
agentbox queue wait-for empty-queue --timeout 1800000 # all queued/running jobs settled
|
|
130
|
+
agentbox queue wait-for box-running --box review
|
|
131
|
+
agentbox queue wait-for box-paused --box 2
|
|
132
|
+
agentbox queue wait-for box-stopped --box 2
|
|
133
|
+
agentbox queue wait-for job-done --job b45f1603841bd2b5 # terminal status (done/failed/cancelled)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
All wait-for commands exit 0 on match, exit 1 on timeout, and accept `--json` for parseable output.
|
|
137
|
+
|
|
138
|
+
### Recipe: queue a plan, then act per turn
|
|
139
|
+
|
|
140
|
+
This is the canonical "drive a Claude Code from another Claude Code" loop. You queue an initial planning prompt, wait for the plan to land, capture it, decide, send the next message, repeat.
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
# 1. Kick off a box with a planning prompt.
|
|
144
|
+
agentbox claude -n design -i "Plan how to add an OAuth login flow to apps/web, then enter plan mode. Don't start coding."
|
|
145
|
+
|
|
146
|
+
# 2. Wait until Claude is at the ExitPlanMode approval prompt.
|
|
147
|
+
agentbox agent wait-for end-plan design --timeout 600000
|
|
148
|
+
|
|
149
|
+
# 3. Read the plan back as text (or JSON) and decide.
|
|
150
|
+
PLAN=$(agentbox agent get-plan-question design)
|
|
151
|
+
echo "$PLAN"
|
|
152
|
+
|
|
153
|
+
# 4. Approve via tmux — option 1 ("Yes, and use auto mode") is already highlighted.
|
|
154
|
+
agentbox drive keypress design "<Enter>"
|
|
155
|
+
|
|
156
|
+
# 5. Wait for the turn to finish.
|
|
157
|
+
agentbox agent wait-for prompt design --timeout 1200000
|
|
158
|
+
|
|
159
|
+
# 6. Fan out follow-up work to a fresh box, in background, while reviewing this one.
|
|
160
|
+
agentbox claude -i "Write the OAuth provider unit tests in apps/web/test/auth/"
|
|
161
|
+
|
|
162
|
+
# 7. Block until everything settles before reporting back.
|
|
163
|
+
agentbox queue wait-for empty-queue --timeout 3600000
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The same shape covers `agent wait-for question` + `agent get-plan-question` (read the choices, send the answer index via `drive keypress 1 "<Down><Enter>"`, then `wait-for prompt`).
|
|
167
|
+
|
|
168
|
+
### Quick mental model
|
|
169
|
+
|
|
170
|
+
- `drive` = "send keystrokes / read screen" — provider-uniform tmux capture-pane / send-keys.
|
|
171
|
+
- `agent` = "what is the Claude TUI currently doing" — hook-driven, race-free, machine-readable.
|
|
172
|
+
- `queue wait-for` = "block on queue or box lifecycle transitions" — poll-based, no new endpoint.
|
|
173
|
+
- All three commands are **stateless** — safe to invoke from any script, any agent, in parallel.
|
|
174
|
+
- `--json` everywhere. Default human text is for the operator; an agent should pass `--json`.
|
|
175
|
+
|
|
176
|
+
## Git through the host relay
|
|
177
|
+
|
|
178
|
+
**The box has no SSH keys, GPG keys, or git remote credentials.** Don't ask the user to add any. When an in-box agent (or a script you run inside the box) does `git push` or `git pull`, the AgentBox-provided `agentbox-ctl git` wrapper POSTs a JSON-RPC call to the host relay (`POST /rpc`, bearer-auth, loopback-only). The relay runs the **real** `git push origin …` on the host, using the user's `SSH_AUTH_SOCK`, `~/.gitconfig`, and identity — and streams stdout/stderr back into the box's terminal. The box's exit code matches the host's.
|
|
179
|
+
|
|
180
|
+
Implications for you, the host-side agent:
|
|
181
|
+
|
|
182
|
+
- Inside the box you can `git commit … && git push` exactly as normal. No setup needed.
|
|
183
|
+
- Pushes are gated host-side: the relay can require a confirm prompt for destructive operations (the user sees it in the dashboard footer, ~25 s TTL). If a push appears to hang, tell the user to check the dashboard.
|
|
184
|
+
- The relay process is started lazily by the first `agentbox create` / `agentbox claude` and persists across runs (PID at `~/.agentbox/relay.pid`, log at `~/.agentbox/relay.log`). You normally don't need to manage it.
|
|
185
|
+
|
|
186
|
+
## Other commands worth knowing
|
|
187
|
+
|
|
188
|
+
| Command | What it does |
|
|
189
|
+
| --- | --- |
|
|
190
|
+
| `agentbox dashboard` | TUI status + switcher across all boxes. The leader is **`Ctrl+a`** (e.g. `Ctrl+a u` opens the box's web URL; `Ctrl+a s` opens the in-box browser; `Ctrl+a q` quits). |
|
|
191
|
+
| `agentbox shell [n\|name]` | Interactive `bash -l` inside the box (also wrapped in tmux by default — detach with `Ctrl+a d`). |
|
|
192
|
+
| `agentbox url [n\|name]` | Open the box's web app URL (`<box-name>.localhost` via Portless) in the host browser. |
|
|
193
|
+
| `agentbox screen [n\|name]` | Open the box's **own** Chromium via VNC — useful for OAuth flows the agent inside the box initiates. |
|
|
194
|
+
| `agentbox code [n\|name]` | Open VS Code / Cursor pointed at the box. |
|
|
195
|
+
| `agentbox prepare --provider <name>` | One-time base image / snapshot build for `daytona` or `hetzner`. With no `--provider`, prints status across all providers. |
|
|
196
|
+
| `agentbox prune --provider <name>` | Clean up orphan boxes / images / snapshots for a provider (docker + daytona supported; hetzner pending). |
|
|
197
|
+
|
|
198
|
+
Per-project numeric index (`1`, `2`, …) and friendly name (`review`, `smoke`) both work wherever `<box>` is accepted. Index `1` is the first box created in the current workspace.
|
|
199
|
+
|
|
200
|
+
## Operating principles
|
|
201
|
+
|
|
202
|
+
1. **Never assume the host needs SSH keys forwarded into a box** — git is handled by the relay, by design.
|
|
203
|
+
2. **Use `-i` whenever the user asks for parallel agent work** rather than spawning multiple foreground sessions. Then point them at `agentbox dashboard` to watch progress.
|
|
204
|
+
3. **Pick the provider deliberately.** `docker` is the fast default. `--provider hetzner` gives a real VPS (heavier, isolated, requires `agentbox prepare --provider hetzner` once). `--provider daytona` is the managed cloud option.
|
|
205
|
+
4. **Cross-check before recommending a command.** If a flag isn't listed here, run `agentbox <command> --help` (it's safe and read-only) before suggesting it to the user.
|
|
206
|
+
5. **`/agentbox-setup` is a different skill.** It runs *inside* a box to generate `/workspace/agentbox.yaml`. Don't conflate it with `/agentbox` (host-side fork) or this reference skill.
|
|
207
|
+
|
|
208
|
+
## Reference
|
|
209
|
+
|
|
210
|
+
- Full docs live in the repo at `docs/` — start with `docs/architecture.md` and `docs/create-and-checkpoints.md` for the model, `docs/host-relay.md` for the relay, `docs/cloud-providers.md` for the cloud paths.
|
|
211
|
+
- npm package: `@madarco/agentbox` — `npm -g install @madarco/agentbox` (or `npx @madarco/agentbox <command>`).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Fork the current Codex session into a new AgentBox box and resume it there (opens in a new terminal tab).
|
|
3
|
+
argument-hint: [provider]
|
|
4
|
+
---
|
|
5
|
+
<!-- agentbox-managed:v1 -->
|
|
6
|
+
|
|
7
|
+
Fork the current Codex session into a fresh AgentBox box running Codex.
|
|
8
|
+
|
|
9
|
+
Optional provider argument: `$ARGUMENTS` (docker | daytona | hetzner; default docker).
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. **Pre-flight (stop on either):**
|
|
14
|
+
- If `AGENTBOX_RELAY_URL` is set in the environment, you are running *inside* a box — box→box fork is not supported yet; stop and tell the user.
|
|
15
|
+
- If `which agentbox` fails, tell the user to install AgentBox (`npm -g install @madarco/agentbox`) and stop.
|
|
16
|
+
|
|
17
|
+
2. **Find the current Codex session id.** Codex exposes no session-id variable, so resolve it from the most recently written rollout file (that is the live session). Run via your shell tool:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
ls -t "$HOME"/.codex/sessions/*/*/*/rollout-*.jsonl 2>/dev/null | head -1 \
|
|
21
|
+
| xargs -I{} basename {} .jsonl \
|
|
22
|
+
| grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That prints the session `<uuid>`. If it prints nothing, stop and tell the user no Codex session was found for this machine.
|
|
26
|
+
|
|
27
|
+
3. **Resolve the provider flag from `$ARGUMENTS`:** empty → none; `docker` | `daytona` | `hetzner` → `--provider $ARGUMENTS`; anything else → stop and report the valid values.
|
|
28
|
+
|
|
29
|
+
4. **Fork.** Run, via your shell tool:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
agentbox fork --agent codex --session <uuid> [--provider <from step 3>]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
5. **Report** the new box name from the command output. Your current Codex session is unaffected — you now have two parallel timelines.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Spawn a parallel AgentBox box running OpenCode for this project (opens in a new terminal tab). Note - the current OpenCode session is not resumed yet; this starts a fresh session.
|
|
3
|
+
---
|
|
4
|
+
<!-- agentbox-managed:v1 -->
|
|
5
|
+
|
|
6
|
+
Spawn a new AgentBox box running OpenCode for the current project, in a new terminal tab.
|
|
7
|
+
|
|
8
|
+
Optional provider argument: `$ARGUMENTS` (docker | daytona | hetzner; default docker).
|
|
9
|
+
|
|
10
|
+
**Note:** resuming an OpenCode session into a box isn't supported yet (sessions live in a shared SQLite DB), so this starts a **fresh** OpenCode session in the box — it does not carry the current conversation.
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. **Pre-flight (stop on either):**
|
|
15
|
+
- If `AGENTBOX_RELAY_URL` is set in the environment, you are running *inside* a box — not supported; stop and tell the user.
|
|
16
|
+
- If `which agentbox` fails, tell the user to install AgentBox (`npm -g install @madarco/agentbox`) and stop.
|
|
17
|
+
|
|
18
|
+
2. **Resolve the provider flag from `$ARGUMENTS`:** empty → none; `docker` | `daytona` | `hetzner` → `--provider $ARGUMENTS`; anything else → stop and report the valid values.
|
|
19
|
+
|
|
20
|
+
3. **Fork.** Run, via your shell tool:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
agentbox fork --agent opencode [--provider <from step 2>]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
4. **Report** the new box name from the command output.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
buildCloudAttachInnerCommand,
|
|
4
|
-
cloudAgentAttach
|
|
5
|
-
} from "./chunk-7KOEFGN2.js";
|
|
6
|
-
import "./chunk-NAVL4R34.js";
|
|
7
|
-
import "./chunk-UK72UQ5U.js";
|
|
8
|
-
export {
|
|
9
|
-
buildCloudAttachInnerCommand,
|
|
10
|
-
cloudAgentAttach
|
|
11
|
-
};
|
|
12
|
-
//# sourceMappingURL=_cloud-attach-DMVH6GWO.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider/registry.ts","../src/wrapped-pty/run.ts","../src/pty/pty-backend.ts","../src/terminal/host.ts","../src/wrapped-pty/input-router.ts","../src/dashboard/sidebar.ts","../src/wrapped-pty/footer.ts","../src/wrapped-pty/prompt-client.ts","../src/commands/_cloud-attach.ts"],"sourcesContent":["/**\n * Provider registry — resolves a `Provider` for either an existing box (from\n * its `provider` discriminator) or a fresh `create` (from --provider flag /\n * config / default). Lazy `import()` keeps the Daytona SDK out of the Docker\n * hot path.\n */\n\nimport type { EffectiveConfig } from '@agentbox/config';\nimport type { BoxRecord, Provider, ProviderName } from '@agentbox/core';\n\nexport type KnownProviderName = 'docker' | 'daytona' | 'hetzner';\n\nconst KNOWN: readonly KnownProviderName[] = ['docker', 'daytona', 'hetzner'];\n\nexport function isKnownProvider(name: string): name is KnownProviderName {\n return (KNOWN as readonly string[]).includes(name);\n}\n\nexport async function getProvider(name: ProviderName): Promise<Provider> {\n switch (name) {\n case 'docker': {\n const mod = await import('@agentbox/sandbox-docker');\n return mod.dockerProvider;\n }\n case 'daytona': {\n // Single lazy import covers both the first-run prompt gate and the\n // provider itself — keeps the Daytona SDK off the Docker hot path.\n // The prompt is a no-op when env is already configured or stdin isn't\n // a TTY (scripted callers get the SDK's \"not configured\" error instead\n // of a hung prompt).\n const mod = await import('@agentbox/sandbox-daytona');\n await mod.ensureDaytonaCredentials();\n return mod.daytonaProvider;\n }\n case 'hetzner': {\n // Same lazy-import pattern as daytona. `ensureHetznerCredentials` walks\n // the user through `agentbox hetzner login` on first use. The base-\n // snapshot gate (`ensureHetznerBaseSnapshot`) is deliberately *not*\n // called here: it would chicken-and-egg `agentbox prepare --provider\n // hetzner` (which exists precisely to BUILD the snapshot). The gate\n // lives inside `backend.provision` instead — `prepare` calls the REST\n // client directly, never `provision`, so it slips past the gate while\n // `create`/`claude`/etc. still trip it.\n const mod = await import('@agentbox/sandbox-hetzner');\n await mod.ensureHetznerCredentials();\n return mod.hetznerProvider;\n }\n default:\n throw new Error(`unknown sandbox provider: ${String(name)}`);\n }\n}\n\n/** Provider for an existing box record. Defaults to 'docker' for legacy records. */\nexport async function providerForBox(box: BoxRecord): Promise<Provider> {\n return getProvider(box.provider ?? 'docker');\n}\n\nexport interface CreateProviderChoice {\n /** Explicit --provider flag, if the command exposed one. */\n flag?: string;\n /** Effective config (carries box.provider for the layered default). */\n config: EffectiveConfig;\n}\n\n/**\n * Provider for a fresh `agentbox create`. Precedence: --provider flag >\n * box.provider config > 'docker'. Throws if the resolved name isn't registered.\n */\nexport async function providerForCreate(choice: CreateProviderChoice): Promise<Provider> {\n const flag = choice.flag?.trim();\n const name = (flag && flag.length > 0 ? flag : choice.config.box.provider) as ProviderName;\n if (typeof name !== 'string' || name.length === 0 || !isKnownProvider(name)) {\n throw new Error(\n `unknown sandbox provider \"${String(name)}\" (known: ${KNOWN.join(', ')})`,\n );\n }\n return getProvider(name);\n}\n","import { spawn, spawnSync } from 'node:child_process';\nimport { readBoxStatus } from '@agentbox/sandbox-docker';\nimport type { AttachOpenIn } from '@agentbox/config';\nimport { loadPtyBackend } from '../pty/pty-backend.js';\nimport { detectHostTerminal, spawnInNewTerminal } from '../terminal/host.js';\nimport {\n createInputRouter,\n type InputRouter,\n type LeaderAction,\n} from './input-router.js';\nimport {\n CURSOR_RESTORE,\n CURSOR_SAVE,\n cursorMoveTo,\n renderFooter,\n SYNC_BEGIN,\n SYNC_END,\n type FooterState,\n} from './footer.js';\nimport { postAnswer, subscribePrompts, type PromptStream } from './prompt-client.js';\nimport type { BoxNoticeEvent, PromptAskEvent } from '@agentbox/relay';\n\nexport interface WrappedAttachOptions {\n /** Docker container name (only used for log lines). */\n container: string;\n /** Full docker argv (e.g. result of buildClaudeAttachArgv). */\n dockerArgv: string[];\n /**\n * The program to spawn for the PTY. Defaults to `'docker'` (the historical\n * behavior; `dockerArgv` is then the docker subcommand argv). Cloud boxes\n * pass `'ssh'` with the Daytona SSH argv instead.\n */\n command?: string;\n /** Relay base URL — http://127.0.0.1:8787 in normal use. */\n relayBaseUrl: string;\n boxId: string;\n /** Friendly box name; rendered in the idle footer. */\n boxName: string;\n /** Per-project box index (BoxRecord.projectIndex). Used together with\n * boxId/boxName to read the per-box status.json for the live session\n * title. Pre-feature boxes lack it; absent is fine. */\n projectIndex?: number;\n /** Mode label affects the idle footer state label only. */\n mode: 'claude' | 'shell' | 'codex' | 'opencode';\n /** Whether the inner session can be detached (tmux-backed). Drives the\n * `Ctrl+a d` detach chord + footer hint. Defaults to `mode === 'claude'`\n * (claude is always tmux-backed); a tmux-backed `agentbox shell` passes\n * `true`, a `--no-tmux` shell leaves it false. */\n detachable?: boolean;\n /** Optional notice printed to stdout *after* the pty exits with code 0\n * (mirrors today's `formatDetachNotice` for `agentbox claude`). */\n detachNotice?: string;\n /** Optional sink for non-fatal errors that we'd otherwise swallow (Ctrl+a\n * action spawn failures, status-poll failures, unexpected prompt-capture\n * rejections). Callers wire this to their command log so post-mortem\n * inspection isn't blind. */\n onError?: (msg: string) => void;\n /** Where to open the attached session. When set to anything other than\n * `same` (or undefined) and the host shell is running inside tmux or iTerm2,\n * the attach runs in a fresh pane/tab/window and this function returns 0\n * without taking over the current terminal. Outside tmux/iTerm2 it falls\n * back to inline attach (the original behavior). */\n openIn?: AttachOpenIn;\n}\n\nconst FOOTER_ROWS = 1;\nconst STATUS_POLL_INTERVAL_MS = 3000;\n/** Spinner advance cadence while a `notice` footer is active. */\nconst SPINNER_INTERVAL_MS = 120;\n/** How long the post-action confirmation flash stays in the footer. */\nconst FLASH_DURATION_MS = 2000;\n\n/** Per-action confirmation text shown in the footer flash. */\nconst ACTION_FLASH: Record<Exclude<LeaderAction, 'detach'>, string> = {\n screen: 'Opening noVNC viewer…',\n code: 'Launching VS Code / Cursor…',\n url: 'Opening box URL…',\n};\n\n/** Per-action `agentbox` subcommand: `<sub> <boxId> <...flags>`. */\nconst ACTION_CMD: Record<\n Exclude<LeaderAction, 'detach'>,\n { sub: string; flags: string[] }\n> = {\n screen: { sub: 'screen', flags: [] },\n // --no-wait: don't block on `wait-ready` — the box is already running.\n code: { sub: 'code', flags: ['--no-wait'] },\n url: { sub: 'url', flags: [] },\n};\n\n/** Recursive `agentbox <agent> attach <box> --attach-in same` argv for the\n * new-pane re-entry. Returns null for modes that don't have an `attach`\n * subcommand (notably `shell`), so the caller can skip new-pane spawning. */\nfunction buildAgentboxAttachArgv(\n mode: WrappedAttachOptions['mode'],\n boxName: string,\n): string[] | null {\n if (mode !== 'claude' && mode !== 'codex' && mode !== 'opencode') return null;\n return [mode, 'attach', boxName, '--attach-in', 'same'];\n}\n\n/**\n * Replace `spawnSync('docker', argv, { stdio: 'inherit' })` with a\n * node-pty wrapper that reserves the bottom row for a permission-prompt\n * footer. Falls back transparently to today's spawnSync behavior when\n * node-pty isn't available (optional dep missing), or when stdin/stdout\n * isn't a TTY (piping / non-interactive use).\n *\n * Returns the pty's exit code; caller `process.exit`s with it.\n */\nexport async function runWrappedAttach(opts: WrappedAttachOptions): Promise<number> {\n const command = opts.command ?? 'docker';\n const logErr = (msg: string): void => {\n opts.onError?.(msg);\n };\n\n // Open-in-new-terminal short-circuit: if the user asked for split/window/tab\n // and we're inside tmux or iTerm2, re-invoke `agentbox <agent> attach <box>\n // --attach-in same` in a fresh pane so the new pane runs the full wrapper\n // (footer + prompt channel) against the already-prepared session — same UX\n // as inline, just in a new pane. The host process then exits 0. Unknown\n // hosts, shell mode (no attach subcommand to recurse into), and spawn\n // failures fall through to the inline attach below.\n const openIn = opts.openIn ?? 'same';\n if (openIn !== 'same') {\n const subArgv = buildAgentboxAttachArgv(opts.mode, opts.boxName);\n const host = subArgv ? detectHostTerminal() : 'unknown';\n if (subArgv && host !== 'unknown' && process.argv[1]) {\n const r = await spawnInNewTerminal({\n host,\n mode: openIn,\n argv: [process.execPath, process.argv[1], ...subArgv],\n cwd: process.cwd(),\n title: opts.boxName,\n });\n if (r.launched) {\n process.stdout.write(r.note + '\\n');\n return 0;\n }\n if (r.error) logErr(r.error);\n // fall through to inline attach\n }\n }\n\n if (!process.stdout.isTTY || !process.stdin.isTTY) {\n // Non-interactive path: piping / scripts. Don't wrap — preserves\n // machine-readable stdout, no footer corruption.\n return runFallback(command, opts.dockerArgv);\n }\n const backend = await loadPtyBackend();\n if (!backend) {\n // One-line stderr notice; preserves current behavior bit-for-bit.\n process.stderr.write(\n 'agentbox: permission prompts disabled (node-pty backend unavailable)\\n',\n );\n return runFallback(command, opts.dockerArgv);\n }\n\n const cols = process.stdout.columns ?? 80;\n const rows = process.stdout.rows ?? 24;\n const innerRows = Math.max(1, rows - FOOTER_ROWS);\n\n const pty = backend.ptySpawn(command, opts.dockerArgv, {\n name: 'xterm-256color',\n cols,\n rows: innerRows,\n env: process.env,\n });\n\n // claude is always tmux-backed; a tmux-backed `agentbox shell` opts in via\n // `detachable: true`, a `--no-tmux` shell leaves it false (nothing to detach).\n const detachable = opts.detachable ?? opts.mode === 'claude';\n\n // Idle footer = dashboard's statusLine() with a single hint (`Control+a:\n // Actions`, expanding to the chord menu while the leader is open). Session\n // title + claude activity come from the per-box status.json polled below.\n let leaderActive = false;\n const buildIdle = (sessionTitle?: string, claudeActivity?: string): FooterState => ({\n kind: 'idle',\n boxName: opts.boxName,\n sessionTitle,\n claudeActivity,\n mode: opts.mode,\n detachable,\n leaderActive,\n });\n let footerState: FooterState = buildIdle();\n let lastSessionTitle: string | undefined;\n let lastActivity: string | undefined;\n // Prompt + notice + flash are tracked independently of `footerState`;\n // `recomputeFooter` derives the visible state (prompt > notice > flash > idle).\n let capturingPrompt: PromptAskEvent | null = null;\n let activeNotice: BoxNoticeEvent | null = null;\n let noticeFrame = 0;\n let spinnerTimer: ReturnType<typeof setInterval> | null = null;\n // Transient confirmation shown after a Ctrl+a action fires.\n let flashMessage: string | null = null;\n let flashTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Lazy SGR mirror: when the inner pty's most recent attribute is bright\n // bold, our footer paint won't reset it correctly via the inner program's\n // next byte. We always end the footer with SGR reset, but the inner\n // program may be in the middle of a graphics run when the footer redraw\n // happens — wrap the redraw in cursor save/restore + sync output so the\n // inner program never sees our cursor moves and the user sees one atomic\n // frame.\n const redrawFooter = (): void => {\n const cs = process.stdout.columns ?? cols;\n const rs = process.stdout.rows ?? rows;\n const line = renderFooter(footerState, cs);\n // Position at the last row, then write. Save/restore around the lot so\n // the inner pty's cursor doesn't move.\n const payload =\n SYNC_BEGIN +\n CURSOR_SAVE +\n cursorMoveTo(rs, 1) +\n line +\n CURSOR_RESTORE +\n SYNC_END;\n process.stdout.write(payload);\n };\n\n // Derive `footerState` from the current prompt / notice / flash / idle\n // inputs. A pending prompt outranks a notice (it hard-blocks the box's\n // RPC); a notice outranks a flash; a flash outranks idle. Called after any\n // input changes.\n const recomputeFooter = (): void => {\n if (capturingPrompt) {\n footerState = { kind: 'prompt', prompt: capturingPrompt };\n } else if (activeNotice) {\n footerState = { kind: 'notice', message: activeNotice.message, frame: noticeFrame };\n } else if (flashMessage) {\n footerState = { kind: 'flash', message: flashMessage };\n } else {\n footerState = buildIdle(lastSessionTitle, lastActivity);\n }\n };\n\n const startSpinner = (): void => {\n if (spinnerTimer) return;\n spinnerTimer = setInterval(() => {\n noticeFrame++;\n // Only repaint while the notice is the visible state — if a prompt is\n // covering it the frame still advances so it resumes mid-animation.\n if (footerState.kind === 'notice') {\n recomputeFooter();\n redrawFooter();\n }\n }, SPINNER_INTERVAL_MS);\n if (typeof spinnerTimer.unref === 'function') spinnerTimer.unref();\n };\n const stopSpinner = (): void => {\n if (spinnerTimer) {\n clearInterval(spinnerTimer);\n spinnerTimer = null;\n }\n };\n\n // Wire pty -> stdout. The inner program writes raw bytes; we forward as-is.\n // The outer terminal has `rows` real rows, but the pty thinks it has `innerRows`.\n // The inner program's writes can still physically touch row `rows` (our footer\n // row) via: (1) scroll when its bottom line emits a newline — the terminal\n // scrolls the whole screen and row `rows` gets cleared; (2) clear-screen\n // sequences like `\\x1b[2J`; (3) alt-screen entry `\\x1b[?1049h`; (4) column\n // wraparound from the inner program's last row. The scroll-region setup\n // below limits (1); always-repaint here handles the rest. Each redraw is\n // wrapped in synchronized output (DECSET 2026) so the user never sees a\n // half-painted frame on terminals that support it (iTerm2/WezTerm/kitty/\n // Apple Terminal/Ghostty).\n pty.onData((d: string) => {\n process.stdout.write(d);\n redrawFooter();\n });\n\n // Ctrl+a leader chord map — keys mirror the dashboard's (`c`/`s`/`u`).\n // A detachable (tmux-backed) session also gets `d: detach`; a plain\n // `--no-tmux` shell has nothing to detach from.\n const leaderChords: Record<string, LeaderAction> = detachable\n ? { c: 'code', s: 'screen', u: 'url', d: 'detach' }\n : { c: 'code', s: 'screen', u: 'url' };\n\n // Run a Ctrl+a leader action. `detach` writes the tmux detach sequence to\n // the pty (`\\x02` = Ctrl+b, tmux's secondary prefix; `d` = detach-client) —\n // the attach process then exits 0 and teardown runs normally. The other\n // actions shell out to the real `agentbox` subcommand, detached, so the\n // long-running open/launch never blocks (or corrupts) this terminal.\n const runAction = (name: LeaderAction): void => {\n if (name === 'detach') {\n pty.write('\\x02d');\n return;\n }\n const cliEntry = process.argv[1];\n if (typeof cliEntry === 'string' && cliEntry.length > 0) {\n const cmd = ACTION_CMD[name];\n try {\n spawn(\n process.execPath,\n [cliEntry, cmd.sub, opts.boxId, ...cmd.flags],\n { detached: true, stdio: 'ignore' },\n ).unref();\n } catch (e) {\n // Best-effort — the footer flash still shows. Surface for inspection.\n logErr(`leader-action spawn (${name}) failed: ${(e as Error).message}`);\n }\n }\n flashMessage = ACTION_FLASH[name];\n if (flashTimer) clearTimeout(flashTimer);\n flashTimer = setTimeout(() => {\n flashTimer = null;\n flashMessage = null;\n recomputeFooter();\n redrawFooter();\n }, FLASH_DURATION_MS);\n if (typeof flashTimer.unref === 'function') flashTimer.unref();\n recomputeFooter();\n redrawFooter();\n };\n\n // Wire stdin -> pty (through the router so prompts + the leader can intercept).\n const router: InputRouter = createInputRouter({\n onForward: (b) => {\n // node-pty wants utf8 strings; stdin is binary safe via Buffer.\n pty.write(b.toString('utf8'));\n },\n onAnswer: (body) => {\n // Fire-and-forget; the relay-side route is idempotent. We don't\n // block the input flow on the network roundtrip.\n void postAnswer({ relayBaseUrl: opts.relayBaseUrl, body });\n capturingPrompt = null;\n recomputeFooter();\n redrawFooter();\n },\n leaderChords,\n onLeaderChange: (open) => {\n leaderActive = open;\n recomputeFooter();\n redrawFooter();\n },\n onAction: (name) => {\n runAction(name);\n },\n });\n\n if (process.stdin.isTTY) process.stdin.setRawMode(true);\n process.stdin.resume();\n const onStdinData = (chunk: Buffer): void => {\n router.feed(chunk);\n };\n process.stdin.on('data', onStdinData);\n\n // Resize: keep the pty one row shorter than the host terminal; the footer\n // owns the last row directly. Re-apply the scroll region too — most\n // terminals reset DECSTBM on resize.\n const onResize = (): void => {\n const cs = process.stdout.columns ?? cols;\n const rs = process.stdout.rows ?? rows;\n const inner = Math.max(1, rs - FOOTER_ROWS);\n pty.resize(cs, inner);\n process.stdout.write(`\\x1b[1;${String(inner)}r`);\n redrawFooter();\n };\n process.stdout.on('resize', onResize);\n\n // SSE: subscribe to the relay's prompt stream for this box.\n const stream: PromptStream = subscribePrompts({\n relayBaseUrl: opts.relayBaseUrl,\n boxId: opts.boxId,\n onPrompt: (ev: PromptAskEvent) => {\n capturingPrompt = ev;\n recomputeFooter();\n redrawFooter();\n // capture() returns a Promise that resolves with the answer body; the\n // input-router's onAnswer callback already POSTs and resets the footer.\n // We just need to await so unhandled rejections (router.abort) don't\n // crash the process.\n router.capture(ev).catch((e: unknown) => {\n // Expected reasons: sibling answered ('resolved-elsewhere'), pty exit.\n // Anything else is a real bug worth surfacing.\n const msg = e instanceof Error ? e.message : String(e);\n if (msg !== 'resolved-elsewhere') {\n logErr(`prompt capture rejected: ${msg}`);\n }\n });\n },\n onResolved: (id: string) => {\n // Clear footer if it's still showing this id (sibling wrapper won).\n if (capturingPrompt && capturingPrompt.id === id) {\n capturingPrompt = null;\n router.abort('resolved-elsewhere');\n recomputeFooter();\n redrawFooter();\n }\n },\n onNotice: (ev: BoxNoticeEvent) => {\n activeNotice = ev;\n startSpinner();\n recomputeFooter();\n redrawFooter();\n },\n onNoticeCleared: (id: string) => {\n if (activeNotice && activeNotice.id === id) {\n activeNotice = null;\n stopSpinner();\n recomputeFooter();\n redrawFooter();\n }\n },\n });\n\n // Poll the box's status.json for `claude.sessionTitle` so the idle\n // footer can show what claude set as its terminal title (mirrors the\n // dashboard's sidebar entry). Best-effort — paused/stopped boxes and\n // pre-status-feature boxes return null and we just keep the previous\n // title (or no title).\n const pollStatus = async (): Promise<void> => {\n try {\n const status = await readBoxStatus({\n id: opts.boxId,\n name: opts.boxName,\n projectIndex: opts.projectIndex,\n });\n const nextTitle = status?.claude?.sessionTitle?.trim() || undefined;\n const nextActivity = status?.claude?.state || undefined;\n if (nextTitle === lastSessionTitle && nextActivity === lastActivity) return;\n lastSessionTitle = nextTitle;\n lastActivity = nextActivity;\n if (footerState.kind === 'idle') {\n recomputeFooter();\n redrawFooter();\n }\n } catch (e) {\n // readBoxStatus already swallows the common cases (paused/stopped/pre-feature);\n // anything reaching here is unexpected and worth a log line.\n logErr(`status poll failed: ${(e as Error).message}`);\n }\n };\n void pollStatus();\n const statusTimer = setInterval(() => {\n void pollStatus();\n }, STATUS_POLL_INTERVAL_MS);\n if (typeof statusTimer.unref === 'function') statusTimer.unref();\n\n // Restrict the outer terminal's scroll region to rows 1..innerRows so the\n // inner program's natural scrolling (bottom-line newline) doesn't push\n // content into our footer row. DECSTBM also resets the cursor to (1,1) on\n // some terminals, so we follow it with a cursor restore. Reverted in\n // teardown via `\\x1b[r` (clear scroll region -> full screen).\n process.stdout.write(`\\x1b[1;${String(innerRows)}r`);\n\n // Plain shell (`--no-tmux`): bash doesn't enter alt-screen, so without help\n // the user's pre-shell host-terminal content stays visible above bash's\n // freshly drawn prompt. Clear the visible screen + home the cursor before\n // the pty's first write. We don't touch scrollback (`\\x1b[3J`) — the user's\n // pre-shell context stays scroll-up-able. Claude and the tmux-backed shell\n // skip this: they enter their own alt-screen on init and would just\n // overpaint anyway (clearing first would only flicker).\n if (opts.mode === 'shell' && !detachable) {\n process.stdout.write('\\x1b[H\\x1b[2J');\n }\n\n // Initial paint so the idle footer appears immediately.\n redrawFooter();\n\n // Wait for the pty to exit, then tear down everything.\n const exitCode = await new Promise<number>((resolve) => {\n pty.onExit(({ exitCode }) => resolve(exitCode));\n });\n\n // Teardown order: stop reading stdin, restore cooked mode, drop SSE,\n // dispose the router (rejects any in-flight capture), clear the footer\n // row so the shell prompt below doesn't sit on top of our bar.\n process.stdin.off('data', onStdinData);\n process.stdout.off('resize', onResize);\n clearInterval(statusTimer);\n stopSpinner();\n if (flashTimer) clearTimeout(flashTimer);\n if (process.stdin.isTTY) process.stdin.setRawMode(false);\n process.stdin.pause();\n stream.close();\n router.dispose();\n const rsFinal = process.stdout.rows ?? rows;\n const csFinal = process.stdout.columns ?? cols;\n // Clear the scroll region first so the cursor moves below can reach row N\n // without the terminal trying to keep them inside the smaller region.\n // Then move to the footer row, erase it, return the cursor.\n process.stdout.write(\n '\\x1b[r' +\n cursorMoveTo(rsFinal, 1) +\n `\\x1b[2K` +\n cursorMoveTo(rsFinal, csFinal),\n );\n\n if (exitCode === 0 && opts.detachNotice) {\n // Match the cosmetic of the old attachClaudeSession: overwrite tmux's\n // own `[detached]` line if it's visible, then print the reattach hint.\n process.stdout.write('\\x1b[1A\\x1b[2K\\r' + opts.detachNotice + '\\n');\n }\n return exitCode;\n}\n\n/**\n * Fallback when node-pty is unavailable or stdio isn't a TTY. Identical to\n * today's call: blocking spawnSync with inherited stdio.\n */\nfunction runFallback(command: string, argv: string[]): number {\n const child = spawnSync(command, argv, { stdio: 'inherit' });\n return child.status ?? 0;\n}\n","import type { Terminal as XtermTerminal } from '@xterm/headless';\n\n/**\n * The `@xterm/headless` `Terminal` class. Injected (not imported) because\n * @xterm/headless is CJS — a static ESM named import breaks Node's loader for\n * the whole CLI, so callers dynamic-import it and pass the ctor through.\n *\n * Used by the dashboard (for its xterm-headless screen-state mirror). The\n * wrapped-pty wrapper does not need it — the inner program writes raw bytes\n * directly to the user's terminal, no parsing required.\n */\nexport type TerminalCtor = new (opts: {\n cols: number;\n rows: number;\n allowProposedApi: boolean;\n scrollback: number;\n convertEol: boolean;\n}) => XtermTerminal;\n\n/**\n * Minimal shape of a node-pty IPty (avoids a hard type dep on the optional\n * module — node-pty is in optionalDependencies, may not be installed).\n */\nexport interface IPtyLike {\n onData(cb: (d: string) => void): void;\n onExit(cb: (e: { exitCode: number }) => void): void;\n write(d: string): void;\n resize(cols: number, rows: number): void;\n kill(): void;\n}\n\nexport type PtySpawn = (\n file: string,\n args: string[],\n opts: { name: string; cols: number; rows: number; env: NodeJS.ProcessEnv },\n) => IPtyLike;\n\nexport interface PtyBackend {\n ptySpawn: PtySpawn;\n /** Present for callers that also need the xterm headless ctor (dashboard). */\n termCtor: TerminalCtor;\n}\n\n/**\n * Dynamic-load the optional pty + xterm/headless backends. Returns null\n * when either prebuild is missing (we don't throw — callers decide how to\n * degrade). Centralized here so the dashboard and the wrapped-pty wrapper\n * use the same exact load dance.\n */\nexport async function loadPtyBackend(): Promise<PtyBackend | null> {\n try {\n const ptyMod = (await import('@homebridge/node-pty-prebuilt-multiarch')) as Record<\n string,\n unknown\n >;\n const xtermMod = (await import('@xterm/headless')) as Record<string, unknown>;\n const spawn =\n (ptyMod['spawn'] as unknown) ??\n (ptyMod['default'] as Record<string, unknown> | undefined)?.['spawn'];\n const Terminal =\n (xtermMod['Terminal'] as unknown) ??\n (xtermMod['default'] as Record<string, unknown> | undefined)?.['Terminal'];\n if (typeof spawn !== 'function' || typeof Terminal !== 'function') {\n return null;\n }\n return {\n ptySpawn: spawn as unknown as PtySpawn,\n termCtor: Terminal as unknown as TerminalCtor,\n };\n } catch {\n return null;\n }\n}\n","import { spawn } from 'node:child_process';\nimport type { AttachOpenIn } from '@agentbox/config';\n\nexport type HostTerminal = 'tmux' | 'iterm2' | 'unknown';\n\n/**\n * Identify the user's host terminal from env vars. tmux wins over iTerm2 even\n * when nested — when `TMUX` is set, the tmux CLI is the right primitive (it can\n * split the current pane / open a new window without going through AppleScript).\n *\n * macOS-only by design: the CLI itself is macOS-only (see CLAUDE.md), so we\n * don't try to recognize gnome-terminal / alacritty / Windows Terminal.\n */\nexport function detectHostTerminal(env: NodeJS.ProcessEnv = process.env): HostTerminal {\n const tmux = env['TMUX'];\n if (tmux && tmux.length > 0) return 'tmux';\n const termProgram = env['TERM_PROGRAM'];\n if (termProgram === 'iTerm.app') return 'iterm2';\n return 'unknown';\n}\n\n/** Single-quote a string so it survives a shell parse intact. */\nfunction shellQuote(s: string): string {\n if (s.length === 0) return \"''\";\n // Replace any internal `'` with the four-byte sequence `'\\''` (close, escaped\n // quote, reopen). Cheaper than picking double-quotes — no $/`/\\ to worry about.\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/** Escape a string for embedding in a double-quoted AppleScript literal. */\nfunction appleScriptEscape(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/** Join an argv into a single shell-safe command line. */\nfunction shellJoin(argv: string[]): string {\n return argv.map(shellQuote).join(' ');\n}\n\nexport interface SpawnInNewTerminalArgs {\n host: Exclude<HostTerminal, 'unknown'>;\n /** Where to open the session in that host's terminology. `'same'` is rejected\n * by the caller — we never produce `same` here. */\n mode: Exclude<AttachOpenIn, 'same'>;\n /** Full argv to run in the new pane: `[program, ...args]`. The first element\n * is the binary; the rest are passed verbatim. */\n argv: string[];\n /** Working directory for the new pane. Passed to tmux via `-c` and prepended\n * to the iTerm2 command as `cd <cwd> && exec …`. */\n cwd: string;\n /** Short title for the new tmux window / iTerm2 tab when applicable. */\n title: string;\n}\n\nexport interface SpawnInNewTerminalResult {\n launched: boolean;\n /** One-line user-facing message printed to the host's stdout on success.\n * Empty string when `launched` is false. */\n note: string;\n /** stderr captured from the spawner, when `launched` is false. Used only for\n * the command log; not surfaced to the user. */\n error?: string;\n}\n\n/**\n * Open a fresh tmux pane / iTerm2 split-tab-window and run `<command> <argv...>`\n * there. Returns synchronously after the new pane is requested — the inner\n * command runs in its own terminal and is no longer this process's child.\n *\n * On failure (tmux/osascript exits non-zero, or wasn't found), the caller is\n * expected to fall back to inline attach.\n */\nexport async function spawnInNewTerminal(\n args: SpawnInNewTerminalArgs,\n): Promise<SpawnInNewTerminalResult> {\n if (args.host === 'tmux') return spawnInTmux(args);\n return spawnInITerm2(args);\n}\n\nasync function spawnInTmux(args: SpawnInNewTerminalArgs): Promise<SpawnInNewTerminalResult> {\n // `-c <cwd>` drops the new pane in the host pane's directory so the\n // recursive `agentbox` invocation can resolve project-scoped refs (and so\n // any commands the user runs after detaching start somewhere sensible).\n // The command is passed as a single shell-quoted positional after `--`;\n // tmux hands it to /bin/sh -c, which is why each argv element needs\n // single-quoting.\n const cmdStr = shellJoin(args.argv);\n let tmuxArgv: string[];\n let noteKind: string;\n if (args.mode === 'split') {\n tmuxArgv = ['split-window', '-h', '-c', args.cwd, '--', cmdStr];\n noteKind = 'tmux split';\n } else {\n // `window` and `tab` both map to tmux's only \"another full screen\" primitive.\n tmuxArgv = ['new-window', '-n', args.title, '-c', args.cwd, '--', cmdStr];\n noteKind = 'tmux window';\n }\n const r = await runQuiet('tmux', tmuxArgv);\n if (r.code !== 0) {\n return {\n launched: false,\n note: '',\n error: `tmux ${tmuxArgv.join(' ')} exited ${String(r.code)}: ${r.stderr.trim()}`,\n };\n }\n return {\n launched: true,\n note: `Attached in new ${noteKind} — Ctrl+a d to detach the box's tmux session.`,\n };\n}\n\nasync function spawnInITerm2(args: SpawnInNewTerminalArgs): Promise<SpawnInNewTerminalResult> {\n // iTerm2 launches `command` through a shell, but doesn't honor a starting\n // directory parameter on its AppleScript verbs. Prepend `cd <cwd> && exec`\n // so the new tab/window/split lands in the host pane's cwd and replaces\n // the launching shell with the agentbox process.\n const inner = shellJoin(args.argv);\n const cmdLine = `cd ${shellQuote(args.cwd)} && exec ${inner}`;\n const cmdLit = `\"${appleScriptEscape(cmdLine)}\"`;\n\n let script: string;\n let noteKind: string;\n switch (args.mode) {\n case 'split':\n // iTerm2's AppleScript dictionary doesn't expose a `split … with command`\n // form, so we split, then `write text` into the new session.\n script =\n 'tell application \"iTerm\" to ' +\n 'tell current session of current window to ' +\n `tell (split vertically with default profile) to write text ${cmdLit}`;\n noteKind = 'iTerm2 split';\n break;\n case 'tab':\n script =\n 'tell application \"iTerm\" to ' +\n `tell current window to create tab with default profile command ${cmdLit}`;\n noteKind = 'iTerm2 tab';\n break;\n case 'window':\n script =\n 'tell application \"iTerm\" to ' +\n `create window with default profile command ${cmdLit}`;\n noteKind = 'iTerm2 window';\n break;\n }\n\n const r = await runQuiet('osascript', ['-e', script]);\n if (r.code !== 0) {\n return {\n launched: false,\n note: '',\n error: `osascript exited ${String(r.code)}: ${r.stderr.trim()}`,\n };\n }\n return {\n launched: true,\n note: `Attached in new ${noteKind} — Ctrl+a d to detach the box's tmux session.`,\n };\n}\n\ninterface QuietResult {\n code: number;\n stderr: string;\n}\n\n/** Spawn `cmd argv...`, capture stderr, ignore stdout. Resolves on exit. */\nfunction runQuiet(cmd: string, argv: string[]): Promise<QuietResult> {\n return new Promise((resolve) => {\n const child = spawn(cmd, argv, { stdio: ['ignore', 'ignore', 'pipe'] });\n let stderr = '';\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8');\n });\n child.on('error', (err) => {\n resolve({ code: 127, stderr: err.message });\n });\n child.on('exit', (code) => {\n resolve({ code: typeof code === 'number' ? code : 1, stderr });\n });\n });\n}\n","import type { PromptAnswerBody, PromptAskEvent } from '@agentbox/relay';\n\n/**\n * Steady-state input forwarder + active-prompt capture + Ctrl+a leader.\n *\n * In steady state every byte goes to the pty unmodified — *unless* a\n * `leaderChords` map is supplied, in which case `Ctrl+a` (0x01) opens the\n * actions menu (leader-only: a literal Ctrl+a needs a double-press).\n *\n * Only when `capture()` is awaiting does the router intercept the next\n * keystroke and resolve the prompt with a y/n/cancel answer. Anything else\n * the user types while a prompt is active is dropped (not forwarded) — the\n * inner program doesn't see partial keys.\n */\nexport interface InputRouter {\n /** True while a prompt is being captured. Used by the run loop to know\n * whether to redraw the footer eagerly. */\n readonly capturing: boolean;\n /** Feed raw bytes from process.stdin. Forwards or captures internally. */\n feed(buf: Buffer): void;\n /** Activate prompt capture. Resolves with the answer body. Subsequent\n * capture() calls before resolution overwrite the previous prompt (the\n * newer one wins — relay broadcast order is canonical). */\n capture(p: PromptAskEvent): Promise<PromptAnswerBody>;\n /** Reject the in-flight capture (pty exit, sibling-wrapper answered). */\n abort(reason: 'pty-exit' | 'resolved-elsewhere'): void;\n dispose(): void;\n}\n\ninterface ActivePrompt {\n ev: PromptAskEvent;\n resolve: (b: PromptAnswerBody) => void;\n reject: (e: Error) => void;\n}\n\n/** Actions reachable from the Ctrl+a leader menu. */\nexport type LeaderAction = 'screen' | 'code' | 'url' | 'detach';\n\nconst KEY_ENTER = 0x0d;\nconst KEY_LF = 0x0a;\nconst KEY_ESC = 0x1b;\nconst KEY_CTRL_C = 0x03;\nconst KEY_Y_LOW = 0x79;\nconst KEY_Y_UP = 0x59;\nconst KEY_N_LOW = 0x6e;\nconst KEY_N_UP = 0x4e;\nconst KEY_LEADER = 0x01; // Ctrl-a\n\nconst DEFAULT_LEADER_TIMEOUT_MS = 2000;\n\nexport interface InputRouterOptions {\n onForward: (b: Buffer) => void;\n /** Called when a prompt's capture is resolved — the run loop POSTs the answer. */\n onAnswer: (body: PromptAnswerBody) => void;\n /** Ctrl+a leader chord map: a single lowercase character → action. When\n * omitted or empty the leader is disabled and `Ctrl+a` forwards verbatim. */\n leaderChords?: Readonly<Record<string, LeaderAction>>;\n /** Fired when the leader menu opens (true) / closes (false). */\n onLeaderChange?: (active: boolean) => void;\n /** Fired when a recognized chord key resolves the leader. */\n onAction?: (name: LeaderAction) => void;\n /** ms the leader menu stays open with no key before auto-closing (default 2000). */\n leaderTimeoutMs?: number;\n /** Injected for unit tests; defaults to global timers. */\n setTimer?: (ms: number, fn: () => void) => unknown;\n clearTimer?: (h: unknown) => void;\n}\n\nexport function createInputRouter(opts: InputRouterOptions): InputRouter {\n let active: ActivePrompt | null = null;\n let disposed = false;\n\n const leaderChords = opts.leaderChords ?? {};\n const leaderEnabled = Object.keys(leaderChords).length > 0;\n const leaderTimeoutMs = opts.leaderTimeoutMs ?? DEFAULT_LEADER_TIMEOUT_MS;\n const setTimer = opts.setTimer ?? ((ms, fn) => setTimeout(fn, ms) as unknown);\n const clearTimer =\n opts.clearTimer ?? ((h) => clearTimeout(h as ReturnType<typeof setTimeout>));\n let leader = false;\n let leaderTimer: unknown = null;\n\n const disarmLeader = (): void => {\n if (leaderTimer != null) {\n clearTimer(leaderTimer);\n leaderTimer = null;\n }\n };\n\n const exitLeader = (): void => {\n if (!leader) return;\n leader = false;\n disarmLeader();\n opts.onLeaderChange?.(false);\n };\n\n const enterLeader = (): void => {\n leader = true;\n disarmLeader();\n // Leader-only: a lone Ctrl+a just times the menu out — it is never\n // auto-forwarded. A literal Ctrl+a is sent via a double-press.\n leaderTimer = setTimer(leaderTimeoutMs, () => {\n leaderTimer = null;\n exitLeader();\n });\n opts.onLeaderChange?.(true);\n };\n\n // The leader is open and `b` is the chord byte that resolves it.\n const resolveLeaderByte = (b: number): void => {\n if (b === KEY_LEADER) {\n // Double Ctrl+a → one literal Ctrl+a to the inner program.\n exitLeader();\n opts.onForward(Buffer.from([KEY_LEADER]));\n return;\n }\n if (b === KEY_ESC) {\n // Esc dismisses the menu; nothing forwarded.\n exitLeader();\n return;\n }\n const action = leaderChords[String.fromCharCode(b).toLowerCase()];\n if (action) {\n exitLeader();\n opts.onAction?.(action);\n return;\n }\n // Unrecognized chord: close the menu, forward the key so typing isn't lost.\n exitLeader();\n opts.onForward(Buffer.from([b]));\n };\n\n const settle = (\n answer: PromptAnswerBody['answer'],\n cancelled?: boolean,\n ): void => {\n if (!active) return;\n const body: PromptAnswerBody = {\n id: active.ev.id,\n answer,\n ...(cancelled ? { cancelled: true } : {}),\n };\n const p = active;\n active = null;\n p.resolve(body);\n opts.onAnswer(body);\n };\n\n const handleCapturedByte = (b: number): void => {\n if (!active) return;\n if (b === KEY_Y_LOW || b === KEY_Y_UP) {\n settle('y');\n return;\n }\n if (b === KEY_N_LOW || b === KEY_N_UP) {\n settle('n');\n return;\n }\n if (b === KEY_ESC || b === KEY_CTRL_C) {\n settle('n', true);\n return;\n }\n if (b === KEY_ENTER || b === KEY_LF) {\n // Enter accepts the default answer.\n const def = active.ev.defaultAnswer ?? 'n';\n settle(def);\n return;\n }\n // Anything else: ignored (not forwarded, not consumed).\n };\n\n // Leader-aware steady-state forwarding: scan bytes, batching non-leader\n // runs into a single onForward call, and intercept `Ctrl+a` chords.\n const feedSteady = (buf: Buffer): void => {\n let chunkStart = 0;\n const flushChunk = (end: number): void => {\n if (end > chunkStart) opts.onForward(buf.subarray(chunkStart, end));\n chunkStart = end;\n };\n for (let i = 0; i < buf.length; i++) {\n const byte = buf[i];\n if (byte === undefined) continue;\n if (leader) {\n resolveLeaderByte(byte);\n chunkStart = i + 1;\n continue;\n }\n if (byte === KEY_LEADER) {\n flushChunk(i); // forward everything typed before the Ctrl+a\n chunkStart = i + 1;\n enterLeader();\n }\n }\n flushChunk(buf.length);\n };\n\n return {\n get capturing(): boolean {\n return active !== null;\n },\n feed(buf: Buffer): void {\n if (disposed) return;\n if (active) {\n // A multi-byte read starting with ESC is a CSI/SS3/OSC escape\n // sequence — mouse click (`\\x1b[<…M/m`), arrow / function key,\n // window-focus event, bracketed-paste markers, etc. Drop the\n // whole chunk: the user pressed something we don't model as a\n // confirmation key, and they'd be (correctly) surprised if a stray\n // mouse click registered as \"deny\". A *real* Esc keypress arrives\n // as a single byte in its own read, which still cancels below.\n if (buf.length > 1 && buf[0] === KEY_ESC) return;\n // Process bytes one at a time so a paste of \"yes\\n\" is handled\n // sanely: the 'y' settles, the rest is dropped — we don't want\n // stray bytes leaking to the pty after the prompt closed mid-buf.\n // (After settle, `active` is null; remaining bytes fall through to\n // forward path below.)\n for (let i = 0; i < buf.length; i++) {\n const byte = buf[i];\n if (byte === undefined) continue;\n if (active) {\n handleCapturedByte(byte);\n } else {\n // Active became null mid-buffer (settled). Forward the rest as\n // a normal keystroke chunk.\n opts.onForward(buf.subarray(i));\n return;\n }\n }\n return;\n }\n if (!leaderEnabled) {\n opts.onForward(buf);\n return;\n }\n feedSteady(buf);\n },\n capture(ev: PromptAskEvent): Promise<PromptAnswerBody> {\n return new Promise<PromptAnswerBody>((resolve, reject) => {\n // A relay prompt outranks the actions menu — close the leader first.\n if (leader) exitLeader();\n if (active) {\n // A new prompt arrived before the old one was answered — abort\n // the old one (treated as cancelled) and switch to the new one.\n // The relay already broadcast `prompt-ask` for both; we owe the\n // first an answer or it'll stay pending forever.\n settle('n', true);\n }\n active = { ev, resolve, reject };\n });\n },\n abort(reason): void {\n if (!active) return;\n const p = active;\n active = null;\n const msg = reason === 'pty-exit' ? 'pty exited' : 'resolved by sibling wrapper';\n p.reject(new Error(msg));\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n disarmLeader();\n if (active) {\n const p = active;\n active = null;\n p.reject(new Error('input router disposed'));\n }\n },\n };\n}\n","export interface SidebarBox {\n id: string;\n name: string;\n /** Container state: 'running' | 'paused' | 'stopped' | 'missing' | … */\n state: string;\n /** Activity of the agent this box runs (claude / codex) — 'working' | 'idle'\n * | 'waiting' | 'unknown' | undefined. Resolved from whichever agent is\n * active (see `resolveAgent` in commands/dashboard.ts). */\n activity?: string;\n /** The in-box terminal/session title the active agent set, or undefined. */\n sessionTitle?: string;\n /** 1-based per-project box number, shown as `[N]`; undefined for\n * pre-feature boxes and the synthetic \"+ New box\" entry. */\n index?: number;\n /** Absolute project root; used to group boxes under a project header.\n * Undefined for pre-feature boxes and the synthetic \"+ New box\" entry. */\n project?: string;\n /** This box has an unanswered relay `prompt-ask` event (e.g. agentbox-ctl\n * git push / cp / download waiting for user confirmation). The compositor\n * injects this flag from its in-memory map of active prompts. Overrides\n * the activity cell — `▲ prompt` reads more urgent than `● working`. */\n pendingPrompt?: boolean;\n /** This box has an active relay notice (currently: a checkpoint is being\n * captured, freezing the box). Injected by the compositor; shown as\n * `◆ checkpoint` in the activity cell. Outranked by `pendingPrompt`. */\n checkpointing?: boolean;\n}\n\n/** Per-row ownership + styling map returned alongside the rendered lines so\n * the compositor can highlight the selected box and style headers without\n * re-deriving the (now non-uniform) layout. */\nexport interface SidebarRender {\n lines: string[];\n /** boxId rendered on row `i`, else null (banner / group header / blank). */\n rowOwner: (string | null)[];\n /** true for the banner and project-header rows (styled like the banner). */\n headerRows: boolean[];\n}\n\n/** Truncate to `max` printable chars, appending `…` when it had to cut\n * (keeps the head). */\nfunction ellipsize(s: string, max: number): string {\n if (max <= 0) return '';\n if (s.length <= max) return s;\n if (max === 1) return '…';\n return s.slice(0, max - 1) + '…';\n}\n\n/** Truncate keeping the *tail* (the distinguishing part of a box name like\n * `…-78b94c78`), prepending `…` when it had to cut. */\nfunction ellipsizeHead(s: string, max: number): string {\n if (max <= 0) return '';\n if (s.length <= max) return s;\n if (max === 1) return '…';\n return '…' + s.slice(s.length - (max - 1));\n}\n\nexport function activityCell(b: SidebarBox): string {\n // Pending relay prompt outranks every other state — the user needs to\n // act before whatever the box is doing can continue.\n if (b.pendingPrompt) return '▲ prompt';\n // A checkpoint freezes the box; surface it over the activity state.\n if (b.checkpointing) return '◆ checkpoint';\n if (b.state !== 'running') return `[${b.state}]`;\n switch (b.activity) {\n case 'working':\n return '● working';\n case 'idle':\n return '○ idle';\n case 'waiting':\n return '◐ waiting';\n default:\n return '? unknown';\n }\n}\n\n/** Synthetic sidebar entry pinned at the top: selecting it opens the create\n * menu. Carried in the compositor's box list like a real box (sentinel id),\n * so selection/switch/highlight need no special-casing. */\nexport const NEW_BOX_ID = '__agentbox_new__';\nexport const NEW_BOX_LABEL = '+ New box';\n\n/** Sidebar banner label (rendered into the top border). */\nexport const SIDEBAR_HEADER = 'AgentBox';\n\n/** Top border: a flat line on the top + a rounded corner into the right edge\n * only (no left/bottom, to save space): `──── AgentBox ─────…` filling\n * exactly `w`. The left end is a straight line; the matching rounded\n * top-right corner (`╮`) is drawn by the compositor at the sidebar separator\n * column. */\nfunction topBorder(label: string, w: number): string {\n const lead = `──── ${label} `;\n if (lead.length >= w) return lead.slice(0, w);\n return lead + '─'.repeat(w - lead.length);\n}\n/** Lines `sidebarLines` reserves before the box rows (banner + blank). The\n * compositor uses this to locate the selected box row for highlighting. */\nexport const SIDEBAR_HEADER_LINES = 2;\n\nfunction fit(s: string, w: number): string {\n if (s.length === w) return s;\n if (s.length > w) return s.slice(0, w);\n return s + ' '.repeat(w - s.length);\n}\n\n/** `s` centered in a field of `w` columns (truncated if it doesn't fit). */\nfunction center(s: string, w: number): string {\n if (s.length >= w) return s.slice(0, w);\n const pad = w - s.length;\n const leftPad = Math.floor(pad / 2);\n return ' '.repeat(leftPad) + s + ' '.repeat(pad - leftPad);\n}\n\n/** `basename` of an absolute project root, for the group header label. */\nfunction projectLabel(project: string | undefined): string {\n if (!project) return '(no project)';\n const parts = project.split('/').filter(Boolean);\n return parts[parts.length - 1] ?? project;\n}\n\n/** Strip the leading decoration Claude prepends to its terminal title (the\n * spinner glyph, e.g. `✳ `) plus any leading symbols/asterisks/space, so the\n * sidebar shows just the words. Falls back to the trimmed original if the\n * title is all decoration. */\nfunction stripTitleGlyph(s: string): string {\n const t = s.replace(/^[\\s\\p{S}*·]+/u, '');\n return t.length > 0 ? t : s.trim();\n}\n\n/**\n * Render one box row: `marker<num> <title|name> <status>`. The number and\n * the status are width-protected; the middle (title, else the box name with\n * its meaningful tail kept) flexes and ellipsizes so the status is never\n * eaten. Compact: no brackets, no glyph, single-char marker.\n */\nfunction boxRow(b: SidebarBox, marker: string, w: number): string {\n const numStr = b.index != null ? `${b.index} ` : '';\n const status = activityCell(b);\n const left = `${marker}${numStr}`;\n // -2: 1 gap before status, 1 margin after so the label doesn't touch the\n // sidebar's right border.\n const room = w - left.length - status.length - 2;\n if (room <= 0) return fit(`${left}${status}`, w);\n const middle =\n b.state === 'running' && b.sessionTitle\n ? ellipsize(stripTitleGlyph(b.sessionTitle), room)\n : ellipsizeHead(b.name, room);\n // Left segment padded so the status sits one column in from the right edge,\n // with a trailing space as the right margin.\n return fit(`${left}${middle}`, w - status.length - 1) + status + ' ';\n}\n\n/**\n * The sidebar region as exactly `h` lines, each exactly `w` columns, plus a\n * per-row ownership/style map. Pure — no ANSI positioning (the compositor\n * places it). Boxes are grouped under a ` ── <project> ── ` header (callers\n * pass them pre-sorted by project).\n */\nexport function sidebarLines(\n boxes: SidebarBox[],\n selectedId: string,\n w: number,\n h: number,\n): SidebarRender {\n const lines: string[] = [topBorder(SIDEBAR_HEADER, w), fit('', w)];\n const rowOwner: (string | null)[] = [null, null];\n const headerRows: boolean[] = [true, false];\n const push = (line: string, owner: string | null, header: boolean): void => {\n lines.push(fit(line, w));\n rowOwner.push(owner);\n headerRows.push(header);\n };\n\n let prevProject: string | undefined;\n let seenGroup = false;\n for (const b of boxes) {\n const marker = b.id === selectedId ? '▸' : ' ';\n if (b.id === NEW_BOX_ID) {\n push(`${marker}${NEW_BOX_LABEL}`, b.id, false);\n continue;\n }\n if (!seenGroup || b.project !== prevProject) {\n push(center(` ── ${projectLabel(b.project)} ── `, w), null, true);\n prevProject = b.project;\n seenGroup = true;\n }\n push(boxRow(b, marker, w), b.id, false);\n }\n if (boxes.length === 0) push(' (no boxes)', null, false);\n while (lines.length < h) push('', null, false);\n return {\n lines: lines.slice(0, h),\n rowOwner: rowOwner.slice(0, h),\n headerRows: headerRows.slice(0, h),\n };\n}\n\n/**\n * Centered action menu for a running box with no Claude session.\n * Exactly `h` lines, each exactly `w` columns. Pure.\n */\nexport function menuLines(boxName: string, w: number, h: number): string[] {\n const body = [\n '',\n ` No agent session in ${boxName}.`,\n '',\n ' [c] Start Claude',\n ' [x] Start Codex',\n ' [o] Start OpenCode',\n ' [s] Open a shell',\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then c/s/u/q (code/screen/url/quit)',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n/**\n * Centered action menu for a non-running box (paused/stopped): resume +\n * destroy, with a two-step destroy confirm (the TUI can't show a prompt).\n * Exactly `h` lines, each exactly `w` columns. Pure.\n */\nexport function lifecycleMenuLines(\n boxName: string,\n state: 'paused' | 'stopped',\n confirmDestroy: boolean,\n w: number,\n h: number,\n): string[] {\n const body = confirmDestroy\n ? [\n '',\n ` Destroy ${boxName}?`,\n ' This removes the container and its volumes.',\n '',\n ' [y] Yes, destroy',\n ' [any other key] Cancel',\n ]\n : [\n '',\n ` Box ${boxName} is ${state}.`,\n '',\n state === 'paused' ? ' [u] Unpause' : ' [s] Start',\n ' [d] Destroy',\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then q quit',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n/**\n * Centered menu for the synthetic \"+ New box\" entry. Exactly `h` lines, each\n * exactly `w` columns. Pure.\n */\nexport function createMenuLines(where: string, w: number, h: number): string[] {\n const body = [\n '',\n ' Create a new box',\n '',\n ' [c] Create + launch Claude',\n ' [x] Create + launch Codex',\n ' [o] Create + launch OpenCode',\n ' [n] Create only',\n '',\n ` in ${where}`,\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then q quit',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n// Status-bar palette — matches the in-box tmux footer\n// (`buildTmuxSessionArgs`): dark bar, blue brand block, dim-grey hints\n// with white key chords.\n/** The footer/sidebar background gray. Truecolor (not palette index 236) so\n * it pins an exact RGB — terminals can remap/shade indexed colors per\n * context, which made the sidebar and status bar look like different grays.\n * Single source so the two regions can't drift. */\nexport const BAR_BG = '\\x1b[48;2;48;48;48m';\nconst BAR_BASE = BAR_BG + '\\x1b[38;5;250m';\nconst BAR_BRAND = '\\x1b[48;5;39m\\x1b[38;5;16m'; // blue block (not bold)\nconst BRAND_BOLD = '\\x1b[1m'; // box name only\nconst BRAND_NOBOLD = '\\x1b[22m';\nconst HINT_KEY = '\\x1b[38;5;255m'; // white: the key chord\nconst HINT_TXT = '\\x1b[38;5;245m'; // gray: labels + separators\nconst BAR_RESET = '\\x1b[0m';\n\n// [key chord, label]. Modifiers spelled out (no ⌥/^ glyphs); arrows use the\n// ↑/↓ glyphs. Rendered as `KEYS: label` with the chord white, label gray.\nconst SWITCH_HINT: readonly [string, string] = ['Control+Option+↑/↓', 'switch'];\nconst HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n SWITCH_HINT,\n ['Control+a c', 'code'],\n ['Control+a s', 'screen'],\n ['Control+a u', 'url'],\n ['Control+a q', 'quit'],\n];\n\n/** Minimal hint tier when the bar is too narrow for the full `HINT_GROUPS`:\n * box switching (always important) + the leader. Pressing `Ctrl-a` then\n * expands to `ADVANCED_HINT_GROUPS` (the compositor swaps while the leader is\n * active). */\nexport const COLLAPSED_HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n SWITCH_HINT,\n ['Control+a', 'more'],\n];\n\n/** The expanded \"which-key\" chord menu shown while the Ctrl-a leader is\n * pending — every chord, compact (`KEY: label`), reverts on the next key. */\nexport const ADVANCED_HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n ['t', 'stop'],\n ['p', 'pause'],\n ['d', 'destroy'],\n ['q', 'quit'],\n];\n\n/**\n * Status line, exactly `w` printable columns, colored to match the in-box tmux\n * footer (dark bar, blue ` agentbox ▸ … ` brand block on the left, dim-grey\n * shortcut hints on the right). `stateLabel` overrides the box's activity text\n * (used for `shell` / `menu` panes where the box `activity` would otherwise\n * show a misleading `unknown`). `fallbackGroups`, when given, is the narrow-bar tier\n * tried before brand-core-only — used to keep one essential chord pinned\n * (instead of the default dashboard `COLLAPSED_HINT_GROUPS`).\n */\nexport function statusLine(\n box: SidebarBox | undefined,\n w: number,\n stateLabel?: string,\n groups: ReadonlyArray<readonly [string, string]> = HINT_GROUPS,\n fallbackGroups?: ReadonlyArray<readonly [string, string]>,\n): string {\n const state =\n stateLabel ?? (box ? (box.state === 'running' ? (box.activity ?? 'unknown') : box.state) : '');\n // \"agentbox ▸ \" stays normal weight; only the box name + state are bold.\n const brandPrefix = box ? ' agentbox ▸ ' : ' agentbox ';\n // Brand *core* (no title) — the width-protected segment. The title is the\n // lowest-priority segment: it only fills space left after brand + hints.\n const base = box ? `${box.name} (${state})` : '';\n const coreMain = box ? `${base} ` : '';\n const corePlain = brandPrefix + coreMain;\n\n const SEP = ' │ ';\n const renderHints = (\n g: ReadonlyArray<readonly [string, string]>,\n ): { plain: string; styled: string } => ({\n plain: g.map(([k, l]) => `${k}: ${l}`).join(SEP) + ' ',\n styled:\n g.map(([k, l]) => `${HINT_KEY}${k}${HINT_TXT}: ${l}`).join(`${HINT_TXT}${SEP}`) + ' ',\n });\n\n // Hint tier: shortcuts beat the title. Try the requested groups; if the\n // brand core + those hints overflow, fall back to the narrow-bar tier\n // (`fallbackGroups` if supplied, else the dashboard's minimal leader hint);\n // if even that overflows, render brand-core-only (title can never push the\n // box name off-screen).\n let hints: { plain: string; styled: string } | null = null;\n for (const g of [groups, fallbackGroups ?? COLLAPSED_HINT_GROUPS]) {\n const h = renderHints(g);\n if (corePlain.length + h.plain.length + 1 <= w) {\n hints = h;\n break;\n }\n }\n if (!hints) {\n return BAR_BASE + BAR_BRAND + fit(corePlain, w) + BAR_RESET;\n }\n\n // Title fills only the leftover, ellipsized; dropped entirely when there's\n // no meaningful room (≈ ` — ` + a few chars). Capped at 40 cols as before.\n const room = w - corePlain.length - hints.plain.length - 1;\n let titleSeg = '';\n if (box?.sessionTitle && room >= 7) {\n titleSeg = ` — ${ellipsize(box.sessionTitle, Math.min(40, room - 3))}`;\n }\n\n const leftPlain = brandPrefix + base + titleSeg + (box ? ' ' : '');\n const leftStyled =\n BAR_BRAND + brandPrefix + BRAND_BOLD + base + titleSeg + (box ? ' ' : '') + BRAND_NOBOLD;\n const gap = w - leftPlain.length - hints.plain.length;\n // brand block (name + title bold) → base bar → gap → white/gray hints.\n return (\n BAR_BASE +\n leftStyled +\n BAR_BASE +\n ' '.repeat(gap) +\n hints.styled +\n BAR_RESET\n );\n}\n","import { BAR_BG, statusLine, type SidebarBox } from '../dashboard/sidebar.js';\nimport type { PromptAskEvent } from '@agentbox/relay';\n\n/**\n * Footer rendering state. `idle` reuses the dashboard's `statusLine` shape\n * (brand chip + box name + optional session title + right-aligned hint);\n * `prompt` is shown while a `prompt-ask` event is being captured; `notice`\n * is an animated informational warning (e.g. checkpoint in progress);\n * `flash` is a transient confirmation after a Ctrl+a action fires.\n */\nexport type FooterState =\n | {\n kind: 'idle';\n boxName: string;\n /** Claude's tmux pane title (from BoxStatus.claude.sessionTitle).\n * Undefined until the first status poll completes (or in shell mode). */\n sessionTitle?: string;\n /** Claude activity hint shown in `(<state>)` after the name. Same field\n * the dashboard sidebar uses (`working` / `idle` / `waiting` / etc.). */\n claudeActivity?: string;\n /** Mode drives the state label: claude shows claude activity, the\n * others show `(shell)` / `(codex)` / `(opencode)`. */\n mode: 'claude' | 'shell' | 'codex' | 'opencode';\n /** Whether the session can be detached (tmux-backed). Drives the\n * expanded leader menu + the pinned `Control+a d: detach` hint. */\n detachable?: boolean;\n /** True while the Ctrl+a leader menu is open — swaps the collapsed\n * `Control+a: Actions` hint for the expanded chord list. */\n leaderActive?: boolean;\n }\n | { kind: 'prompt'; prompt: PromptAskEvent }\n | {\n kind: 'notice';\n /** Warning text, e.g. \"Checkpoint in progress — …\". */\n message: string;\n /** Monotonic counter; the spinner glyph is `SPINNER_FRAMES[frame % len]`. */\n frame: number;\n }\n | {\n kind: 'flash';\n /** Transient confirmation text, e.g. \"Opening noVNC viewer…\". */\n message: string;\n };\n\n/**\n * Spinner cycle for the `notice` footer. Solid half-filled circles, not\n * braille: braille glyphs read as a faint dot cluster on the yellow banner\n * (set vs unset dots are hard to tell apart), so the motion gets lost. The\n * rotating black half of these is unambiguous.\n */\nexport const SPINNER_FRAMES = ['◐', '◓', '◑', '◒'] as const;\n\nconst URGENT = '\\x1b[38;5;220m\\x1b[1m'; // bright yellow + bold (active prompt)\nconst TXT = '\\x1b[38;5;250m'; // dim gray body text\nconst SUBTLE = '\\x1b[38;5;245m'; // very dim (Y/N hint)\nconst RESET = '\\x1b[0m';\n// Notice footer = a full-width warning banner: bright yellow background with\n// near-black bold text. High contrast so the \"box is frozen\" state is\n// unmissable — deliberately louder than the dim-on-dark idle/prompt bars.\nconst NOTICE_BG = '\\x1b[48;5;220m'; // bright yellow background\nconst NOTICE_FG = '\\x1b[38;5;16m\\x1b[1m'; // near-black + bold text\n// Flash footer = a calm one-line confirmation on the normal dark bar.\nconst FLASH_FG = '\\x1b[38;5;150m\\x1b[1m'; // soft green + bold\n\n/** Collapsed idle hint (plain `--no-tmux` shell) — the leader is hidden\n * behind one chord. */\nconst COLLAPSED_HINTS_PLAIN: ReadonlyArray<readonly [string, string]> = [\n ['Control+a', 'Actions'],\n];\n/** Collapsed idle hint (detachable session) — the detach chord stays pinned\n * on the right even while the actions menu is closed. */\nconst COLLAPSED_HINTS_DETACHABLE: ReadonlyArray<readonly [string, string]> = [\n ['Control+a', 'Actions'],\n ['Control+a d', 'detach'],\n];\n/** Narrow-bar fallback for a detachable session: drop the `Actions` hint\n * first, but never the detach chord. */\nconst DETACH_PIN_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['Control+a d', 'detach'],\n];\n/** Expanded which-key menu shown while the Ctrl+a leader is open. A\n * detachable (tmux-backed) session also gets `d: detach`; a plain shell\n * has nothing to detach from. */\nconst DETACHABLE_LEADER_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n ['d', 'detach'],\n];\nconst PLAIN_LEADER_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n];\n\n/**\n * Truncate `s` to exactly `width` visible columns, padding with spaces when\n * shorter. ANSI SGR sequences must NOT be present in the input.\n */\nfunction padTo(visible: string, width: number): string {\n if (visible.length === width) return visible;\n if (visible.length > width) {\n if (width <= 1) return visible.slice(0, width);\n return visible.slice(0, width - 1) + '…';\n }\n return visible + ' '.repeat(width - visible.length);\n}\n\n/**\n * Render the footer row as a single ANSI string. Caller positions the\n * cursor at the last row, col 0 before writing, and restores it afterwards.\n * Always ends with SGR reset so the inner pty's next byte starts clean.\n */\nexport function renderFooter(state: FooterState, cols: number): string {\n if (cols <= 0) return '';\n if (state.kind === 'idle') {\n const sidebarBox: SidebarBox = {\n id: '', // unused by statusLine\n name: state.boxName,\n state: 'running', // we're attached, so the container is up\n activity: state.claudeActivity,\n sessionTitle: state.sessionTitle,\n };\n const isClaude = state.mode === 'claude';\n const detachable = state.detachable ?? isClaude;\n // Shell/codex modes have no claude activity to surface — passing\n // `stateLabel` overrides statusLine's default (which would otherwise show\n // `(unknown)` because `claudeActivity` is undefined and the container is\n // running).\n const stateLabel = isClaude ? undefined : state.mode === 'shell' ? 'shell' : state.mode;\n if (state.leaderActive) {\n const leaderHints = detachable ? DETACHABLE_LEADER_HINTS : PLAIN_LEADER_HINTS;\n return statusLine(sidebarBox, cols, stateLabel, leaderHints);\n }\n // Collapsed: a detachable session keeps the detach chord pinned on the\n // right (its narrow-bar fallback drops `Actions` first, never `detach`).\n const collapsed = detachable ? COLLAPSED_HINTS_DETACHABLE : COLLAPSED_HINTS_PLAIN;\n const fallback = detachable ? DETACH_PIN_HINTS : undefined;\n return statusLine(sidebarBox, cols, stateLabel, collapsed, fallback);\n }\n if (state.kind === 'flash') {\n // Flash state: a brief \"<arrow> <message>\" confirmation on the dark bar.\n const prefix = ' ▸ '; // ▸\n const inner = Math.max(0, cols - prefix.length);\n const message = padTo(state.message, inner);\n return `${BAR_BG}${FLASH_FG}${prefix}${TXT}${message}${RESET}`;\n }\n if (state.kind === 'notice') {\n // Notice state: \"<spinner> <message>\" rendered as a full-width\n // high-contrast yellow warning banner. The spinner reassures the user\n // the box is busy, not stuck.\n const spinner = SPINNER_FRAMES[state.frame % SPINNER_FRAMES.length]!;\n const prefix = ` ${spinner} `;\n const inner = Math.max(0, cols - prefix.length);\n const message = padTo(state.message, inner);\n return `${NOTICE_BG}${NOTICE_FG}${prefix}${message}${RESET}`;\n }\n // Prompt state: \"[!] <message> [detail] [y/N]\"\n // The y/N hint is suffixed; we squeeze the message+detail into the space\n // left over (truncating message first, then detail).\n const def = state.prompt.defaultAnswer ?? 'n';\n const yn = def === 'y' ? '[Y/n]' : '[y/N]';\n const tag = ' [!] ';\n const sep = ' ';\n const hintW = ` ${yn} `.length;\n const inner = Math.max(0, cols - tag.length - hintW);\n const detailRaw = state.prompt.detail ?? '';\n let message = state.prompt.message;\n let detail = detailRaw;\n const messageBudget = Math.max(8, inner - (detail.length > 0 ? sep.length + 8 : 0));\n if (message.length > messageBudget) {\n message = message.slice(0, Math.max(0, messageBudget - 1)) + '…';\n }\n const usedByMessage = message.length;\n const detailBudget = Math.max(0, inner - usedByMessage - sep.length);\n if (detail.length > detailBudget) {\n detail = detailBudget <= 1 ? '' : detail.slice(0, detailBudget - 1) + '…';\n }\n const middlePlain = detail.length > 0 ? `${message}${sep}${detail}` : message;\n const padded = padTo(middlePlain, inner);\n return `${BAR_BG}${URGENT}${tag}${TXT}${padded}${SUBTLE} ${yn} ${RESET}`;\n}\n\n/**\n * ANSI sequence to move the cursor to (row, col) — 1-based, terminal convention.\n */\nexport function cursorMoveTo(row: number, col: number): string {\n return `\\x1b[${String(row)};${String(col)}H`;\n}\n\nexport const CURSOR_SAVE = '\\x1b7';\nexport const CURSOR_RESTORE = '\\x1b8';\n\n/**\n * Synchronized output toggles (DECSET/DECRST 2026). Wrap a multi-write\n * footer paint so terminals that support it commit one atomic frame.\n */\nexport const SYNC_BEGIN = '\\x1b[?2026h';\nexport const SYNC_END = '\\x1b[?2026l';\n","import { request as httpRequest, type IncomingMessage } from 'node:http';\nimport { request as httpsRequest } from 'node:https';\nimport type { BoxNoticeEvent, PromptAnswerBody, PromptAskEvent } from '@agentbox/relay';\n\n/**\n * SSE subscription back to the relay's `GET /admin/prompts/stream`. The\n * relay pushes:\n * - `event: prompt-ask` data: PromptAskEvent (with id)\n * - `event: prompt-resolved` data: { id }\n * - `event: notice-set` data: BoxNoticeEvent (with id)\n * - `event: notice-clear` data: { id }\n * - `event: ping` data: { ts }\n *\n * We reconnect with exponential backoff on any error or close — the only\n * way to know the relay is back is to keep trying. Subscribers are\n * loopback-only so latency is sub-ms.\n */\nexport interface PromptStream {\n /** Stop subscribing; aborts any in-flight reconnect attempt. */\n close(): void;\n}\n\nexport interface SubscribeOptions {\n relayBaseUrl: string;\n boxId: string;\n onPrompt: (ev: PromptAskEvent) => void;\n /** Server-driven: a sibling wrapper (or this one) answered; the run loop\n * clears the footer for stale ids it didn't originate. */\n onResolved: (id: string) => void;\n /** A box-level informational notice was set (e.g. checkpoint in progress). */\n onNotice?: (ev: BoxNoticeEvent) => void;\n /** A previously-set notice was cleared (explicitly or via its TTL). */\n onNoticeCleared?: (id: string) => void;\n onError?: (err: Error) => void;\n}\n\nconst INITIAL_BACKOFF_MS = 200;\nconst MAX_BACKOFF_MS = 5_000;\n\nexport function subscribePrompts(opts: SubscribeOptions): PromptStream {\n let closed = false;\n let req: ReturnType<typeof httpRequest> | null = null;\n let res: IncomingMessage | null = null;\n let reconnectTimer: NodeJS.Timeout | null = null;\n let backoffMs = INITIAL_BACKOFF_MS;\n let url: URL;\n try {\n url = new URL(opts.relayBaseUrl);\n } catch (err) {\n if (opts.onError) opts.onError(err instanceof Error ? err : new Error(String(err)));\n return { close: () => {} };\n }\n const isHttps = url.protocol === 'https:';\n const transport = isHttps ? httpsRequest : httpRequest;\n const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;\n\n function scheduleReconnect(): void {\n if (closed) return;\n const delay = backoffMs;\n backoffMs = Math.min(MAX_BACKOFF_MS, backoffMs * 2);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n connect();\n }, delay);\n if (typeof reconnectTimer.unref === 'function') reconnectTimer.unref();\n }\n\n /**\n * SSE message parser: server sends `event: <type>\\n` then `data: <json>\\n\\n`.\n * The relay never splits an event across writes (one chunk per dispatch),\n * but we still buffer by message boundary `\\n\\n` so a mid-message slice\n * doesn't corrupt parsing.\n */\n let buffer = '';\n function consumeMessages(): void {\n let idx = buffer.indexOf('\\n\\n');\n while (idx !== -1) {\n const raw = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n idx = buffer.indexOf('\\n\\n');\n // Drop the SSE comment line we send on connect (`: connected`).\n if (raw.startsWith(':')) continue;\n let event = '';\n let dataLine = '';\n for (const line of raw.split('\\n')) {\n if (line.startsWith('event:')) event = line.slice('event:'.length).trim();\n else if (line.startsWith('data:')) dataLine = line.slice('data:'.length).trim();\n }\n if (event === 'prompt-ask' && dataLine.length > 0) {\n try {\n const ev = JSON.parse(dataLine) as PromptAskEvent;\n if (ev && typeof ev.id === 'string') opts.onPrompt(ev);\n } catch {\n /* malformed; relay should never send this — ignore rather than die */\n }\n } else if (event === 'prompt-resolved' && dataLine.length > 0) {\n try {\n const payload = JSON.parse(dataLine) as { id?: string };\n if (payload && typeof payload.id === 'string') opts.onResolved(payload.id);\n } catch {\n /* malformed; ignore */\n }\n } else if (event === 'notice-set' && dataLine.length > 0) {\n try {\n const ev = JSON.parse(dataLine) as BoxNoticeEvent;\n if (ev && typeof ev.id === 'string') opts.onNotice?.(ev);\n } catch {\n /* malformed; ignore */\n }\n } else if (event === 'notice-clear' && dataLine.length > 0) {\n try {\n const payload = JSON.parse(dataLine) as { id?: string };\n if (payload && typeof payload.id === 'string') opts.onNoticeCleared?.(payload.id);\n } catch {\n /* malformed; ignore */\n }\n }\n // 'ping' has no caller-visible side effect — its purpose is to keep\n // the socket from going idle and to let the wrapper detect dead links\n // via socket-level errors. No-op here.\n }\n }\n\n function connect(): void {\n if (closed) return;\n req = transport({\n host: url.hostname,\n port,\n method: 'GET',\n path: `${url.pathname.replace(/\\/$/, '')}/admin/prompts/stream?boxId=${encodeURIComponent(opts.boxId)}`,\n headers: { Accept: 'text/event-stream' },\n });\n req.on('response', (r) => {\n res = r;\n if (r.statusCode !== 200) {\n // 400/403 — relay says \"no for you\"; bail without retrying since\n // these are config errors (no boxId, not loopback) that won't fix\n // themselves.\n if (opts.onError) opts.onError(new Error(`SSE stream returned ${String(r.statusCode)}`));\n r.resume();\n close();\n return;\n }\n backoffMs = INITIAL_BACKOFF_MS; // reset on a healthy connect\n r.setEncoding('utf8');\n r.on('data', (chunk: string) => {\n buffer += chunk;\n consumeMessages();\n });\n r.on('end', () => {\n if (!closed) scheduleReconnect();\n });\n r.on('error', () => {\n if (!closed) scheduleReconnect();\n });\n });\n req.on('error', () => {\n if (!closed) scheduleReconnect();\n });\n req.end();\n }\n\n function close(): void {\n if (closed) return;\n closed = true;\n if (reconnectTimer) clearTimeout(reconnectTimer);\n try {\n res?.destroy();\n } catch {\n /* best-effort */\n }\n try {\n req?.destroy();\n } catch {\n /* best-effort */\n }\n }\n\n connect();\n return { close };\n}\n\n/**\n * POST a PromptAnswerBody to /admin/prompts/answer. Fire-and-(mostly)-\n * forget: we don't retry on failure because the relay's `prompts.resolve`\n * is idempotent and a double-resolve returns 404. If the relay was dead,\n * the SSE reconnect loop will repush any prompts that are still pending.\n */\nexport interface PostAnswerOptions {\n relayBaseUrl: string;\n body: PromptAnswerBody;\n}\n\nexport interface PostAnswerResult {\n ok: boolean;\n status: number;\n}\n\nexport function postAnswer(opts: PostAnswerOptions): Promise<PostAnswerResult> {\n return new Promise<PostAnswerResult>((resolve) => {\n let url: URL;\n try {\n url = new URL(opts.relayBaseUrl);\n } catch {\n resolve({ ok: false, status: 0 });\n return;\n }\n const isHttps = url.protocol === 'https:';\n const transport = isHttps ? httpsRequest : httpRequest;\n const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;\n const json = JSON.stringify(opts.body);\n const req = transport(\n {\n host: url.hostname,\n port,\n method: 'POST',\n path: `${url.pathname.replace(/\\/$/, '')}/admin/prompts/answer`,\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json).toString(),\n },\n timeout: 3000,\n },\n (res) => {\n res.resume();\n const status = res.statusCode ?? 0;\n // 204 = accepted; 404 = already answered (idempotent). Both are \"done\".\n resolve({ ok: status === 204 || status === 404, status });\n },\n );\n req.on('error', () => resolve({ ok: false, status: 0 }));\n req.on('timeout', () => {\n req.destroy();\n resolve({ ok: false, status: 0 });\n });\n req.write(json);\n req.end();\n });\n}\n","import { DEFAULT_RELAY_PORT } from '@agentbox/sandbox-docker';\nimport type { BoxRecord } from '@agentbox/core';\nimport type { AttachOpenIn } from '@agentbox/config';\nimport { providerForBox } from '../provider/registry.js';\nimport { runWrappedAttach } from '../wrapped-pty/index.js';\n\nconst RELAY_HOST_URL = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;\n\n/**\n * Attach to (or create) a tmux session inside a cloud sandbox over SSH and\n * run an agent CLI inside it. Shared between `agentbox claude`/`codex`/\n * `opencode` so the SSH + tmux mechanics live in one place.\n *\n * The inner command tmux runs is `bash -lc 'exec <binary>'`:\n * - login shell so `/home/vscode/.local/bin` is on PATH and `/etc/profile.d/\n * agentbox.sh` exports `AGENTBOX_BOX_*` env;\n * - `exec` so the agent gets PID 2 (Ctrl-c in the agent kills the session\n * cleanly rather than dropping to bash).\n *\n * When `extraArgs` is non-empty, we base64-encode the argv (one arg per line)\n * and hand the inner shell a small `mapfile`-based launcher that reconstructs\n * the array — see `buildCloudAttachInnerCommand`. Base64 is alphanumeric+`/+=`\n * so it survives every shell-quoting layer (host single-quote, SSH, tmux,\n * bash) untouched, which avoids the 3-layer escaping mess the literal form\n * would otherwise require.\n */\nexport interface CloudAgentAttachArgs {\n box: BoxRecord;\n /** In-sandbox binary path or name (`claude`, `codex`, `opencode`). */\n binary: string;\n /** Tmux session name (e.g. `claude`). */\n sessionName: string;\n /** Mode label for the wrapper's footer. */\n mode: 'claude' | 'codex' | 'opencode';\n /**\n * Extra args the user typed after `--`. Passed through to the in-box agent\n * verbatim via a base64-encoded launcher. Limitation: args containing\n * literal `\\n` aren't supported (none of claude/codex/opencode flags do).\n */\n extraArgs?: string[];\n /**\n * Where to open the attached session in the host's terminal (`split`/`window`/\n * `tab`/`same`). Forwarded to `runWrappedAttach`. Daytona attaches are forced\n * to `same` for now because `provider.buildAttach()` may return a `cleanup`\n * that tears down per-call SSH tunnels — running cleanup while a detached\n * new pane still holds the connection would kill the pane. Hetzner's\n * ControlMaster is per-box-lifetime so spawn-and-detach is safe there.\n */\n openIn?: AttachOpenIn;\n}\n\n/**\n * Render the inner shell command tmux runs inside the cloud sandbox. Exported\n * so unit tests can exercise the base64 round-trip without spinning up SSH.\n *\n * Empty `extraArgs` keeps the no-args path identical to the pre-args\n * behaviour — `bash -lc 'exec <binary>'` with a backslash-space so the outer\n * shell-quoting layers don't split `exec` from the binary name.\n */\nexport function buildCloudAttachInnerCommand(binary: string, extraArgs?: string[]): string {\n if (!extraArgs || extraArgs.length === 0) {\n return `bash -lc exec\\\\ ${binary}`;\n }\n // One arg per line, base64-encoded. The launcher runs `mapfile -t A` against\n // the decoded stream, then `exec <binary> \"${A[@]}\"` so each arg lands as\n // its own argv element — quotes/spaces inside an arg are preserved exactly\n // because base64 is opaque to every outer shell quoting pass.\n const blob = Buffer.from(extraArgs.join('\\n'), 'utf8').toString('base64');\n // **bash -lc body MUST be single-quoted, not double-quoted.** When tmux\n // launches the session command, it goes through `/bin/sh -c <cmd>`. If we\n // double-quote, sh's parser sees `\"${A[@]}\"` and expands it eagerly —\n // before mapfile ever runs — to the empty string, so claude is invoked as\n // `claude \"\"` and the wizard's initial prompt is silently dropped. Single\n // quotes are inert in sh's parser: the literal `${A[@]}` reaches bash,\n // which expands it AFTER mapfile populates A. The outer shellSingle wrap\n // in renderInnerCommand re-escapes any internal `'` as `'\\''`, so this\n // composes fine.\n return `bash -lc 'mapfile -t A < <(echo ${blob} | base64 -d); exec ${binary} \"\\${A[@]}\"'`;\n}\n\nexport async function cloudAgentAttach(args: CloudAgentAttachArgs): Promise<void> {\n const provider = await providerForBox(args.box);\n if (!provider.buildAttach) {\n throw new Error(`provider '${provider.name}' does not support interactive attach`);\n }\n const command = buildCloudAttachInnerCommand(args.binary, args.extraArgs);\n const spec = await provider.buildAttach(args.box, 'agent', {\n sessionName: args.sessionName,\n command,\n });\n // Daytona-only: force inline attach. `spec.cleanup` would otherwise run as\n // soon as the host process returns from the spawn (before the new pane has\n // released the per-call SSH tunnel), breaking the detached attach.\n const safeOpenIn: AttachOpenIn | undefined =\n args.box.provider === 'daytona' ? 'same' : args.openIn;\n try {\n const code = await runWrappedAttach({\n container: args.box.name,\n command: spec.argv[0],\n dockerArgv: spec.argv.slice(1),\n relayBaseUrl: RELAY_HOST_URL,\n boxId: args.box.id,\n boxName: args.box.name,\n projectIndex: args.box.projectIndex,\n mode: args.mode,\n detachable: true,\n openIn: safeOpenIn,\n });\n process.exit(code);\n } finally {\n if (spec.cleanup) await spec.cleanup();\n }\n}\n"],"mappings":"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","names":["spawn","spawn","inner","message","spawn","exitCode"]}
|