bus: list messages stranded by a holder change in agents, row 80 (#1574)
A holder that releases a role without reading what it was delivered leaves
those messages where no later holder receives or reads them. The agents
view now lists them for human and reader sessions under `stranded`, as
{id, from, run, at}, oldest first, at most 50. The bus doesn't redeliver,
route or mark them read; a person who still needs one sends it again.
Round 2 is a mechanical rebase of round 1 onto row 93. Reviewed by Filbert
(28206, approve); Sage gate record 28207. Gate on 7496251c: 14 node suites
and 9 scripts green.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -55,7 +55,8 @@ test('agents: a held role with nothing on the bus is idle; a role session gets t
|
||||
['coder', 'idle', null, []],
|
||||
['pm', 'idle', null, []],
|
||||
]);
|
||||
for (const r of rows) assert.deepEqual(Object.keys(r), [...CLAIM, 'state', 'working', 'inputNeeded']);
|
||||
for (const r of rows) assert.deepEqual(Object.keys(r), [...CLAIM, 'state', 'working', 'inputNeeded', 'stranded']);
|
||||
assert.deepEqual(rows.map((r) => r.stranded), [[], []]);
|
||||
assert.deepEqual(call(b, reader, 'agents'), rows);
|
||||
const plain = call(b, pm, 'agents');
|
||||
for (const r of plain) assert.deepEqual(Object.keys(r), CLAIM);
|
||||
@@ -157,3 +158,83 @@ test('agents: state reads only its own business, even for a run name both use',
|
||||
const there = byRole(call(b, otherHuman, 'agents')).cto;
|
||||
assert.deepEqual([there.state, there.working.messages, there.inputNeeded.length], ['working', 1, 1]);
|
||||
});
|
||||
|
||||
// Row 80 (#1574): a message delivered to a run that released the role unread is stranded for the
|
||||
// role. No later holder receives or reads it, and listing it writes no delivery row.
|
||||
test('agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it', (t) => {
|
||||
const { b, store, agent, human } = setup(t);
|
||||
const first = agent('pm', 'demo', 'pm-one');
|
||||
const sent = call(b, human, 'message.send', { to: 'pm', body: 'left behind' });
|
||||
assert.deepEqual(call(b, first, 'message.receive').map((m) => m.id), [sent.id]);
|
||||
const before = store.all('SELECT * FROM deliveries');
|
||||
call(b, first, 'role.release');
|
||||
assert.deepEqual(call(b, human, 'agents'), [], 'a released role is not listed');
|
||||
const second = agent('pm', 'demo', 'pm-two');
|
||||
const row = byRole(call(b, human, 'agents')).pm;
|
||||
assert.deepEqual(row.stranded, [{ id: sent.id, from: 'human', run: 'pm-one', at: delivered(store, sent.id) }]);
|
||||
assert.deepEqual(call(b, second, 'message.receive'), []);
|
||||
assert.throws(() => call(b, second, 'message.read', { id: sent.id }), { code: 'not-recipient' });
|
||||
assert.throws(() => call(b, first, 'message.read', { id: sent.id }), { code: 'not-holder' });
|
||||
assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded, row.stranded);
|
||||
assert.deepEqual(store.all('SELECT * FROM deliveries'), before, 'the claim and the listing write no delivery row');
|
||||
});
|
||||
|
||||
test('agents: a message the holder read is not stranded; one the current holder has not read is not listed', (t) => {
|
||||
const { b, agent, human } = setup(t);
|
||||
const first = agent('pm', 'demo', 'pm-one');
|
||||
const read = call(b, human, 'message.send', { to: 'pm', body: 'read first' });
|
||||
const unread = call(b, human, 'message.send', { to: 'pm', body: 'still held' });
|
||||
call(b, first, 'message.receive');
|
||||
call(b, first, 'message.read', { id: read.id });
|
||||
assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded, [], 'the current holder still holds the unread one');
|
||||
call(b, first, 'role.release');
|
||||
agent('pm', 'demo', 'pm-two');
|
||||
assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded.map((m) => m.id), [unread.id]);
|
||||
});
|
||||
|
||||
test('agents: stranded is oldest first and capped at 50 for a role', (t) => {
|
||||
const { b, agent, human } = setup(t);
|
||||
const first = agent('pm', 'demo', 'pm-one');
|
||||
const ids = Array.from({ length: 60 }, (_, i) => call(b, human, 'message.send', { to: 'pm', body: `m${i}` }).id);
|
||||
assert.equal(call(b, first, 'message.receive').length, 60);
|
||||
call(b, first, 'role.release');
|
||||
agent('pm', 'demo', 'pm-two');
|
||||
const stranded = byRole(call(b, human, 'agents')).pm.stranded;
|
||||
assert.equal(stranded.length, 50);
|
||||
assert.deepEqual(stranded.map((m) => m.id), ids.slice(0, 50));
|
||||
assert.ok(stranded.every((m) => m.run === 'pm-one' && m.from === 'human'));
|
||||
});
|
||||
|
||||
test('agents: stranded is per role and per business', (t) => {
|
||||
const { b, agent, human } = setup(t);
|
||||
const cto = agent('cto', 'demo', 'cto-one');
|
||||
const pm = agent('pm', 'demo', 'pm-one');
|
||||
const toCto = call(b, human, 'message.send', { to: 'cto', body: 'for cto' });
|
||||
const toPm = call(b, human, 'message.send', { to: 'pm', body: 'for pm' });
|
||||
call(b, cto, 'message.receive');
|
||||
call(b, pm, 'message.receive');
|
||||
call(b, cto, 'role.release');
|
||||
call(b, pm, 'role.release');
|
||||
agent('cto', 'demo', 'cto-two');
|
||||
agent('pm', 'demo', 'pm-two');
|
||||
const rows = byRole(call(b, human, 'agents'));
|
||||
assert.deepEqual(rows.cto.stranded.map((m) => m.id), [toCto.id]);
|
||||
assert.deepEqual(rows.pm.stranded.map((m) => m.id), [toPm.id]);
|
||||
// The same role and run name in another business stays there.
|
||||
const otherHuman = b.bindHuman({ business: 'other', human: 'jason', via: 'cli', outsideAgent: true });
|
||||
const otherPm = agent('pm', 'other', 'pm-one');
|
||||
const elsewhere = call(b, otherHuman, 'message.send', { to: 'pm', body: 'elsewhere' });
|
||||
call(b, otherPm, 'message.receive');
|
||||
call(b, otherPm, 'role.release');
|
||||
agent('pm', 'other', 'pm-two');
|
||||
assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded.map((m) => m.id), [toPm.id]);
|
||||
assert.deepEqual(byRole(call(b, otherHuman, 'agents')).pm.stranded.map((m) => m.id), [elsewhere.id]);
|
||||
});
|
||||
|
||||
// Row 80 (#1574), Sage ruling: a message routed to a held role and not yet received is not stranded.
|
||||
test('agents: a message routed to a held role and not yet received is not stranded', (t) => {
|
||||
const { b, agent, human } = setup(t);
|
||||
agent('pm', 'demo', 'pm-one');
|
||||
call(b, human, 'message.send', { to: 'pm', body: 'routed, not received' });
|
||||
assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded, []);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user