feat(agent): search_documents tool — the model can grep the indexed documents for an exact string
This commit is contained in:
@@ -5,7 +5,8 @@ No new Python app logic exists for this task — the behavior lives in
|
||||
suite (task 06). Like the other frontend-adjacent unit files, this module
|
||||
pins the JS/CSS markers the story depends on, so a silent regression in
|
||||
the tool branch, the persistence shape, or the tool-line styling is
|
||||
caught without a browser.
|
||||
catched without a browser. Phase 68 extends the pins with the
|
||||
``search_documents`` status/line contract.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -39,7 +40,11 @@ def test_tool_branch_is_a_first_class_turn_branch() -> None:
|
||||
"the turn handler must branch on tool frames"
|
||||
)
|
||||
branch = js[tool_idx:delta_idx]
|
||||
assert "toolAcc.push" in branch, "every tool frame is recorded for persistence"
|
||||
assert "toolAcc.push({ name, argument })" in branch, (
|
||||
"every tool frame is recorded for persistence — the record stays"
|
||||
" {name, argument}-generic, no per-tool shape (phase 68: the"
|
||||
" search tool rides the same accumulator)"
|
||||
)
|
||||
assert "clearTurnTimeout()" in branch, "a tool frame proves the stream is alive"
|
||||
assert 'addMessage("brain", "")' in branch, "first frame creates the brain wrap"
|
||||
assert "uiState === UI_STATE.thinking" in branch, (
|
||||
@@ -69,6 +74,16 @@ def test_calling_tool_label_strings() -> None:
|
||||
assert "sendLabel" not in branch, "phase 48: the button keeps its Stop label"
|
||||
assert "`${brand()} is listing documents`" in branch
|
||||
assert "`${brand()} is reading ${argument}`" in branch
|
||||
# Phase 68: the search status — locked name+argument gate, sitting
|
||||
# BETWEEN the read branch and the listing fallback in the ternary.
|
||||
assert "name === \"search_documents\" && argument" in branch, (
|
||||
"the search status requires the name AND a string argument"
|
||||
)
|
||||
assert "`${brand()} is searching for ${argument}`" in branch
|
||||
read = branch.find("is reading")
|
||||
search = branch.find("is searching for")
|
||||
listing = branch.find("is listing documents")
|
||||
assert -1 < read < search < listing, "ternary order: read → search → listing"
|
||||
assert "sendStatus.textContent = toolStatus" in branch, (
|
||||
"the #send-status live region announces what Brain is doing"
|
||||
)
|
||||
@@ -106,6 +121,21 @@ def test_tool_lines_render_into_the_bubble_wrap() -> None:
|
||||
"the path is data — textContent, never innerHTML"
|
||||
)
|
||||
assert "name === \"read_document\" && argument" in body
|
||||
# Phase 68: the search branch mirrors the read branch — the same
|
||||
# name+argument gate, a <code> element, and the pattern through
|
||||
# textContent (never markup); the listing stays the final else.
|
||||
assert "name === \"search_documents\" && argument" in body
|
||||
assert 'line.textContent = "🔎 Searching for "' in body
|
||||
search_part = body.split('name === "search_documents"', 1)[1]
|
||||
assert 'document.createElement("code")' in search_part, (
|
||||
"the pattern gets the same <code> treatment as the read path"
|
||||
)
|
||||
assert "code.textContent = argument" in search_part, (
|
||||
"the pattern is data — textContent, never innerHTML"
|
||||
)
|
||||
assert 'line.textContent = "🔎 Listing documents"' in search_part, (
|
||||
"the listing fallback remains the final else"
|
||||
)
|
||||
|
||||
|
||||
def test_tool_branch_is_append_only_and_interleaving_safe() -> None:
|
||||
|
||||
Reference in New Issue
Block a user