fix(gateway): build row metadata for single session lists

Refs #92057.

Build the request-scoped row metadata context for every non-empty sessions.list result, including limit=1, so single-row lists use the shared subagent metadata read index instead of direct per-row registry snapshot lookups. This keeps the existing single-row store child-session candidate optimization intact while removing the single-row metadata-cache gap.

Verification:
- node scripts/run-vitest.mjs src/gateway/session-utils.single-row-cache.test.ts --maxWorkers=1
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- Crabbox run_f89b56ffea83 / cbx_f1b1f5013225: OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changed
- GitHub PR checks clean on 1ba6619f2e
This commit is contained in:
JC
2026-06-14 04:50:30 -07:00
committed by GitHub
parent c78f9376d9
commit fff193402e
2 changed files with 63 additions and 3 deletions
@@ -72,7 +72,11 @@ const subagentRegistryReadMock = vi.hoisted(() => {
vi.mock("../agents/subagent-registry-read.js", () => subagentRegistryReadMock);
import { loadGatewaySessionRow } from "./session-utils.js";
import {
listSessionsFromStore,
listSessionsFromStoreAsync,
loadGatewaySessionRow,
} from "./session-utils.js";
const MAIN_AGENT_ID = "main";
const TEST_MODEL = "openai/gpt-5.4";
@@ -238,6 +242,62 @@ describe("single gateway session row child-session cache", () => {
);
});
test("builds shared subagent metadata context for single-row session lists", async () => {
await withSingleRowCacheStore(
"openclaw-single-row-list-context-",
"/tmp/openclaw-single-row-list-context",
async ({ now, storePath }) => {
const store: Record<string, SessionEntry> = {
"agent:main:discord:channel:parent": parentSession("parent", now),
};
const cfg: OpenClawConfig = {
agents: {
list: [
{
id: MAIN_AGENT_ID,
default: true,
workspace: "/tmp/openclaw-single-row-list-context",
},
],
defaults: { model: { primary: TEST_MODEL } },
},
} as OpenClawConfig;
const syncListed = listSessionsFromStore({
cfg,
storePath,
store,
opts: { agentId: MAIN_AGENT_ID, limit: 1 },
});
expect(syncListed.sessions).toHaveLength(1);
expect(subagentRegistryReadMock.buildSubagentRunReadIndex).toHaveBeenCalledTimes(
1,
);
expect(
subagentRegistryReadMock.getSessionDisplaySubagentRunByChildSessionKey,
).not.toHaveBeenCalled();
vi.clearAllMocks();
const asyncListed = await listSessionsFromStoreAsync({
cfg,
storePath,
store,
opts: { agentId: MAIN_AGENT_ID, limit: 1 },
});
expect(asyncListed.sessions).toHaveLength(1);
expect(subagentRegistryReadMock.buildSubagentRunReadIndex).toHaveBeenCalledTimes(
1,
);
expect(
subagentRegistryReadMock.getSessionDisplaySubagentRunByChildSessionKey,
).not.toHaveBeenCalled();
},
);
});
test("rebuilds store child candidates after same-object session store writes", async () => {
await withSingleRowCacheStore(
"openclaw-single-row-cache-write-version-",
+2 -2
View File
@@ -2747,7 +2747,7 @@ export function listSessionsFromStore(params: {
: undefined;
const sharedRowContext =
fullRowContext ??
(entries.length > 1 ? buildSessionListRowMetadataContext({ now }) : undefined);
(entries.length > 0 ? buildSessionListRowMetadataContext({ now }) : undefined);
const sessions = entries.map(([key, entry], index) => {
const includeTranscriptFields = index < sessionListTranscriptFieldRows;
@@ -2837,7 +2837,7 @@ export async function listSessionsFromStoreAsync(params: {
: undefined;
const sharedRowContext =
fullRowContext ??
(entries.length > 1 ? buildSessionListRowMetadataContext({ now }) : undefined);
(entries.length > 0 ? buildSessionListRowMetadataContext({ now }) : undefined);
const sessions: GatewaySessionRow[] = [];
for (let i = 0; i < entries.length; i++) {