A terminal panel beside an agent chat
A real shell on the chat's own connection, opened and closed like the inspector and never beside it. The modes govern the model; what a person types is theirs, since they hold the credential and could open the same shell with an ssh client. The model cannot see the panel -- a button copies the output you choose into the composer. The session outlives the socket: closing the panel leaves a build running, and coming back reattaches with the scrollback. Two tabs share one shell and the smaller window decides the size. It ends on an idle timeout, on deleting the chat, on disabling, moving or deleting the connection, and on a restart -- which says why rather than quietly opening a fresh shell that has lost the working directory. The nginx template's `Connection ""` is right for SSE and fails every WebSocket handshake, so `location /` now uses a `map $http_upgrade`; update.sh grows a drift check for it, because the only symptom on a stale vhost is a panel that cannot connect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='css/chat.css') }}">
|
||||
{% if terminal_enabled %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='vendor/xterm.css') }}">
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body_attrs %} data-authenticated="true"{% endblock %}
|
||||
@@ -98,9 +101,20 @@
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if terminal_enabled %}
|
||||
{# To the left of the inspector, and never open beside it: see the
|
||||
toggle group in app.js. #}
|
||||
<button class="btn btn--icon" type="button" aria-label="Terminal"
|
||||
title="Open a shell on {{ agent_profile.name if agent_profile else 'this connection' }}"
|
||||
aria-expanded="false" data-toggle="#terminal" data-toggle-group="side">
|
||||
{{ icon("terminal") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if chat and user.is_admin %}
|
||||
<button class="btn btn--icon" type="button" aria-label="Inspect this chat"
|
||||
title="Inspect this chat" aria-expanded="false" data-toggle="#inspector">
|
||||
title="Inspect this chat" aria-expanded="false" data-toggle="#inspector"
|
||||
data-toggle-group="side">
|
||||
{{ icon("search") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
@@ -253,9 +267,23 @@
|
||||
{% endif %}
|
||||
</main>
|
||||
|
||||
{# A third child of .shell, mirroring the sidebar opposite it. #}
|
||||
{# Third and fourth children of .shell, mirroring the sidebar opposite. The
|
||||
terminal comes first so it sits to the left of the inspector. #}
|
||||
{% if terminal_enabled %}
|
||||
{% include "chat/_terminal.html" %}
|
||||
{% endif %}
|
||||
{% if chat and user.is_admin %}
|
||||
{% include "chat/_inspector.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% if terminal_enabled %}
|
||||
{# Only where it can be used. xterm is nearly three times everything else
|
||||
vendored, so a plain chat must never load it. #}
|
||||
<script src="{{ url_for('static', path='vendor/xterm.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', path='vendor/xterm-addon-fit.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', path='js/terminal.js') }}" defer></script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user