9.9 KiB
Row 225 (C2-10b) Console chat: replies, citations, write notices, status line
Owner: Morgana, from sage-3582 (18:53Z 10-23). Base: origin/refactor 45dca69f6. Branch: morgana/row225.
Brief: docs/plans/2026-10-20_case2-rows.md, C2-10b. Design: agents/gosalyn/work/case2/DESIGN.md 7.4,
5.2 (the reply schema and the check result) and the write notice and library.revert (about line 286).
Ruling on the dependency gap (sage-3591)
225 builds against the design contracts. renderReply stays pure, and the caller supplies the answers
it can't know. The revert route proxies to the bus verb and shows the refusal code until the verb
exists. Landing order: 225 lands after 217 (C2-06), not after 288 (C2-02). Sage adds after 217 to
row 225. Before 288 lands, the Revert button gets the bus's refusal and shows its code, as the brief
allows.
A1. The check result I assume (to 217, Launchpad)
The broker stores one check result with each agent-to-human message the guard passes. The Console reads
it on the message row that /api/chat/threads/<id>/messages returns, under the key check:
{
"check": {
"citations": [
{ "scope": "canon", "agent": null },
{ "scope": "supporting", "agent": "acme/scribe" },
{ "scope": "web", "agent": null }
]
}
}
checkisnullor absent on a message the guard didn't check: a human's message, a free-text reply, a notice, or a message from before 217.check.citationshas one entry per entry of the body'scitations, in the same order.scopeis one ofcanon,playground,supportingandweb. A url cite isweb.agentis theMosaic-Agenttrailer value,<business>/<instance>, fromgit log -1 --format=%(trailers:key=Mosaic-Agent,valueonly) <commit> -- <path>, ornullwhen the file's last commit atcommitcarries none. A url cite hasagent: null. The Console shows the agent-written label whenagentis a nonempty string, and names the agent.
The Console trusts nothing in the body for scope or label. A cite with no matching check entry renders with no badge and no label. It doesn't fall back to a scope it worked out itself.
If 217 uses other names, I change mine to theirs before either of us pushes (sage-3591 item 1).
A2. The revert verb I assume (to 288, Honker)
- Verb:
library.revert. A human verb (design M4): from an agent session it refuseshuman-required, likelibrary.status(broker.mjs:5133). - Args:
{ "project": <business project name>, "commit": <40 lowercase hex> }. The design writeslibrary.revert {commit}.projectis added to match the other library verbs, and like theirs it is the key ofbusiness.projects(mirror.mjs:31), which is the chat project'srepolink, not its UUID. Both are required, and no other key is allowed (invalid-request), as the other library verbs check withkeys(args, ...). - Result:
{ "project", "path", "reverted": <the commit given>, "commit": <the new commit> }. - Refusal codes the Console shows by name:
library-revert-not-agent:commithas noMosaic-Agenttrailer for this business, or isn't reachable from the served commit.stale-base: a later commit changed that path.human-required,invalid-request,project-not-configured.- Before 288 lands, the bus refuses the verb (expected
unknown-verb,broker.mjs:6578), and the Console shows that code.
- HTTP status in the Console (
packages/webui/src/bus.mjsbusStatus):stale-baseandlibrary-revert-not-agentmap to 409. Every other code keeps the existing mapping.
A3. The status line
GET /api/library/status?project=<id> proxies library.status {project} through the existing
libraryStatus option. cli.mjs:110 already passes that option, but startServer doesn't read it.
The status line shows:
- the served commit (7 characters) and its age;
library-divergedwhen the sync state names it;- the fetch error code;
- the reply limits in force, and the keys that fell back;
- the count of R-problems, linking to the list.
It reads the fields readSyncState returns at the base (packages/library/src/mirror.mjs:232). It shows
any field 217 adds (the limits) only when present.
A4. "Changed since" (ruled sage-3596)
Sage ruled that 217 supplies last. On a thread.messages row that carries check, each path cite's
entry gets last, the file's last commit at the served tip, read when the message is read. When last
is absent the Console marks nothing changed, and a test pins that.
One more field is needed, raised in morgana-0200. The cited commit is a library commit, not the
file's own last commit, so a cite made at a commit that didn't touch the file would differ from last
while the file is unchanged. I asked 217 to also store file, the file's last commit at the cited
commit. That's %H from the same git log -1 <commit> -- <path> call that reads the trailer, so it
costs no extra git call. The Console marks a cite changed when both file and last are 40-hex and
differ. The cited commit is reachable from the served one (5.2), so file is an ancestor of last,
and differing means newer. A missing or malformed file or last marks nothing.
The entry the Console reads, in full: { scope, agent, file?, last? }.
A5. no-cited-answer retires
renderItems and its NOTICES (no-cited-answer, no-answer, turn-failed) go. 217 sends the
runner's fixed texts as plain bodies (design 5.2, "Runner texts"), and renderReply shows those as free
text. Design 7.4 names no other notice than library-write, so nothing is lost (sage-3596 item 3).
Files
Both deviations below were accepted in sage-3596 and are named in the manifest for Dewey. The brief owns these:
packages/library/src/render.mjs:renderReplyreplacesrenderItems.packages/library/src/index.mjs: the export.packages/library/README.md: the section.packages/library/tests/render.test.mjs.
It also owns bus.js, its stylesheet, serve.mjs (only the two routes) and new webui tests. Two
deviations, both recorded here:
- The chat view's message list is
packages/webui/src/public/chat.js:85(window.MosaicChat), notbus.js. The reply, notice and Revert rendering goes inchat.jsandchat.css. The status line goes in the chat thread view, so it is also inchat.js. packages/webui/src/console-bus.mjsandcli.mjseach get onelibraryRevertline, beside the existinglibraryStatuswiring.
Overlap with row 220 (C2-10a, Researcher, 3ef6da90d)
220 changes one line each in the webui tests discord.test.mjs, fixture.mjs, history-fixture.mjs, relaunch.test.mjs and return-flow.test.mjs. Its other paths are in control-board, conversation, discord and business. 225 doesn't touch those five files or those packages. Its webui tests are new files.
Handoff to Fethry (mos-1501, Q245)
The row moved to Fethry (Codex, fethry/row225). Everything below is untested: no suite or single file
has run on it, since a run needs Sage's CLEAR. Each changed file passes node --check.
Done in the handoff commit:
render.mjsrenderReply(body, {check, changed, name}); index export; README section;render.test.mjsrewritten.renderItemsis gone from every package.serve.mjs:/render.mjsstatic entry (owner and user file maps),GET /api/library/status(oneprojectquery, answer must name that project),POST /api/library/revert(save's boundary, strict{project, commit}, refusal shows the code, lost or unusable answer isoutcome-unknown, never retried). Both are nulled for a granted user, anduserRoutekeeps them owner-only. The tester listener refuseslibraryRevert.bus.mjs:library-revert-not-agent: 409. That re-pinspackages/webui/src/bus.mjsinpackages/bus/tests/channel-proof.test.mjs, with a comment line, as row 254 did. It's a third deviation, in another package's test.console-bus.mjsandcli.mjs:libraryRevert, besidelibraryStatus.chat.js: dynamicimport('/render.mjs')(CSP isscript-src 'self'); body text until it loads. Cites with a scope badge, the agent-written label, "changed since" fromfile/last, links only for https withrel="noopener noreferrer". A notice gets a Revert button behindconfirm(). The library status line goes under the thread heading.chat.css: cite, badge, notice and status styles.- New tests:
packages/webui/tests/library-routes.test.mjsandpackages/webui/tests/chat-replies-browser.test.mjs. 220's five files are untouched.
Decisions made in the build:
- The library name is the chat project's
repo. A project with no repo reads no status and its Revert does nothing. - These codes hide the status line:
project-not-declared,library-mapping-invalid,project-not-configured,not-configuredandowner-only.- Otherwise every code-repo thread, and every granted user, would show "unavailable".
- Cost: a broken library mapping also hides the line.
library.statusfrom the CLI still shows it.
- "Age" is the time since
lastFetch("fetched 5 min ago"), not the served commit's age, which the status doesn't carry. - The limits fields are assumed to be
replyLimits {requireCitation, citationsPerReply}andreplyLimitsFallback [keys], and are shown only when present. Not agreed with 217. Confirm with Beakley and rename. - A notice body's shape is all the renderer checks. An agent in a business without a library could
send a body that looks like a notice. The page would show it, but
library.revertstill refuses it.
Left:
- Get CLEAR and run the webui and library suites,
channel-proof.test.mjs, and the two new files whole. The browser test's selectors and waits have never run. - Agree the limits field names with 217 (Beakley).
- Granted-user R6 G5 (
granted-user.test.mjs:252) lists owner verbs by hand. Addlibrary.revertonce 288 lands. - BUILD.md: the mutant list and the mos-1275 timeout plan.
- The manifest names the three deviations: chat.js and chat.css in place of bus.js, the console-bus and cli lines, and the channel-proof re-pin.