Two selects that never wrote anything, and a queue

The approval card in Auto mode and the missing /effort were one bug. Both
selects hung their hx-patch on an empty sibling form reached by form="…",
and htmx binds a trigger to the annotated element: change fires on the
select and bubbles to its ancestors, which a sibling is not. The live rows
read agent_mode=manual and params_json={} while the browser showed Auto and
Effort: high. policy.py was never involved.

The verb moves onto the control; the empty form stays as value scoping,
which is the half of the CLAUDE.md note that was right. conftest gains
control_named so a test asserts the element carrying the name carries the
verb, rather than asserting the markup that was there throughout.

The composer's highlight was a third instance of the same carelessness in
CSS: .tok-mention is written for the transcript and scoped to nothing, so
the mirror painted its token in accent-coloured monospace over the
textarea's own text. Scoped under .msg; the mirror restates transparency
and font rather than inheriting them, and bleeds by box-shadow.

/effort is now offered before the first prompt and _new_chat reads it.
/index re-walks the project directory on demand, file_write drops the
listing it just invalidated, and the index ladder falls through to SFTP on
a host that refuses exec instead of returning nothing.

A second message during a reply is queued rather than starting a second
concurrent generation: a real Message row with queued set, so it survives a
restart and can be withdrawn. _drain hands one on at the end of a reply,
_inject takes one in at a tool-round boundary so an agent can be steered
mid-task. Stop leaves the queue undelivered. The terminal's Auto toggle
becomes off/copy/send, and send posts straight to the chat without touching
the composer.

@ now offers notes, skills, this chat's attachments and a URL to fetch; a
knowledge base attaches as a reference rather than a copy. copy_document
carries provenance, which was the one attach path that dropped it.

Also fixes an unrelated live bug: the round loop compared against the
global MAX_ROUNDS of 3 while sizing itself from the agent budget of 40, so
agent replies stopped after three rounds and reported forty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-08-02 19:49:34 +02:00
parent 0bee366488
commit 8a3a225fea
31 changed files with 2131 additions and 81 deletions
+68
View File
@@ -17,6 +17,8 @@ from sqlalchemy import select
from lembas.db.models import Chat, Connection, Model, User
from lembas.services import chat as chat_service
from .conftest import control_named
def _model(db, **capabilities) -> Model:
connection = Connection(name="c", base_url="http://127.0.0.1:1", api_key_encrypted="")
@@ -162,3 +164,69 @@ def test_a_model_with_no_defaults_starts_a_plain_chat(client: TestClient, db, re
client.post("/api/chats/start", data={"content": "hello", "model_id": "m"})
assert db.scalars(select(Chat)).one().params_json == {}
# --- Choosable before the first prompt ---------------------------------------
def test_the_effort_select_carries_its_own_verb(client: TestClient, db, registered):
"""The same invariant the mode select needs, for the same reason.
Both were built on `form=""` pointing at an empty sibling form holding the
`hx-patch`, and both therefore wrote nothing at all: `form=` scopes the
values a request carries, it does not route the event that starts one.
"""
chat = _chat(db)
select = control_named(client.get(f"/chat/{chat.id}").text, "reasoning_effort")
assert select["hx-patch"] == f"/api/chats/{chat.id}"
assert select["form"] == "chat-params-form"
def test_the_effort_is_offered_before_there_is_a_chat(client: TestClient, db, registered):
"""Otherwise it is a setting you can only reach once it is too late to use.
On the new-chat screen there is nothing to PATCH, so it is an ordinary field
of the composer's form and carries no verb -- `_new_chat` reads it.
"""
_model(db)
select = control_named(client.get("/chat").text, "reasoning_effort")
assert "hx-patch" not in select
assert "form" not in select
def test_starting_a_chat_with_an_effort_stores_it(client: TestClient, db, registered):
_model(db)
client.post(
"/api/chats/start",
data={"content": "hello", "model_id": "m", "reasoning_effort": "low"},
)
assert db.scalars(select(Chat)).one().params_json["reasoning_effort"] == "low"
def test_an_explicit_effort_beats_the_models_default(client: TestClient, db, registered):
"""An inherited value is a starting point, not a ceiling."""
model = _model(db)
model.params_json = {"reasoning_effort": "high"}
db.commit()
client.post(
"/api/chats/start",
data={"content": "hello", "model_id": "m", "reasoning_effort": "low"},
)
assert db.scalars(select(Chat)).one().params_json["reasoning_effort"] == "low"
def test_a_nonsense_effort_at_the_start_falls_back(client: TestClient, db, registered):
model = _model(db)
model.params_json = {"reasoning_effort": "high"}
db.commit()
client.post(
"/api/chats/start",
data={"content": "hello", "model_id": "m", "reasoning_effort": "extreme"},
)
assert db.scalars(select(Chat)).one().params_json["reasoning_effort"] == "high"