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:
+13
-6
@@ -8,6 +8,7 @@ from typing import Annotated
|
||||
from fastapi import Depends, HTTPException, Request, status
|
||||
from fastapi.responses import RedirectResponse
|
||||
from sqlalchemy.orm import Session as DBSession
|
||||
from starlette.requests import HTTPConnection
|
||||
|
||||
from lembas.db.models import User
|
||||
from lembas.db.session import get_session_factory
|
||||
@@ -26,17 +27,23 @@ def get_db() -> Iterator[DBSession]:
|
||||
Db = Annotated[DBSession, Depends(get_db)]
|
||||
|
||||
|
||||
def get_current_user(request: Request, db: Db) -> User | None:
|
||||
def get_current_user(conn: HTTPConnection, db: Db) -> User | None:
|
||||
"""Resolve the session cookie to a user, or None when signed out.
|
||||
|
||||
Cached on request.state so several dependencies in one request do not each
|
||||
hit the sessions table.
|
||||
Cached on the connection's state so several dependencies in one request do
|
||||
not each hit the sessions table.
|
||||
|
||||
`HTTPConnection` rather than `Request` because the terminal panel is a
|
||||
WebSocket, and FastAPI injects a `WebSocket` there -- annotating this
|
||||
`Request` fails at *connect* time rather than at import, so it would pass
|
||||
every smoke test and break in a browser. `HTTPConnection` is the base of
|
||||
both and carries the cookies and the state either way.
|
||||
"""
|
||||
cached = getattr(request.state, "user", None)
|
||||
cached = getattr(conn.state, "user", None)
|
||||
if cached is not None:
|
||||
return cached
|
||||
user = resolve_session(db, request.cookies.get(COOKIE_NAME))
|
||||
request.state.user = user
|
||||
user = resolve_session(db, conn.cookies.get(COOKIE_NAME))
|
||||
conn.state.user = user
|
||||
return user
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user