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
+37
View File
@@ -180,6 +180,43 @@ async def test_forgetting_a_connection_drops_its_listings():
assert index_service.cached("profile-2", "/work") is not None
async def test_a_host_that_refuses_to_run_commands_still_gets_a_listing():
"""SFTP is the rung for exactly this, and the ladder used to skip it.
An `ExecError` from git or find -- an SFTP-only account, a forced command,
a shell that is `/bin/false` -- escaped the loop and was caught outside it,
which returned an empty index without ever trying the one method that would
have worked.
"""
class _NoExec(_Fake):
async def run(self, request):
raise ExecError("This account may not run commands.")
executor = _NoExec(tree={"/work": [RemoteEntry("README.md", False, 5)]})
found = await index_service.build(executor, "/work")
assert found.source == "sftp"
assert "README.md" in found.paths
async def test_forgetting_one_tree_leaves_the_others():
"""What a write invalidates is the directory it wrote into, not the machine.
Two chats on one box in different trees share nothing but the connection,
and dropping both would make every write cost somebody else a walk.
"""
executor = _Fake(answers={"git ls-files": _ok("a.py\n")})
await index_service.ensure(executor, "profile-1", "/work")
await index_service.ensure(executor, "profile-1", "/other")
index_service.forget_dir("profile-1", "/work")
assert index_service.cached("profile-1", "/work") is None
assert index_service.cached("profile-1", "/other") is not None
def test_reading_the_cache_never_does_work():
"""`harness` calls this synchronously while assembling the system message,
so it must never be the thing that opens a connection."""