From 2167a1b0332400f0d616729c11e6785b8f0ea6cd Mon Sep 17 00:00:00 2001 From: Joakim Persson Date: Tue, 22 Sep 2026 14:15:22 +0200 Subject: [PATCH] fix(mailbox): pass `order: "desc"` on every cursor-less event_list call Three of the five `mempalace_event_list` calls in the pi extension relied on the server's default ordering: deriveOwed's `status: "open"` candidate query (limit 50) and both windows in deriveClosed (from_agent limit 100, to_agent limit 50). deriveOwed's other two calls already say `order: "desc"` and carry the comment explaining why: on mempalace <= 3.9.0 the default is `asc`, so a cursor-less `limit: N` returns the OLDEST N events, and once a device passes N authored events its newest asks and the replies that close them fall outside the join window. deriveClosed had exactly that latent truncation. Why now: mempalace 3.10.0 (2026-09-15) flips the cursor-less default to newest-first ("Logstream listings default to the newest events when no cursor is given"). That change is server-side -- the extension talks to the fleet hub over MEMPALACE_REMOTE_URL, so it lands when the hub upgrades, not when a client does -- and it would have silently FIXED deriveClosed on 3.10.0 while leaving it broken against any 3.9.0 hub. A mailbox verdict that depends on which server version answers is the wrong shape. Saying `order` explicitly makes both derive* functions read the same window on either version. The selection in deriveClosed (isStrictlyAfter per correlation) is order-independent, so this changes WHICH events are in the window, not how the winner is picked. No behaviour change on a device with < 50 inbound and < 100 authored events; the fleet hub is at 176 events total today, so the window was about to matter. Verified: file compiles under pi's own loader (jiti 2.7.0 from the installed pi-coding-agent) before and after; `order:"desc"` literal count 3 -> 7, which is the 3 added arguments plus 1 comment mention. `node --check` and a bare `tsc` were tried first and both fail identically on HEAD (inline `type` imports / no @types/node) -- checker faults, not this change. --- extensions/pi/mempalace.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/extensions/pi/mempalace.ts b/extensions/pi/mempalace.ts index 5e21833..f30869f 100644 --- a/extensions/pi/mempalace.ts +++ b/extensions/pi/mempalace.ts @@ -1209,10 +1209,16 @@ export default async function mempalaceExtension(pi: ExtensionAPI) { if (!mailboxEnabled || !available) return []; try { const [candidatesRaw, mineRaw, inboundRaw] = await Promise.all([ + // Every cursor-less event_list call in this file passes `order` explicitly. + // The server's default is not ours to lean on: mempalace <= 3.9.0 defaults + // to `asc`, 3.10.0 flips a cursor-less listing to newest-first. Both + // derive* joins want the NEWEST window (see the comment below), so say so + // and the verdict stops depending on which server version answers. client.callTool("mempalace_event_list", { to_agent: mailboxAddress, status: "open", limit: 50, + order: "desc", }), // `order: "desc"` is a fix, not a flourish. event_list defaults to `asc` // (append order), so this asked for the OLDEST 100 events this device @@ -1277,10 +1283,15 @@ export default async function mempalaceExtension(pi: ExtensionAPI) { async function deriveClosed(): Promise { if (!mailboxEnabled || !available) return []; try { + // `order: "desc"` for the same reason as deriveOwed: without it a 3.9.0 + // server hands back the OLDEST window, so past 100 authored events this + // device's newest asks (and past 50 inbound, the replies that close them) + // fall outside the join. The selection below is order-independent + // (isStrictlyAfter), so this only fixes WHICH events are in the window. const [mineRaw, inboundRaw] = await Promise.all([ - client.callTool("mempalace_event_list", { from_agent: mailboxAddress, limit: 100 }), + client.callTool("mempalace_event_list", { from_agent: mailboxAddress, limit: 100, order: "desc" }), // NO status filter, deliberately: that omission is the entire fix. - client.callTool("mempalace_event_list", { to_agent: mailboxAddress, limit: 50 }), + client.callTool("mempalace_event_list", { to_agent: mailboxAddress, limit: 50, order: "desc" }), ]); // Correlations this device opened as a DIRECTED ask. A broadcast owes // nobody a reply, so it cannot be closed by one either.