mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-12 13:52:53 +00:00
refactor: share subagent delivery context test helpers
This commit is contained in:
@@ -81,6 +81,14 @@ type StoredEntry = {
|
||||
lastAccountId?: string;
|
||||
};
|
||||
|
||||
type StoreEntries = Parameters<typeof writeSessionStore>[0]["entries"];
|
||||
|
||||
async function prepareSessionStore(entries: StoreEntries = {}): Promise<void> {
|
||||
setRegistry(defaultRegistry);
|
||||
testState.sessionStorePath = sessionStorePath;
|
||||
await writeSessionStore({ entries });
|
||||
}
|
||||
|
||||
function readStoredEntry(stored: Record<string, StoredEntry>, key: string): StoredEntry {
|
||||
const entry = stored[key];
|
||||
if (!entry) {
|
||||
@@ -96,34 +104,50 @@ function readDeliveryContext(entry: StoredEntry): NonNullable<StoredEntry["deliv
|
||||
return entry.deliveryContext;
|
||||
}
|
||||
|
||||
async function readStoredSessionEntry(key: string): Promise<StoredEntry> {
|
||||
const stored = JSON.parse(await fs.readFile(sessionStorePath, "utf-8")) as Record<
|
||||
string,
|
||||
StoredEntry
|
||||
>;
|
||||
return readStoredEntry(stored, key);
|
||||
}
|
||||
|
||||
async function sendAgentRequest(params: Record<string, unknown>): Promise<void> {
|
||||
const res = await rpcReq(ws, "agent", {
|
||||
deliver: false,
|
||||
...params,
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
}
|
||||
|
||||
function expectDeliveryContextFields(entry: StoredEntry, expected: Record<string, unknown>): void {
|
||||
const deliveryContext = readDeliveryContext(entry);
|
||||
for (const [key, value] of Object.entries(expected)) {
|
||||
expect(deliveryContext[key as keyof typeof deliveryContext]).toBe(value);
|
||||
}
|
||||
}
|
||||
|
||||
describe("subagent session deliveryContext from spawn request params", () => {
|
||||
test("new subagent session inherits deliveryContext from request channel/to/threadId", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
testState.sessionStorePath = sessionStorePath;
|
||||
await writeSessionStore({ entries: {} });
|
||||
await prepareSessionStore();
|
||||
|
||||
const res = await rpcReq(ws, "agent", {
|
||||
await sendAgentRequest({
|
||||
message: "[Subagent Task]: analyze data",
|
||||
sessionKey: "agent:main:subagent:test-delivery-ctx",
|
||||
channel: "slack",
|
||||
to: "channel:C0AF8TW48UQ",
|
||||
accountId: "default",
|
||||
threadId: "1774374945.091819",
|
||||
deliver: false,
|
||||
idempotencyKey: "idem-subagent-delivery-ctx-1",
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
const stored = JSON.parse(await fs.readFile(sessionStorePath, "utf-8")) as Record<
|
||||
string,
|
||||
StoredEntry
|
||||
>;
|
||||
const entry = readStoredEntry(stored, "agent:main:subagent:test-delivery-ctx");
|
||||
const deliveryContext = readDeliveryContext(entry);
|
||||
expect(deliveryContext.channel).toBe("slack");
|
||||
expect(deliveryContext.to).toBe("channel:C0AF8TW48UQ");
|
||||
expect(deliveryContext.threadId).toBe("1774374945.091819");
|
||||
expect(deliveryContext.accountId).toBe("default");
|
||||
const entry = await readStoredSessionEntry("agent:main:subagent:test-delivery-ctx");
|
||||
expectDeliveryContextFields(entry, {
|
||||
channel: "slack",
|
||||
to: "channel:C0AF8TW48UQ",
|
||||
threadId: "1774374945.091819",
|
||||
accountId: "default",
|
||||
});
|
||||
expect(entry.route).toEqual({
|
||||
channel: "slack",
|
||||
accountId: "default",
|
||||
@@ -135,103 +159,84 @@ describe("subagent session deliveryContext from spawn request params", () => {
|
||||
});
|
||||
|
||||
test("existing session deliveryContext is NOT overwritten by request params", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
testState.sessionStorePath = sessionStorePath;
|
||||
await writeSessionStore({
|
||||
entries: {
|
||||
"agent:main:subagent:existing-ctx": {
|
||||
sessionId: "sess-existing",
|
||||
updatedAt: Date.now(),
|
||||
deliveryContext: {
|
||||
channel: "slack",
|
||||
to: "user:U09U1LV7JDN",
|
||||
accountId: "default",
|
||||
threadId: "1771242986.529939",
|
||||
},
|
||||
lastChannel: "slack",
|
||||
lastTo: "user:U09U1LV7JDN",
|
||||
lastAccountId: "default",
|
||||
lastThreadId: "1771242986.529939",
|
||||
await prepareSessionStore({
|
||||
"agent:main:subagent:existing-ctx": {
|
||||
sessionId: "sess-existing",
|
||||
updatedAt: Date.now(),
|
||||
deliveryContext: {
|
||||
channel: "slack",
|
||||
to: "user:U09U1LV7JDN",
|
||||
accountId: "default",
|
||||
threadId: "1771242986.529939",
|
||||
},
|
||||
lastChannel: "slack",
|
||||
lastTo: "user:U09U1LV7JDN",
|
||||
lastAccountId: "default",
|
||||
lastThreadId: "1771242986.529939",
|
||||
},
|
||||
});
|
||||
|
||||
const res = await rpcReq(ws, "agent", {
|
||||
await sendAgentRequest({
|
||||
message: "follow-up",
|
||||
sessionKey: "agent:main:subagent:existing-ctx",
|
||||
channel: "slack",
|
||||
to: "channel:C0AF8TW48UQ",
|
||||
threadId: "9999999999.000000",
|
||||
deliver: false,
|
||||
idempotencyKey: "idem-subagent-delivery-ctx-2",
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
const stored = JSON.parse(await fs.readFile(sessionStorePath, "utf-8")) as Record<
|
||||
string,
|
||||
StoredEntry
|
||||
>;
|
||||
const entry = readStoredEntry(stored, "agent:main:subagent:existing-ctx");
|
||||
const deliveryContext = readDeliveryContext(entry);
|
||||
const entry = await readStoredSessionEntry("agent:main:subagent:existing-ctx");
|
||||
// The ORIGINAL deliveryContext should be preserved (primary wins in merge).
|
||||
expect(deliveryContext.to).toBe("user:U09U1LV7JDN");
|
||||
expect(deliveryContext.threadId).toBe("1771242986.529939");
|
||||
expectDeliveryContextFields(entry, {
|
||||
to: "user:U09U1LV7JDN",
|
||||
threadId: "1771242986.529939",
|
||||
});
|
||||
expect(entry.lastTo).toBe("user:U09U1LV7JDN");
|
||||
});
|
||||
|
||||
test("existing session route metadata survives agent request delivery normalization", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
testState.sessionStorePath = sessionStorePath;
|
||||
await writeSessionStore({
|
||||
entries: {
|
||||
"agent:main:subagent:existing-route-metadata": {
|
||||
sessionId: "sess-existing-route",
|
||||
updatedAt: Date.now(),
|
||||
route: {
|
||||
channel: "slack",
|
||||
accountId: "default",
|
||||
target: {
|
||||
to: "channel:C0AF8TW48UQ",
|
||||
rawTo: "slack://C0AF8TW48UQ",
|
||||
chatType: "channel",
|
||||
},
|
||||
thread: {
|
||||
id: "1771242986.529939",
|
||||
kind: "thread",
|
||||
source: "target",
|
||||
},
|
||||
},
|
||||
deliveryContext: {
|
||||
channel: "slack",
|
||||
await prepareSessionStore({
|
||||
"agent:main:subagent:existing-route-metadata": {
|
||||
sessionId: "sess-existing-route",
|
||||
updatedAt: Date.now(),
|
||||
route: {
|
||||
channel: "slack",
|
||||
accountId: "default",
|
||||
target: {
|
||||
to: "channel:C0AF8TW48UQ",
|
||||
accountId: "default",
|
||||
threadId: "1771242986.529939",
|
||||
rawTo: "slack://C0AF8TW48UQ",
|
||||
chatType: "channel",
|
||||
},
|
||||
thread: {
|
||||
id: "1771242986.529939",
|
||||
kind: "thread",
|
||||
source: "target",
|
||||
},
|
||||
lastChannel: "slack",
|
||||
lastTo: "channel:C0AF8TW48UQ",
|
||||
lastAccountId: "default",
|
||||
lastThreadId: "1771242986.529939",
|
||||
},
|
||||
deliveryContext: {
|
||||
channel: "slack",
|
||||
to: "channel:C0AF8TW48UQ",
|
||||
accountId: "default",
|
||||
threadId: "1771242986.529939",
|
||||
},
|
||||
lastChannel: "slack",
|
||||
lastTo: "channel:C0AF8TW48UQ",
|
||||
lastAccountId: "default",
|
||||
lastThreadId: "1771242986.529939",
|
||||
},
|
||||
});
|
||||
|
||||
const res = await rpcReq(ws, "agent", {
|
||||
await sendAgentRequest({
|
||||
message: "follow-up",
|
||||
sessionKey: "agent:main:subagent:existing-route-metadata",
|
||||
channel: "slack",
|
||||
to: "channel:C0AF8TW48UQ",
|
||||
accountId: "default",
|
||||
threadId: "1771242986.529939",
|
||||
deliver: false,
|
||||
idempotencyKey: "idem-subagent-delivery-route-metadata",
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
const stored = JSON.parse(await fs.readFile(sessionStorePath, "utf-8")) as Record<
|
||||
string,
|
||||
StoredEntry
|
||||
>;
|
||||
const entry = readStoredEntry(stored, "agent:main:subagent:existing-route-metadata");
|
||||
const entry = await readStoredSessionEntry("agent:main:subagent:existing-route-metadata");
|
||||
expect(entry.route).toEqual({
|
||||
channel: "slack",
|
||||
accountId: "default",
|
||||
@@ -253,41 +258,32 @@ describe("subagent session deliveryContext from spawn request params", () => {
|
||||
// first (to set spawnDepth, spawnedBy, etc.), then calls callSubagentGateway({method: "agent"}).
|
||||
// The sessions.patch creates a partial entry without deliveryContext.
|
||||
// The agent handler must seed deliveryContext from the request params.
|
||||
setRegistry(defaultRegistry);
|
||||
testState.sessionStorePath = sessionStorePath;
|
||||
await writeSessionStore({
|
||||
entries: {
|
||||
"agent:main:subagent:pre-patched": {
|
||||
sessionId: "sess-pre-patched",
|
||||
updatedAt: Date.now(),
|
||||
spawnDepth: 1,
|
||||
spawnedBy: "agent:main:slack:direct:u07fdr83w6n:thread:1775577152.364109",
|
||||
},
|
||||
await prepareSessionStore({
|
||||
"agent:main:subagent:pre-patched": {
|
||||
sessionId: "sess-pre-patched",
|
||||
updatedAt: Date.now(),
|
||||
spawnDepth: 1,
|
||||
spawnedBy: "agent:main:slack:direct:u07fdr83w6n:thread:1775577152.364109",
|
||||
},
|
||||
});
|
||||
|
||||
const res = await rpcReq(ws, "agent", {
|
||||
await sendAgentRequest({
|
||||
message: "[Subagent Task]: investigate data",
|
||||
sessionKey: "agent:main:subagent:pre-patched",
|
||||
channel: "slack",
|
||||
to: "user:U07FDR83W6N",
|
||||
accountId: "default",
|
||||
threadId: "1775577152.364109",
|
||||
deliver: false,
|
||||
idempotencyKey: "idem-subagent-delivery-ctx-prepatched",
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
const stored = JSON.parse(await fs.readFile(sessionStorePath, "utf-8")) as Record<
|
||||
string,
|
||||
StoredEntry
|
||||
>;
|
||||
const entry = readStoredEntry(stored, "agent:main:subagent:pre-patched");
|
||||
const deliveryContext = readDeliveryContext(entry);
|
||||
expect(deliveryContext.channel).toBe("slack");
|
||||
expect(deliveryContext.to).toBe("user:U07FDR83W6N");
|
||||
expect(deliveryContext.threadId).toBe("1775577152.364109");
|
||||
expect(deliveryContext.accountId).toBe("default");
|
||||
const entry = await readStoredSessionEntry("agent:main:subagent:pre-patched");
|
||||
expectDeliveryContextFields(entry, {
|
||||
channel: "slack",
|
||||
to: "user:U07FDR83W6N",
|
||||
threadId: "1775577152.364109",
|
||||
accountId: "default",
|
||||
});
|
||||
expect(entry.route).toEqual({
|
||||
channel: "slack",
|
||||
accountId: "default",
|
||||
@@ -298,26 +294,18 @@ describe("subagent session deliveryContext from spawn request params", () => {
|
||||
});
|
||||
|
||||
test("request without to/threadId does not inject empty values", async () => {
|
||||
setRegistry(defaultRegistry);
|
||||
testState.sessionStorePath = sessionStorePath;
|
||||
await writeSessionStore({ entries: {} });
|
||||
await prepareSessionStore();
|
||||
|
||||
const res = await rpcReq(ws, "agent", {
|
||||
await sendAgentRequest({
|
||||
message: "internal task",
|
||||
sessionKey: "agent:main:subagent:no-routing",
|
||||
channel: "slack",
|
||||
deliver: false,
|
||||
idempotencyKey: "idem-subagent-delivery-ctx-3",
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
|
||||
const stored = JSON.parse(await fs.readFile(sessionStorePath, "utf-8")) as Record<
|
||||
string,
|
||||
StoredEntry
|
||||
>;
|
||||
const entry = readStoredEntry(stored, "agent:main:subagent:no-routing");
|
||||
const entry = await readStoredSessionEntry("agent:main:subagent:no-routing");
|
||||
expectDeliveryContextFields(entry, { channel: "slack" });
|
||||
const deliveryContext = readDeliveryContext(entry);
|
||||
expect(deliveryContext.channel).toBe("slack");
|
||||
expect(deliveryContext.to).toBeUndefined();
|
||||
expect(deliveryContext.threadId).toBeUndefined();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user