





















@@ -1,7 +1,9 @@
11import fs from "node:fs/promises";
2+import os from "node:os";
23import path from "node:path";
34import { afterAll, beforeAll, beforeEach, describe, expect, it, type Mock } from "vitest";
45import { resolveSessionTranscriptPath } from "../config/sessions.js";
6+import type { OpenClawConfig } from "../config/types.openclaw.js";
57import { emitAgentEvent } from "../infra/agent-events.js";
68import { captureEnv } from "../test-utils/env.js";
79import {
@@ -10,6 +12,7 @@ import {
1012installGatewayTestHooks,
1113startGatewayServer,
1214testState,
15+writeSessionStore,
1316} from "./test-helpers.js";
14171518const { createOpenClawTools } = await import("../agents/openclaw-tools.js");
@@ -45,12 +48,26 @@ async function emitLifecycleAssistantReply(params: {
4548}) {
4649const commandParams = params.opts as {
4750sessionId?: string;
51+sessionKey?: string;
4852runId?: string;
4953extraSystemPrompt?: string;
5054};
5155const sessionId = commandParams.sessionId ?? params.defaultSessionId;
5256const runId = commandParams.runId ?? sessionId;
53-const sessionFile = resolveSessionTranscriptPath(sessionId);
57+let sessionFile = resolveSessionTranscriptPath(sessionId);
58+if (testState.sessionStorePath && commandParams.sessionKey) {
59+const rawStore = JSON.parse(await fs.readFile(testState.sessionStorePath, "utf-8")) as Record<
60+string,
61+{
62+sessionId?: string;
63+sessionFile?: string;
64+}
65+>;
66+const entry = rawStore[commandParams.sessionKey];
67+if (entry?.sessionId === sessionId && entry.sessionFile) {
68+sessionFile = entry.sessionFile;
69+}
70+}
5471await fs.mkdir(path.dirname(sessionFile), { recursive: true });
55725673const startedAt = Date.now();
@@ -220,3 +237,97 @@ describe("sessions_send label lookup", () => {
220237},
221238);
222239});
240+241+describe("sessions_send agent targeting", () => {
242+it(
243+"starts configured agent main session by agentId before sending",
244+{ timeout: SESSION_SEND_E2E_TIMEOUT_MS },
245+async () => {
246+const configPath = process.env.OPENCLAW_CONFIG_PATH;
247+if (!configPath) {
248+throw new Error("OPENCLAW_CONFIG_PATH missing in gateway test environment");
249+}
250+const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-send-agent-"));
251+const config: OpenClawConfig = {
252+tools: {
253+sessions: {
254+visibility: "all",
255+},
256+agentToAgent: {
257+enabled: true,
258+},
259+},
260+agents: {
261+list: [{ id: "main", default: true }, { id: "orion" }],
262+},
263+};
264+265+testState.sessionStorePath = path.join(dir, "sessions.json");
266+testState.agentsConfig = config.agents;
267+try {
268+await fs.mkdir(path.dirname(configPath), { recursive: true });
269+await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
270+await writeSessionStore({
271+entries: {
272+main: {
273+sessionId: "sess-main",
274+updatedAt: Date.now(),
275+},
276+},
277+});
278+279+const spy = agentCommand as unknown as Mock<(opts: unknown) => Promise<void>>;
280+spy.mockImplementation(async (opts: unknown) =>
281+emitLifecycleAssistantReply({
282+ opts,
283+defaultSessionId: "orion-created",
284+resolveText: () => "orion response",
285+}),
286+);
287+spy.mockClear();
288+289+const tool = createOpenClawTools({
290+agentSessionKey: "agent:main:main",
291+ config,
292+}).find((candidate) => candidate.name === "sessions_send");
293+if (!tool) {
294+throw new Error("missing sessions_send tool");
295+}
296+297+const result = await tool.execute("call-agent-id", {
298+agentId: "orion",
299+message: "hello orion",
300+timeoutSeconds: 5,
301+});
302+const details = result.details as {
303+status?: string;
304+reply?: string;
305+sessionKey?: string;
306+};
307+expect(details.status).toBe("ok");
308+expect(details.reply).toBe("orion response");
309+expect(details.sessionKey).toBe("agent:orion:main");
310+311+const firstCall = spy.mock.calls.at(0)?.[0] as
312+| { sessionId?: string; sessionKey?: string }
313+| undefined;
314+expect(firstCall?.sessionKey).toBe("agent:orion:main");
315+expect(firstCall?.sessionId).toBeTypeOf("string");
316+317+const rawStore = JSON.parse(
318+await fs.readFile(testState.sessionStorePath, "utf-8"),
319+) as Record<
320+string,
321+{
322+sessionId?: string;
323+}
324+>;
325+expect(rawStore["agent:orion:main"]?.sessionId).toBe(firstCall?.sessionId);
326+} finally {
327+testState.agentsConfig = undefined;
328+testState.sessionStorePath = undefined;
329+await fs.rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
330+}
331+},
332+);
333+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。