{% extends "admin/_layout.html" %}
{% from "_macros.html" import icon %}
{% set section = "agents" %}

{% block title %}Agents - {{ brand.name }}{% endblock %}
{% block heading %}Agents{% endblock %}

{% block admin_content %}
<p class="admin-lede">
  An <strong>Agent</strong> chat can read files, write files and run commands on
  a machine reached over SSH. Nothing runs on this server. People add their own
  connections under <strong>Connections</strong>; what you decide here is
  whether the feature exists and what one reply may spend.
</p>

<div class="alert">
  {{ icon("shield", "icon--sm") }}
  <span>
    There is no sandbox to configure, and that is deliberate: containment is
    whatever host somebody points a connection at. A container built for the
    job is a very different thing from a key to a live server, and {{ brand.name }}
    cannot tell them apart. What a model reads — a web page, a file, the output
    of the last command — is untrusted, and in <strong>Auto</strong> mode
    nothing stands between that and a command running.
  </span>
</div>

{% if problem %}
<div class="alert alert--error">{{ icon("warning", "icon--sm") }} <span>{{ problem }}</span></div>
{% endif %}

{% if saved %}
<div class="alert alert--success">{{ icon("check", "icon--sm") }} <span>Saved.</span></div>
{% endif %}

<form method="post" action="/admin/agents" class="form-grid">

  <section class="card">
    <h2 class="card__title">Switch</h2>
    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="enabled" value="true" {{ 'checked' if values.enabled }}>
        <span>Allow agent chats</span>
      </label>
      <p class="field__hint">
        Off, nobody can start one and no agent tool is offered, whatever
        permissions they hold. {{ profile_count }} connection{{ '' if profile_count == 1 else 's' }}
        saved across all accounts.
      </p>
    </div>
    <p class="field__hint">
      People also need the <strong>Run commands</strong> permission, a model
      flagged <strong>Agent execution</strong>, and a connection of their own.
    </p>
  </section>

  <section class="card">
    <h2 class="card__title">Connections to this machine</h2>
    <p class="card__lede">
      Agent chats reach a machine over SSH, and the point of that is that it is
      not this one — nothing runs on the host holding the database and the
      encryption key. A connection pointed at <code>127.0.0.1</code> walks past
      that, and from the SSH layer's point of view it looks like any other host.
      {% if loopback_count %}
      <strong>{{ loopback_count }} saved connection{{ '' if loopback_count == 1 else 's' }}
      point{{ 's' if loopback_count == 1 else '' }} here.</strong>
      {% endif %}
    </p>
    <div class="field">
      {% for value, label, hint in loopback_modes %}
      <label class="checkbox">
        <input type="radio" name="loopback" value="{{ value }}"
               {{ 'checked' if (values.loopback or 'off') == value }}>
        <span><strong>{{ label }}</strong> — {{ hint }}</span>
      </label>
      {% endfor %}
    </div>
    <div class="field">
      <label class="field__label" for="loopback_port">The allowed port</label>
      <input class="input" type="number" id="loopback_port" name="loopback_port"
             min="0" max="65535" step="1" value="{{ values.loopback_port or 0 }}">
      <p class="field__hint">
        Only read when the position above is <strong>Only on one port</strong>.
        Port 22 is refused whatever is typed here — that one is this host's own
        sshd, not a container that published its port on the loopback interface.
      </p>
    </div>
  </section>

  <section class="card">
    <h2 class="card__title">The modes</h2>
    <p class="field__hint">
      Set per chat and switchable at any time. This is what each one means; the
      two lists below adjust them.
    </p>
    <dl class="mode-list">
      {% for value, label, hint in modes %}
      <div class="mode-list__row">
        <dt><strong>{{ label }}</strong></dt>
        <dd>{{ hint }}</dd>
      </div>
      {% endfor %}
    </dl>
  </section>

  <section class="card">
    <h2 class="card__title">What never needs asking</h2>
    <div class="field">
      <label class="field__label" for="allow_default">Always allow</label>
      <textarea class="textarea input--mono" id="allow_default" name="allow_default" rows="5"
                spellcheck="false">{{ allow_text }}</textarea>
      <p class="field__hint">
        One per line: a tool name like <code>file_read</code>, or a command with
        wildcards like <code>git *</code>. A command containing anything that
        joins two commands together — a semicolon, a pipe, backticks — can never
        match one of these, so <code>git *</code> does not quietly also mean
        <code>git status; curl … | sh</code>.
      </p>
    </div>
  </section>

  <section class="card">
    <h2 class="card__title">What always needs asking</h2>
    <div class="field">
      <label class="field__label" for="deny_default">Always ask</label>
      <textarea class="textarea input--mono" id="deny_default" name="deny_default" rows="5"
                spellcheck="false">{{ deny_text }}</textarea>
      <p class="field__hint">
        Checked before everything, including <strong>Auto</strong>. Treat it as
        a guard against an accident rather than against an adversary:
        <code>rm -rf /*</code> here does not stop <code>/bin/rm -rf /</code>, and
        nothing pattern-shaped could. The same limit as above applies, and it
        cuts the other way here: a command line that runs more than one thing
        matches none of these, so in <strong>Auto</strong>
        <code>shutdown -h now</code> asks and <code>shutdown -h now &amp;</code>
        runs. Anything that must never happen belongs on the far side, in that
        account’s own permissions.
      </p>
    </div>
  </section>

  <section class="card">
    <h2 class="card__title">What one command may spend</h2>

    <div class="field">
      <label class="field__label" for="default_timeout">Timeout (seconds)</label>
      <input class="input" id="default_timeout" name="default_timeout"
             value="{{ values.default_timeout }}" inputmode="numeric">
    </div>
    <div class="field">
      <label class="field__label" for="max_timeout">Longest a command may ask for</label>
      <input class="input" id="max_timeout" name="max_timeout"
             value="{{ values.max_timeout }}" inputmode="numeric">
    </div>
    <div class="field">
      <label class="field__label" for="max_output_bytes">Most output to keep</label>
      <input class="input" id="max_output_bytes" name="max_output_bytes"
             value="{{ values.max_output_bytes }}" inputmode="numeric">
      <p class="field__hint">
        Characters. The rest is cut off and the model is told so.
      </p>
    </div>
  </section>

  <section class="card">
    <h2 class="card__title">Background commands</h2>
    <p class="card__lede">
      A command that would outlast its timeout can be left running instead of
      killed — detached on the far side, checked on later. It is how a long
      install, build or download becomes possible at all.
    </p>
    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="background_enabled"
               {{ 'checked' if values.background_enabled }}>
        <span>Allow commands to run in the background</span>
      </label>
      <p class="field__hint">
        Off means byte-for-byte the old behaviour: a command that hits its
        timeout is killed. On, a command can be launched detached (or kept
        running when it times out), writing to a file under
        <code>/tmp</code> on the machine, and the model gets tools to read and
        stop it. A detached command's log can grow without bound on the host —
        that is the host's to contain, as with everything an agent runs there.
      </p>
    </div>
    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="background_on_timeout"
               {{ 'checked' if values.background_on_timeout }}>
        <span>Keep a timed-out command running instead of killing it</span>
      </label>
      <p class="field__hint">
        Off leaves the timeout a hard stop; the model can still choose to
        background a command up front.
      </p>
    </div>
    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="background_notify"
               {{ 'checked' if values.background_notify }}>
        <span>Wake the model when a background job finishes</span>
      </label>
      <p class="field__hint">
        On, a finished job starts (or joins) a reply carrying its result. Off,
        the model only sees it the next time it runs of its own accord.
      </p>
    </div>
    <div class="field">
      <label class="field__label" for="background_max_jobs">Most jobs watched at once</label>
      <input class="input" id="background_max_jobs" name="background_max_jobs"
             value="{{ values.background_max_jobs }}" inputmode="numeric">
      <p class="field__hint">
        Each is a periodic reconnect to the machine. Jobs past this still run;
        they are simply not watched, and the model is not woken for them.
      </p>
    </div>
  </section>

  <section class="card">
    <h2 class="card__title">What one reply may spend</h2>
    <p class="field__hint">
      Four separate bounds, because they fail differently: the clock stops one
      slow command eating an afternoon, tool output stops a model filling its own
      context with build logs and having no room to answer, written tokens stop
      one that keeps going, and the step count is a backstop against a runaway.
    </p>

    <div class="field">
      <label class="field__label" for="max_completion_tokens">
        Most a reply may write
      </label>
      <input class="input" id="max_completion_tokens" name="max_completion_tokens"
             value="{{ values.max_completion_tokens }}" inputmode="numeric">
      <p class="field__hint">
        In tokens, across every round of one reply. This is the bound that
        normally ends a long piece of work. Zero means no ceiling.
      </p>
    </div>
    <div class="field">
      <label class="field__label" for="max_wall_seconds">Longest a reply may take</label>
      <input class="input" id="max_wall_seconds" name="max_wall_seconds"
             value="{{ values.max_wall_seconds }}" inputmode="numeric">
      <p class="field__hint">Time spent waiting for you to answer does not count.</p>
    </div>
    <div class="field">
      <label class="field__label" for="max_total_output_bytes">Most output across a reply</label>
      <input class="input" id="max_total_output_bytes" name="max_total_output_bytes"
             value="{{ values.max_total_output_bytes }}" inputmode="numeric">
    </div>
    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="nudge_unfinished"
               {{ 'checked' if values.nudge_unfinished }}>
        <span>Ask it to carry on when it stops with tasks outstanding</span>
      </label>
      <p class="field__hint">
        Only ever against a plan, and only while tasks on it are still open —
        that is the one thing there is to be objectively wrong about. A reply
        with no plan that says it has finished is believed. It is asked at most
        twice in a row, and if it stops a third time that is recorded in the
        transcript rather than argued with.
      </p>
    </div>

    <div class="field">
      <label class="field__label" for="max_steps">Most rounds of tool calls</label>
      <input class="input" id="max_steps" name="max_steps"
             value="{{ values.max_steps }}" inputmode="numeric">
      <p class="field__hint">
        A backstop, not a working budget. An agent reply is meant to run until
        the task is done, so a number low enough to be what stops it is a number
        that stops it halfway. Use the token ceiling above for a real limit.
      </p>
    </div>
  </section>

  <section class="card">
    <h2 class="card__title">Asking you things</h2>

    <div class="field">
      <label class="field__label" for="approval_timeout">How long a question waits</label>
      <input class="input" id="approval_timeout" name="approval_timeout"
             value="{{ values.approval_timeout }}" inputmode="numeric">
      <p class="field__hint">
        Seconds. After this the reply carries on without an answer and says so.
        At least a minute, whatever is typed here.
      </p>
    </div>

    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="ask_free_text" value="true"
               {{ 'checked' if values.ask_free_text }}>
        <span>Let people write their own answer</span>
      </label>
      <p class="field__hint">
        When a model asks a question it can offer answers to pick from, and by
        default a box to write something else. Turn this off if you would rather
        nobody typed free text into a prompt a model composed.
      </p>
    </div>
  </section>

  <section class="card">
    <h2 class="card__title">The terminal</h2>
    <p class="field__hint">
      A panel beside an agent chat holding an interactive shell on that chat's
      own connection. What somebody types there is <em>theirs</em>: the modes and
      the two lists above govern the model, not the person at the keyboard, who
      could open the same shell with an ssh client. The model cannot see the
      panel; sending it something is a button they press.
    </p>

    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="terminal_enabled" value="true"
               {{ 'checked' if values.terminal_enabled }}>
        <span>Allow the terminal panel</span>
      </label>
      <p class="field__hint">
        People also need the <strong>Open a terminal</strong> permission.
        {{ terminal_count }} shell{{ '' if terminal_count == 1 else 's' }} open right now.
      </p>
    </div>

    <div class="field">
      <label class="field__label" for="terminal_idle_timeout">Close a shell after</label>
      <input class="input" id="terminal_idle_timeout" name="terminal_idle_timeout"
             value="{{ values.terminal_idle_timeout }}" inputmode="numeric">
      <p class="field__hint">
        Seconds with nobody watching <em>and</em> nothing typed. Closing the
        panel does not end the session — a build carries on and is still there
        on the way back — so this is what eventually ends one.
      </p>
    </div>
    <div class="field">
      <label class="field__label" for="terminal_max_sessions">Most shells at once</label>
      <input class="input" id="terminal_max_sessions" name="terminal_max_sessions"
             value="{{ values.terminal_max_sessions }}" inputmode="numeric">
    </div>
    <div class="field">
      <label class="field__label" for="terminal_max_per_user">Most shells per person</label>
      <input class="input" id="terminal_max_per_user" name="terminal_max_per_user"
             value="{{ values.terminal_max_per_user }}" inputmode="numeric">
      <p class="field__hint">
        One per chat. Each holds an SSH connection open on the far machine.
      </p>
    </div>

    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="terminal_integration"
               {{ 'checked' if values.terminal_integration }}>
        <span>Mark where commands begin and end</span>
      </label>
      <p class="field__hint">
        Gives bash and zsh the same invisible markers VS Code and WezTerm use,
        so <strong>Copy</strong>, <strong>Send</strong> and the automatic
        toggle know which output belongs to which command. Written by the shell
        into a temporary file it deletes itself, and any other shell is started
        exactly as it was before. Off means those buttons fall back to copying
        the last of the screen as it appeared, wraps and all.
      </p>
    </div>
  </section>

  <section class="card">
    <h2 class="section-title">The project directory</h2>
    <p class="muted">
      A listing of the directory a chat works in, so a reply does not spend its
      first rounds finding out what is there — and so files can be attached by
      name with <strong>@</strong>. Built by one read-only command
      (<code>git ls-files</code> where it works, otherwise <code>find</code>),
      cached briefly, and shared by every chat pointed at the same directory.
    </p>

    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="index_enabled"
               {{ 'checked' if values.index_enabled }}>
        <span>List the project directory</span>
      </label>
      <p class="field__hint">
        Off means no listing is built at all, and the file picker offers only
        what is in the library.
      </p>
    </div>

    <div class="field">
      <label class="field__label" for="index_chars">Characters of it in the prompt</label>
      <input class="input" id="index_chars" name="index_chars"
             value="{{ values.index_chars }}" inputmode="numeric">
      <p class="field__hint">
        This is spent on <em>every</em> request in an agent chat, so it is a
        budget rather than a limit: directories that will not fit are shown as
        a count and the model is told to look inside them itself.
        <strong>0</strong> keeps the listing for the file picker and puts none
        of it in the prompt.
      </p>
    </div>

    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="instructions_enabled"
               {{ 'checked' if values.instructions_enabled }}>
        <span>Read the project's own instructions</span>
      </label>
      <p class="field__hint">
        Looks for <code>AGENTS.md</code> or <code>CLAUDE.md</code> in the root
        of the project directory and puts it in the prompt, so a model follows
        the conventions of the project it is working in. The file is written by
        whoever works on that project, so it is treated as untrusted: it can say
        how to work, and cannot grant permission for anything. The exact wording
        around it is the <em>The project's own instructions</em> fragment on
        <a href="/admin/prompts">Prompts</a>, and clearing that fragment removes
        the only path by which the file reaches a model.
      </p>
    </div>

    <div class="field">
      <label class="field__label" for="instructions_chars">Characters of it to use</label>
      <input class="input" id="instructions_chars" name="instructions_chars"
             value="{{ values.instructions_chars }}" inputmode="numeric">
      <p class="field__hint">
        Cut at a line boundary past this. <strong>0</strong> is the same as
        switching it off.
      </p>
    </div>
  </section>

  <div class="btn-row">
    <button class="btn btn--primary" type="submit">Save changes</button>
  </div>
</form>

{#
  A second form, and a second settings group. Subagents are not an agent-chat
  feature — an ordinary chat can delegate as well — but this is the page
  somebody comes to when they want to know what one reply may set going on its
  own, and a nav entry for a single card would be worse than the near-miss.

  A form cannot nest inside another, so this sits *after* the one above rather
  than as a card inside it. Two forms means the browser posts only the one whose
  button was pressed, which is what keeps each group's save handler writing one
  key.
#}
<form method="post" action="/admin/agents/subagents" class="form-grid">
  <section class="card">
    <h2 class="card__title">Helpers</h2>
    <p class="field__hint">
      A reply can hand a self-contained piece of work to a second model that
      runs on its own and reports back — several at once, which is what makes
      research fan out instead of queueing. This applies to ordinary chats as
      much as agent ones.
    </p>

    <div class="alert">
      {{ icon("shield", "icon--sm") }}
      <span>
        A helper cannot ask anybody anything, so nothing in its chat can stop
        for approval. It therefore gets only what this chat could already do
        <em>without</em> asking: it reads, it searches, and on a machine it runs
        a short fixed list of read-only commands and nothing else, in every mode
        including <strong>Auto</strong>. It cannot send helpers of its own.
      </span>
    </div>

    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="enabled" value="true"
               {{ 'checked' if subagents.enabled }}>
        <span>Let a model delegate</span>
      </label>
      <p class="field__hint">
        People also need the <strong>Delegate to a helper</strong> permission,
        and the model needs the <strong>Tools</strong> capability. Off by
        default: a reply that spawns helpers spends model time multiplicatively,
        and on one local endpoint four at once is four times the queue rather
        than four times the speed.
      </p>
    </div>

    <div class="field">
      <label class="field__label" for="sub_max_per_reply">Most helpers one reply may send</label>
      <input class="input" id="sub_max_per_reply" name="max_per_reply"
             type="number" min="1" max="20" step="1"
             value="{{ subagents.max_per_reply }}">
      <p class="field__hint">
        Fanning out across a handful of independent questions is what this is
        for. A reply that wants twenty has misread the tool.
      </p>
    </div>

    <div class="field">
      <label class="field__label" for="sub_max_concurrent">Running at once, instance-wide</label>
      <input class="input" id="sub_max_concurrent" name="max_concurrent"
             type="number" min="1" max="50" step="1"
             value="{{ subagents.max_concurrent }}">
      <p class="field__hint">
        Each is a whole generation against the same endpoint the reply that
        asked for it is waiting on. Past this a model is told to do the work
        itself rather than made to wait.
      </p>
    </div>

    <div class="field">
      <label class="field__label" for="sub_max_completion_tokens">Most a helper may write</label>
      <input class="input" id="sub_max_completion_tokens" name="max_completion_tokens"
             type="number" min="0" max="5000000" step="1000"
             value="{{ subagents.max_completion_tokens }}">
      <p class="field__hint">
        In tokens, across every round. A helper answers one question, so this
        should run out well before the reply that asked does. Zero means no
        ceiling.
      </p>
    </div>

    <div class="field">
      <label class="field__label" for="sub_wall_seconds">Longest a helper may take</label>
      <input class="input" id="sub_wall_seconds" name="wall_seconds"
             type="number" min="30" max="7200" step="30"
             value="{{ subagents.wall_seconds }}">
      <p class="field__hint">
        Seconds. Past it the helper is <em>stopped</em>, not abandoned: what it
        had written is kept and handed back with a note saying it is partial.
      </p>
    </div>

    <div class="field">
      <label class="field__label" for="sub_max_rounds">Most rounds of tool calls</label>
      <input class="input" id="sub_max_rounds" name="max_rounds"
             type="number" min="1" max="200" step="1"
             value="{{ subagents.max_rounds }}">
      <p class="field__hint">
        A backstop, as it is above. The clock and the token ceiling are what
        normally end one.
      </p>
    </div>

    <div class="field">
      <label class="checkbox">
        <input type="checkbox" name="keep_transcript" value="true"
               {{ 'checked' if subagents.keep_transcript }}>
        <span>Keep a helper's own chat afterwards</span>
      </label>
      <p class="field__hint">
        Off means it is deleted once its answer has been handed over, which is
        what keeps this cheap to use. Turn it on to work out why one came back
        with something odd. Kept chats are temporary either way and are swept a
        day later, and neither appears in anybody's sidebar.
      </p>
    </div>
  </section>

  <div class="btn-row">
    <button class="btn btn--primary" type="submit">Save changes</button>
  </div>
</form>
{% endblock %}
