feat(discord): writes on write-marked roots, web fetch and search, held prompts (#1509)

Row 23. write_file and edit_file for roots marked write: true under the
same fence as reads; web_fetch (https only, public addresses, pinned
connection, capped body) and web_search through SearXNG; extension
renamed to tools.mjs. Engine holds a prompt while pi is busy and sends
it as its own run, so a second message mid-turn no longer folds into
the first (live defect). fake-pi models the real follow-up folding.

Suite 52/52, node tests 129. rev-code-02 APPROVED round 3, comment
26362, tree dbd2ce9a. Records: QUEUE rows 23-24, CURRENT, BUILD-LOG
phase, SESSIONS, row 24 brief (git verbs, D5-D7 ruled).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-18 07:27:50 -05:00
co-authored by Claude Fable 5.1
parent 1ac812d3d5
commit 1685deb423
24 changed files with 1519 additions and 113 deletions
+36
View File
@@ -2472,3 +2472,39 @@ corrupted and no boundary is affected. Suite 41 → 48 (three checks run
the real pi offline: the extension exposes exactly three tools, `--no-tools` hides them, a missing
`MOSAIC_DISCORD_TOOLS` makes pi exit), node tests 101 → 116. Not pushed;
the live check in #sage-admin comes after the commit.
## 2026-09-16 — Discord writes and web tools (#1509, QUEUE row 23)
Before: the Discord Sage could only list, read and search the declared
roots, and knew nothing outside the system prompt. After: a root marked
`write: true` in the binding admits `write_file` and `edit_file` under the
same fence as reads (root by name, relative path, no symlinks, regular
files, size cap, credential shapes refused, one budget per message); a
`web` key admits `web_fetch` and `web_search`. Fetch is https only, no
userinfo, every DNS answer must be a public address, the connection is
pinned to the vetted address, at most three re-vetted redirects, a body
cap, html to text, 15 s timeout. Search goes to a SearXNG instance named
in the binding (json format, at most ten results). All rules live in
`src/tools.mjs` and `src/web.mjs` and run without pi; the extension is
`packages/discord/extension/tools.mjs` (renamed from `readonly-tools.mjs`).
Rulings: D1 SearXNG, D2 Jason and Carmen write, D3 any https host, D4
rev-code-02 reviews.
Defect found live by Jason: a second message during a turn went to pi as a
follow-up, pi folded it into the running loop with one `agent_end`, the
first answer was lost and the second failed. Fix: the engine now holds a
prompt client-side until pi settles and sends it as its own run, so every
message gets its own reply and record. `tests/fake-pi.mjs` models the real
folding so the old behaviour cannot come back green. Second live finding:
a turn timed out at 180 s while the model thought for 116 s before its
first tool call; `turnTimeoutSeconds` raised to 600 by reload.
Verification: `scripts/test-discord.sh` 52/52 and node tests 129 pass on
the frozen bytes; rev-code-02 APPROVED round 3 on 2026-09-17T02:09Z (#1509
comment 26362, 19 per-file pins matched, tree
dbd2ce9a778b54b3756e861c390b454452d51246). Live: SearXNG container
`mosaic-searxng` on 127.0.0.1:8888, binding `web` key, service restarted
and pi environment verified; Jason's first web turn worked. Records of the
live setup live in the sage seat evidence directory, never in the repo.
Follow-ups: a failed turn record should carry its partial tool calls; row
24 (git verbs) is next.