feat(discord): read-only tools for the Discord Sage through a Mosaic pi extension confined to declared roots (#1509)
A binding may declare `tools` with named roots. pi starts with --no-builtin-tools and the package's own extension, allowlisting list_dir, read_file and search. src/tools.mjs holds the rules: names not paths, per-segment lstat walk, one checked descriptor read that refuses symlinks, swaps, FIFOs, hard links and oversize files, credential shapes refusing the whole read, and a per-message call budget. The engine settles on agent_end and records tool calls in the turn record. Jason's rulings R1-R7 in the brief, section 7. rev-code-02 approved round 2 (comment 26276) on tree 43f0329b after four round 1 fixes. Suite 48/48, node tests 116. Not pushed. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -2443,3 +2443,32 @@ seat changed was refused by the process and the binding stayed; the
|
||||
revert applied with no differences; `systemctl --user reload` applied.
|
||||
Carmen's own first message is the remaining check. Her id is only in the
|
||||
binding file.
|
||||
|
||||
## 2026-09-14 — Discord read-only tools (#1509, QUEUE row 21)
|
||||
|
||||
Before: the Discord Sage ran pi with `--no-tools`; every answer came from
|
||||
the system prompt and the message alone, and the context block said so.
|
||||
After: a binding may declare `tools` with named read-only roots. pi then
|
||||
starts with `--no-builtin-tools`, loads the package's own extension
|
||||
(`packages/discord/extension/readonly-tools.mjs`) by explicit path and
|
||||
allowlists exactly `list_dir`, `read_file` and `search`. The rules live in
|
||||
`src/tools.mjs` and run without pi: root by name only, relative paths,
|
||||
no `..`, empty or dot-prefixed segments, a per-segment `lstat` walk that
|
||||
refuses symlinks, real path under the root, regular files only, a size
|
||||
cap, a NUL check, credential shapes refusing the whole read, and a budget
|
||||
of calls per message. A refusal is a fixed reason; the model never sees a
|
||||
host path outside the root. The engine now settles on `agent_end` and
|
||||
records every tool call in the turn record with its outcome. Without
|
||||
`tools` nothing changes: the same flags and the same paragraph, byte for
|
||||
byte. Jason's rulings R1–R7 are in the brief, section 7; the departures
|
||||
from the design are in section 8. Review: rev-code-02 approved round 2
|
||||
(26276) after four round 1 findings: late tool events landing in the
|
||||
next prompt's record, a read-time symlink swap, a missed header form, and
|
||||
an install message that needed USER set. One non-blocking finding is left
|
||||
for a later round: the malformed-line handler in `engine-pi.mjs` still
|
||||
picks the first unfinished prompt, so garbage from a dead run could fail
|
||||
the next prompt with the fixed engine-protocol line. No record is
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user