The hidden shell
The agent runs its shell commands in a hidden shell. This is a real zsh that efrd
starts for each conversation. It reads your startup files, so your aliases, functions
and PATH work for the agent too. You do not see the hidden shell's screen. You see
each command and its output in the turn view.
How the hidden shell starts
efrd starts the hidden shell of a conversation at the first shell call that needs
it. The shell is an interactive login zsh. It reads your
.zshenv, .zprofile, .zshrc and .zlogin as a terminal of yours does. When no
zsh is on the PATH, efrd uses /bin/sh.
The hidden shell keeps its state between calls: the directory, the variables, the
functions and the aliases. When you cd between two prompts, your next prompt moves
the hidden shell to your new directory too, and the shell keeps its variables. The
agent then works where you are.
Your .zshrc
The hidden shell has EFR_HIDDEN_SHELL=1 in its environment. If your .zshrc runs
exec tmux or an instant prompt, skip that part when the variable is set:
if [[ -z $EFR_HIDDEN_SHELL ]]; then
# exec tmux, instant prompt and the like
fi
The efr plugin itself does nothing in the hidden shell.
What efr changes in its environment
-
Nobody reads a pager in the hidden shell. So
PAGER,GIT_PAGER,MANPAGERand the other pager variables arecat.git logandsystemctl statusdo not wait for you to quitless. -
Nobody can use an editor there either.
EDITOR,VISUAL,GIT_EDITORand the like name a small script that fails at once:efr: there is no editor in the hidden shell; pass the text another way, such as git commit -m, a file, or ask the user to edit itSo
git commitwithout-morgit rebase -ifails at once and does not wait. -
SCRATCHnames the scratch directory of the conversation, in every command of the agent: in the sandbox, outside it and in a job. ASCRATCHthat your startup files set does not reach the agent's commands. Your own shell does not change. -
efrd gives its own environment to the hidden shell. Do not put an API key in the environment of efrd: in
manual,cautiousandfull, a command of the agent can read it withenv. Only theautosandbox removes names such as*API_KEY*. Useefr login. Models, providers and login tells how.
When the hidden shell closes
efrd closes a hidden shell after shell.idle_minutes (60) with no output and no
input, while it waits at its prompt and nobody watches it. The next call starts a new
shell. The hidden shell also ends when efrd stops, and a command that runs in it ends
too.
A command that asks for input
While a command of the agent runs, its last output lines show below its line. When the command waits for an answer, type it and press Enter.
- A password. efr does not show what you type. The agent sees a password only if the program that asked for it prints it. The prompt text comes from the command, so check which command asks before you answer.
- A plain question, such as
[Y/n], shows as you type it. The agent sees your answer if the program shows it. - No prompt. If a command prints nothing for 10 seconds and efr sees no prompt, a
dim line offers
Ctrl+\. Press it to type an input for the command. efr does not show this input as you type it. Before you pressCtrl+\, what you type goes to the input row, never to the command.
While the last output line of the command looks like a password prompt, the input row hides. What you type then is not shown and never goes to the model.
In auto, efr never types an answer that may be a password into a command in the
sandbox: such a prompt stops the command.
sudo and ssh
A command outside the sandbox that may ask for a password, such as sudo, ssh, or
rsync, scp or git over ssh, can run past the agent's timeout while you follow
the turn. The limit is shell.interactive_timeout_minutes (60). The same is true for
any command that waits for input when its timeout passes.
As in any terminal, sudo remembers your password for a few minutes in the hidden
shell. To make it forget the password after each call, set this in config.toml:
[shell]
sudo_cache = "per_call"
Each sudo call asks for your approval with either value.
A busy hidden shell
A shell call answers the agent at its timeout. When its command still runs then, the
command goes on in the hidden shell, such as a slow build, or an rsync that asked
for a password after its call answered. The hidden shell takes no other call until
that command ends. efr tells you so:
-
The call shows
still runs in the hidden shellin place of its check mark. -
While you follow a turn of the conversation and the command waits for input, an answer line asks for it. It names the command, who approved the call (
you approved it,your rule allowed it) and the row of the screen that asks, such asu@h's password:. Read that row before you answer: it names the program and the host that ask. Enter sends the answer. Ctrl+C stops the command, not the turn. -
At the end of the turn, one line names the command:
the hidden shell still runs: rsync -a ... (it waits for a password)When you can answer there, efr stays open with
enter answers · ctrl+c stops it · esc leaves it. Otherwise the line adds· ,shell answers or stops it, and efr ends. -
When no terminal showed it, your next prompt shows a notice, such as:
efr: <title>: the hidden shell still runs rsync -a ... and waits for a password (asked by "u@h's password:"; you approved it). Type the password only after ,shell; never at this prompt.
Never type the password at your own prompt. zsh runs it as a command and keeps it in your history. In sticky mode, it goes to the agent.
,shell
,shell answers or stops the command. It works in sticky mode too. efr shell [conversation] does the same by hand. It shows the command, how long it runs, who
approved it and the row where it waits, or the hidden shell is free. It stays inline
until the command ends.
| Key | What it does |
|---|---|
| Enter | Answer the wait. A password is not shown. |
| Ctrl+C | Stop the command. |
| Ctrl+C again | Ask close the hidden shell? [y/n], for a command that ignores Ctrl+C. |
| Ctrl+B | Move the command into a background job, while it waits for no input. A command outside the sandbox asks move this command to the background? [y/n] first. The hidden shell is free at once. |
| Esc | Leave. The command stays as it is. |
What waits and what stops
-
A wait for a password stays while you can answer it. When nobody can answer, it stays only for a line that may ask for one (
sudo,ssh, andrsync,scporgitover ssh) and that you or your own rule approved. efr stops every other such wait at once and tells you:efr stopped rsync -a ... in the hidden shell: it asked for a password while nobody followed -
A command that waits for input stops
shell.interactive_timeout_minutes(60) after its start. A command that waits for nothing, such as a slow build, runs until it ends. -
While the command waits for input, the agent's next shell call fails at once. The agent reads which command holds the shell, and that you answer or stop it with
,shell. It never asks you to type at your prompt.
A command left running has the full rules.
To make a long command free the hidden shell, move it to a job with Ctrl+B: see
Background jobs. This works while
its call runs, and also after its call answered and the command was left running: in
the turn view while no call runs, and in ,shell.
Settings
| Setting | Default | What it sets |
|---|---|---|
shell.program | unset | The shell, as an absolute path. Unset: zsh on the PATH. |
shell.login | true | Start the hidden shell as a login shell. |
shell.idle_minutes | 60 | Minutes with no output or input before efrd closes an idle shell. 0 keeps idle shells. |
shell.sudo_cache | "keep" | per_call makes the hidden shell forget sudo's password after each call. |
shell.interactive_timeout_minutes | 60 | How long a command that may ask for input can run while you follow the turn. |
config.toml reference lists every key. ADR 0017 records the decision for a busy hidden shell.
You cannot attach to the hidden shell yet, so a full-screen program does not work there.