AI_SBX_LAUNCH=tmux (or run --launch tmux) attaches to a three-window tmux session - agent, edit, shell - instead of the bare agent. The launcher is vendored at tasks/ai/workspace and installed into the sandbox, so a custom image and this task cannot drift. It is invoked through a login shell because /etc/sandbox-persistent.sh is where PATH, the mise shims, the AWS credentials and every secret placeholder live, and the tmux server hands that environment to all three windows. AI_SBX_DOTFILES=chezmoi renders the host chezmoi target state and unpacks it into the sandbox, so no dotfiles repository, decryption key or network access is needed inside. chezmoi archive decrypts as it renders, so the target list is an allowlist, encrypted files resolving inside it are refused, and the rendered archive is scanned for credential shapes before it enters the sandbox. Both default to off; with neither set, run behaves exactly as before.
63 lines
2.1 KiB
Python
63 lines
2.1 KiB
Python
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Runs inside the sandbox as ai-sbx-workspace. The caller must start it from a
|
|
# login shell: the tmux server inherits this process's environment, so PATH, the
|
|
# mise shims, the AWS credentials and every secret placeholder reach all three
|
|
# windows through it. A non-login shell here loses the lot, and the symptom is
|
|
# "npm cannot authenticate" rather than anything that points at tmux.
|
|
|
|
SESSION=ai-sbx
|
|
|
|
# The claude invocation is observed, not assumed: sampling the process table of
|
|
# a sandbox attached with "sbx run" shows this exact command line. Getting it
|
|
# wrong would silently change the agent's permission model, so agents whose
|
|
# invocation has not been observed fall back to the bare name.
|
|
agent_command() {
|
|
case "$1" in
|
|
claude)
|
|
printf '%s' 'claude --dangerously-skip-permissions'
|
|
;;
|
|
*)
|
|
printf '%s' "$1"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main() {
|
|
local agent="${1:-claude}"
|
|
local agent_cmd
|
|
agent_cmd="$(agent_command "$agent")"
|
|
|
|
if ! command -v tmux >/dev/null 2>&1; then
|
|
printf '%s\n' \
|
|
'tmux is not installed in this sandbox; starting the agent directly.' \
|
|
'' \
|
|
'Add tmux to AI_SBX_TOOLS, or use a custom image that carries it:' \
|
|
'' \
|
|
' AI_SBX_TOOLS = "bun tmux neovim"' \
|
|
'' >&2
|
|
# Deliberate word splitting: the command comes from the mapping above.
|
|
# shellcheck disable=SC2086
|
|
exec $agent_cmd
|
|
fi
|
|
|
|
# Attach-or-create. Detaching and re-running must land back in the same
|
|
# session with the agent's context intact, which is most of the point.
|
|
if tmux has-session -t "$SESSION" 2>/dev/null; then
|
|
exec tmux attach-session -t "$SESSION"
|
|
fi
|
|
|
|
tmux new-session -d -s "$SESSION" -n agent -c "$PWD"
|
|
tmux new-window -t "$SESSION:" -n edit -c "$PWD"
|
|
tmux new-window -t "$SESSION:" -n shell -c "$PWD"
|
|
|
|
tmux send-keys -t "$SESSION:agent" "$agent_cmd" C-m
|
|
tmux send-keys -t "$SESSION:edit" 'nvim .' C-m
|
|
|
|
tmux select-window -t "$SESSION:agent"
|
|
exec tmux attach-session -t "$SESSION"
|
|
}
|
|
|
|
main "$@"
|