bus, cli, webui: every message to the human, read state, Messages page (#1588)
Row 94, round 2. Rocko built it, Dewey approved it (comment 28104) on manifest 53b01ea9 (16 paths, basee918a915). The bus keeps a messages view with read state, where delivered but not read stays read:false. The CLI gains messages and follow, and inbox leaves out messages already read while inbox --json stays byte-compatible. The Console gets a Messages page and an unread count in the nav. Mos gave the go under decision 123 (public/ files). Sage gate on645489e4plus the patch, 12:52:14Z to 12:55:44Z: bus 115, cli 129, webui 104, all fail 0. Every scripts/test-*.sh exited 0 (auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 27, release 14, task 98). Manifest 16/16 before and after. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -555,7 +555,7 @@ export class Broker {
|
||||
if (!object(args)) fail('invalid-request');
|
||||
this.#secretCheck(args);
|
||||
return this.#store.transaction(() => {
|
||||
if (s.reader && !['inbox', 'agents', 'tasks', 'trail', 'decisions', 'credentials', 'replies'].includes(request.verb))
|
||||
if (s.reader && !['inbox', 'agents', 'tasks', 'trail', 'decisions', 'credentials', 'replies', 'messages'].includes(request.verb))
|
||||
fail('read-only');
|
||||
const result = this.#dispatch(s, request.verb, args);
|
||||
this.#secretCheck(result);
|
||||
@@ -978,6 +978,27 @@ export class Broker {
|
||||
};
|
||||
});
|
||||
}
|
||||
// Row 94: the human's message history. Sequence-based cursors remain stable
|
||||
// as new messages arrive. Successful reads write no delivery or read receipt.
|
||||
case 'messages': {
|
||||
if (!s.human && !s.reader) fail('human-required');
|
||||
keys(a, ['limit', 'after']);
|
||||
if (('limit' in a && !(Number.isSafeInteger(a.limit) && a.limit >= 1 && a.limit <= 200)) ||
|
||||
('after' in a && !id(a.after))) fail('invalid-request');
|
||||
let cursor = null;
|
||||
if ('after' in a) {
|
||||
cursor = this.#store.get("SELECT seq FROM messages WHERE business=? AND to_role='human' AND id=?", s.business, a.after);
|
||||
if (!cursor) fail('message-not-found');
|
||||
}
|
||||
const counts = this.#store.get(
|
||||
"SELECT count(*) AS total, coalesce(sum(NOT EXISTS(SELECT 1 FROM deliveries d WHERE d.message=m.id AND d.op='read')),0) AS unread FROM messages m WHERE business=? AND to_role='human'", s.business);
|
||||
const limit = a.limit ?? 50;
|
||||
const rows = this.#store.all(
|
||||
"SELECT m.*, EXISTS(SELECT 1 FROM deliveries d WHERE d.message=m.id AND d.op='read') AS read FROM messages m WHERE business=? AND to_role='human' AND (? IS NULL OR m.seq<?) ORDER BY m.seq DESC LIMIT ?",
|
||||
s.business, cursor?.seq ?? null, cursor?.seq ?? null, limit + 1);
|
||||
const messages = rows.slice(0, limit).map(m => ({ ...m, read: !!m.read }));
|
||||
return { messages, ...counts, next: rows.length > limit ? messages.at(-1).id : null };
|
||||
}
|
||||
// Row 81 (#1575): the replies to a message, for the Console's web-send receipt. Human and
|
||||
// reader sessions only, like decisions. It writes no delivery or read row, so a reply it
|
||||
// lists still reaches the next `mosaic talk`. An id naming no message in the business
|
||||
|
||||
@@ -8,5 +8,6 @@ export function views(client) {
|
||||
decisions: (list) => client.call('decisions', { list }),
|
||||
credentials: () => client.call('credentials'),
|
||||
replies: (id) => client.call('replies', { id }),
|
||||
messages: (args = {}) => client.call('messages', args),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user