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:
@@ -254,3 +254,78 @@ def test_somebody_elses_connection_is_not_browsable(
|
||||
# 404 and not 403: whether that connection exists is not this endpoint's to
|
||||
# reveal to somebody who does not own it.
|
||||
assert client.get(f"/api/agents/{profile.id}/browse").status_code == 404
|
||||
|
||||
|
||||
# --- Reading the project directory again -------------------------------------
|
||||
def _agent_chat(db, profile):
|
||||
from lembas.db.models import KIND_AGENT, Chat, Connection, Model
|
||||
|
||||
connection = Connection(name="c", base_url="http://127.0.0.1:1", api_key_encrypted="")
|
||||
db.add(connection)
|
||||
db.commit()
|
||||
db.add(Model(connection_id=connection.id, model_id="m", capabilities_json={"tools": True}))
|
||||
db.commit()
|
||||
|
||||
chat = Chat(
|
||||
user_id=profile.owner_id,
|
||||
model_id="m",
|
||||
connection_id=connection.id,
|
||||
kind=KIND_AGENT,
|
||||
ssh_profile_id=profile.id,
|
||||
project_dir=profile.default_dir,
|
||||
)
|
||||
db.add(chat)
|
||||
db.commit()
|
||||
return chat
|
||||
|
||||
|
||||
def test_reindexing_walks_the_tree_again(client: TestClient, db, registered, served_tree):
|
||||
"""The listing is built only when a reply starts and then held for five
|
||||
minutes, so anything done in the terminal panel is invisible to it until
|
||||
then. This is the way to say "look again"."""
|
||||
from lembas.services.agent import index as index_service
|
||||
|
||||
profile = _profile(db, served_tree)
|
||||
chat = _agent_chat(db, profile)
|
||||
|
||||
response = client.post(f"/api/chats/{chat.id}/index")
|
||||
assert response.status_code == 200, response.text
|
||||
body = response.json()
|
||||
|
||||
assert body["ok"] is True
|
||||
assert body["files"] >= 2 # README.md and src/
|
||||
assert index_service.cached(profile.id, served_tree["root"]) is not None
|
||||
|
||||
|
||||
def test_reindexing_a_plain_chat_says_there_is_nothing_to_read(
|
||||
client: TestClient, db, registered, served_tree
|
||||
):
|
||||
from lembas.db.models import Chat, Connection, Model
|
||||
|
||||
connection = Connection(name="c", base_url="http://127.0.0.1:1", api_key_encrypted="")
|
||||
db.add(connection)
|
||||
db.commit()
|
||||
db.add(Model(connection_id=connection.id, model_id="m"))
|
||||
db.commit()
|
||||
chat = Chat(user_id=_profile(db, served_tree).owner_id, model_id="m",
|
||||
connection_id=connection.id)
|
||||
db.add(chat)
|
||||
db.commit()
|
||||
|
||||
assert client.post(f"/api/chats/{chat.id}/index").status_code == 409
|
||||
|
||||
|
||||
def test_reindexing_somebody_elses_chat_is_not_possible(
|
||||
client: TestClient, db, registered, served_tree
|
||||
):
|
||||
profile = _profile(db, served_tree)
|
||||
chat = _agent_chat(db, profile)
|
||||
|
||||
client.post("/auth/logout", follow_redirects=False)
|
||||
client.post(
|
||||
"/auth/register",
|
||||
data={"name": "Sam", "email": "sam@shire.test", "password": "potatoes-po-ta-toes"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
assert client.post(f"/api/chats/{chat.id}/index").status_code == 404
|
||||
|
||||
Reference in New Issue
Block a user