feat(discord): eyes reaction as a read receipt on every admitted message (#1509)

QUEUE row 15, MVP iteration 1 after the Sage pilot. rest.react is best
effort (2xx true, anything else false, never throws); the connector reacts
at admission before the engine runs and records the outcome in the turn
record as receipt. Drops and refusals get no reaction. Suite 28/28, 90
node tests.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-13 14:20:30 -05:00
co-authored by Claude Fable 5.1
parent 788515dc69
commit 93d6b62457
10 changed files with 115 additions and 6 deletions
+33 -1
View File
@@ -2,7 +2,7 @@ import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { join } from "node:path";
import { createConnector, FIXED_LINES, RECONCILE_WINDOW_MS } from "../src/connector.mjs";
import { createConnector, FIXED_LINES, RECONCILE_WINDOW_MS, READ_RECEIPT } from "../src/connector.mjs";
import { readOutbox, readDrops, listTurns, readInboxIds, appendOutbox, appendInbox, ensureJournal, requestStop, writeTurn, countAdmissionsOn, noticeOn } from "../src/journal.mjs";
import { makeRoot, binding, message, IDS, fakeRest, fakeGateway, fakeEngine } from "./helpers.mjs";
@@ -403,3 +403,35 @@ test("journal: no token-shaped string and no model output on the drop path reach
}
await connector.stop();
});
test("receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn", async () => {
const { journalDir, rest, connector } = setup({ bindingOverrides: { limits: { turnsPerDay: 1, turnTimeoutSeconds: 180, replyChunkChars: 1900, inboundMaxChars: 4000 } } });
await connector.start();
const r1 = await connector.handleMessage(message({ id: "300000000000000301", content: "first" }));
assert.equal(await r1.turn, "ok");
assert.deepEqual(rest.reactions, [{ channelId: IDS.admin, messageId: "300000000000000301", emoji: READ_RECEIPT }]);
assert.equal(READ_RECEIPT, "\u{1F440}");
let turns = listTurns(journalDir);
assert.deepEqual(turns[0].receipt, { emoji: READ_RECEIPT, ok: true });
// Unlisted author: dropped, no reaction.
const r2 = await connector.handleMessage(message({ id: "300000000000000302", author: { id: IDS.stranger, username: "s" } }));
assert.equal(r2.accepted, false);
// Over the ceiling: refused with the fixed line, no reaction.
const r3 = await connector.handleMessage(message({ id: "300000000000000303", content: "second" }));
assert.deepEqual(r3, { accepted: false, reason: "ceiling" });
assert.equal(rest.reactions.length, 1);
await connector.stop();
});
test("receipt: Discord refusing the reaction leaves the turn intact and records ok false", async () => {
const { journalDir, rest, connector } = setup();
rest.reactOk = false;
await connector.start();
const r = await connector.handleMessage(message({ id: "300000000000000311", content: "hi" }));
assert.equal(await r.turn, "ok");
assert.equal(rest.calls.length, 1, "the reply still went out");
const turns = listTurns(journalDir);
assert.equal(turns[0].status, "ok");
assert.deepEqual(turns[0].receipt, { emoji: READ_RECEIPT, ok: false });
await connector.stop();
});