feat(agent): align the document tools with the harness-trained shape — ls, read(path), grep(pattern, path?)

This commit is contained in:
2026-09-03 11:17:47 -04:00
parent 16f1cfbcaf
commit 801639efcc
55 changed files with 4031 additions and 1466 deletions
+31 -9
View File
@@ -133,14 +133,22 @@ function addThinkingBlock(wrap, thinking) {
body.insertBefore(block, body.querySelector(".bubble"));
}
/* Tool-call lines (phase 37) — the local copy of the chat page's
* appendToolLine: one visible "calling tool" row per saved
* {name, argument} record, in saved order, above the answer. The
* path argument goes through textContent, so nothing HTML-shaped can
* come from storage. Lines are not interactive (no focus targets).
* The two content marks (the read glyph / the list glyph) are the
* exact app.js template strings — the frontend emoji guard strips
* precisely those two literals in this file, as in app.js. */
/* Tool-call lines (phase 37; phase 70 remapped the tool names to the
* harness surface ls / read(path) / grep(pattern, path?)) — the local
* copy of the chat page's appendToolLine: one visible "calling tool"
* row per saved {name, argument} record, in saved order, above the
* answer. Every argument (path / pattern / source scope) goes through
* textContent, so nothing HTML-shaped can come from storage. Lines
* are not interactive (no focus targets). Phase 70: the NEW names
* render (read → the Reading line, grep → the Searching-for line,
* ls → the Listing-documents line, scoped ls → the
* Listing-documents-in-<scope> line), and the pre-phase-70 names
* (read_document / search_documents / list_documents) still render
* exactly as before — a row saved before the remap keeps its exact
* line (no migration). The content marks are the exact app.js
* template strings — the frontend emoji guard (tests/integration/
* test_api.py) strips precisely those literals in this file, as in
* app.js. */
function addToolLines(wrap, tools) {
if (!Array.isArray(tools) || !tools.length) return;
const body = wrap.querySelector(".msg-body");
@@ -156,11 +164,25 @@ function addToolLines(wrap, tools) {
line.setAttribute("role", "listitem");
const argument =
typeof t.argument === "string" && t.argument ? t.argument : null;
if (t.name === "read_document" && argument) {
// Phase 70: new names first, legacy names kept — a conversation
// saved before the remap renders byte-identical (no migration).
if ((t.name === "read" || t.name === "read_document") && argument) {
line.textContent = "📄 Reading ";
const code = document.createElement("code");
code.textContent = argument; // the path is data, never markup
line.appendChild(code);
} else if (
(t.name === "grep" || t.name === "search_documents") && argument
) {
line.textContent = "🔎 Searching for ";
const code = document.createElement("code");
code.textContent = argument; // the pattern is data, never markup
line.appendChild(code);
} else if (t.name === "ls" && argument) {
line.textContent = "🔎 Listing documents in ";
const code = document.createElement("code");
code.textContent = argument; // the source scope is data, never markup
line.appendChild(code);
} else {
line.textContent = "🔎 Listing documents";
}