























@@ -66,9 +66,10 @@ function createDeferred<T>() {
66666767async function withGatewayChatHarness(
6868run: (ctx: { ws: GatewaySocket; createSessionDir: () => Promise<string> }) => Promise<void>,
69+options?: { headers?: Record<string, string> },
6970) {
7071const tempDirs: string[] = [];
71-const ws = await harness.openWs();
72+const ws = await harness.openWs(options?.headers);
7273const createSessionDir = async () => {
7374const sessionDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gw-"));
7475tempDirs.push(sessionDir);
@@ -865,7 +866,14 @@ describe("gateway server chat", () => {
865866{
866867id: "first",
867868ok: true,
868-payload: { runId: "idem-active-a", status: "started" },
869+payload: expect.objectContaining({
870+runId: "idem-active-a",
871+status: "started",
872+serverTiming: {
873+receivedToAckMs: expect.any(Number),
874+loadSessionMs: expect.any(Number),
875+},
876+}),
869877error: undefined,
870878},
871879]);
@@ -877,7 +885,14 @@ describe("gateway server chat", () => {
877885{
878886id: "first",
879887ok: true,
880-payload: { runId: "idem-active-a", status: "started" },
888+payload: expect.objectContaining({
889+runId: "idem-active-a",
890+status: "started",
891+serverTiming: {
892+receivedToAckMs: expect.any(Number),
893+loadSessionMs: expect.any(Number),
894+},
895+}),
881896error: undefined,
882897},
883898{
@@ -993,13 +1008,27 @@ describe("gateway server chat", () => {
9931008{
9941009id: "first",
9951010ok: true,
996-payload: { runId: "idem-sequential-a", status: "started" },
1011+payload: expect.objectContaining({
1012+runId: "idem-sequential-a",
1013+status: "started",
1014+serverTiming: {
1015+receivedToAckMs: expect.any(Number),
1016+loadSessionMs: expect.any(Number),
1017+},
1018+}),
9971019error: undefined,
9981020},
9991021{
10001022id: "second",
10011023ok: true,
1002-payload: { runId: "idem-sequential-b", status: "started" },
1024+payload: expect.objectContaining({
1025+runId: "idem-sequential-b",
1026+status: "started",
1027+serverTiming: {
1028+receivedToAckMs: expect.any(Number),
1029+loadSessionMs: expect.any(Number),
1030+},
1031+}),
10031032error: undefined,
10041033},
10051034]);
@@ -1326,47 +1355,70 @@ describe("gateway server chat", () => {
13261355process.env.OPENCLAW_DIAGNOSTICS = "timeline";
13271356process.env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH = timelinePath;
13281357try {
1329-await withGatewayChatHarness(async ({ ws, createSessionDir }) => {
1330-const spy = getReplyFromConfig;
1331-await connectOk(ws);
1358+await withGatewayChatHarness(
1359+async ({ ws, createSessionDir }) => {
1360+const spy = getReplyFromConfig;
1361+await connectOk(ws, {
1362+client: {
1363+id: GATEWAY_CLIENT_NAMES.CONTROL_UI,
1364+version: "1.0.0",
1365+platform: "web",
1366+mode: GATEWAY_CLIENT_MODES.WEBCHAT,
1367+},
1368+});
133213691333-await createSessionDir();
1334-await writeMainSessionStore();
1335-mockGetReplyFromConfigOnce(async () => undefined);
1370+ await createSessionDir();
1371+ await writeMainSessionStore();
1372+ mockGetReplyFromConfigOnce(async () => undefined);
133613731337-const sendRes = await rpcReq(ws, "chat.send", {
1338-sessionKey: "main",
1339-message: "hello",
1340-idempotencyKey: "idem-timeline",
1341-});
1342-expect(sendRes.ok).toBe(true);
1343-1344-await vi.waitFor(() => {
1345-expect(spy.mock.calls.length).toBeGreaterThan(0);
1346-}, FAST_WAIT_OPTS);
1347-await vi.waitFor(async () => {
1348-const events = await readTimelineEvents(timelinePath);
1349-const ackReady = events.find(
1350-(event) =>
1351-event.type === "mark" &&
1352-event.name === "gateway.chat_send.ack_ready" &&
1353-(event.attributes as Record<string, unknown> | undefined)?.runId === "idem-timeline",
1354-);
1355-expect(ackReady?.attributes).toMatchObject({
1374+const sendRes = await rpcReq(ws, "chat.send", {
1375+sessionKey: "main",
1376+message: "hello",
1377+idempotencyKey: "idem-timeline",
1378+});
1379+expect(sendRes.ok).toBe(true);
1380+expect(sendRes.payload).toMatchObject({
13561381runId: "idem-timeline",
1357-ackStatus: "started",
1382+status: "started",
1383+serverTiming: {
1384+receivedToAckMs: expect.any(Number),
1385+loadSessionMs: expect.any(Number),
1386+},
13581387});
1359-expect(
1360-events.some(
1388+1389+await vi.waitFor(() => {
1390+expect(spy.mock.calls.length).toBeGreaterThan(0);
1391+}, FAST_WAIT_OPTS);
1392+await vi.waitFor(async () => {
1393+const events = await readTimelineEvents(timelinePath);
1394+const ackReady = events.find(
13611395(event) =>
1362-event.type === "span.end" &&
1363-event.name === "gateway.chat_send.dispatch_inbound" &&
1396+event.type === "mark" &&
1397+event.name === "gateway.chat_send.ack_ready" &&
13641398(event.attributes as Record<string, unknown> | undefined)?.runId ===
13651399"idem-timeline",
1366-),
1367-).toBe(true);
1368-}, FAST_WAIT_OPTS);
1369-});
1400+);
1401+expect(ackReady?.attributes).toMatchObject({
1402+runId: "idem-timeline",
1403+ackStatus: "started",
1404+serverReceivedToAckMs: expect.any(Number),
1405+serverLoadSessionMs: expect.any(Number),
1406+});
1407+expect(
1408+events.some(
1409+(event) =>
1410+event.type === "span.end" &&
1411+event.name === "gateway.chat_send.dispatch_inbound" &&
1412+(event.attributes as Record<string, unknown> | undefined)?.runId ===
1413+"idem-timeline",
1414+),
1415+).toBe(true);
1416+}, FAST_WAIT_OPTS);
1417+},
1418+{
1419+headers: { origin: `http://127.0.0.1:${harness.port}` },
1420+},
1421+);
13701422} finally {
13711423if (previousDiagnostics === undefined) {
13721424delete process.env.OPENCLAW_DIAGNOSTICS;
@@ -1382,6 +1434,43 @@ describe("gateway server chat", () => {
13821434}
13831435});
138414361437+test("chat.send omits ACK server timing for public WebChat clients", async () => {
1438+await withGatewayChatHarness(
1439+async ({ ws, createSessionDir }) => {
1440+await connectOk(ws, {
1441+client: {
1442+id: GATEWAY_CLIENT_NAMES.WEBCHAT_UI,
1443+version: "1.0.0",
1444+platform: "web",
1445+mode: GATEWAY_CLIENT_MODES.WEBCHAT,
1446+},
1447+});
1448+1449+await createSessionDir();
1450+await writeMainSessionStore();
1451+mockGetReplyFromConfigOnce(async () => undefined);
1452+1453+const sendRes = await rpcReq(ws, "chat.send", {
1454+sessionKey: "main",
1455+message: "hello",
1456+idempotencyKey: "idem-public-webchat",
1457+});
1458+1459+expect(sendRes.ok).toBe(true);
1460+expect(sendRes.payload).toMatchObject({
1461+runId: "idem-public-webchat",
1462+status: "started",
1463+});
1464+expect(
1465+(sendRes.payload as { serverTiming?: unknown } | undefined)?.serverTiming,
1466+).toBeUndefined();
1467+},
1468+{
1469+headers: { origin: `http://127.0.0.1:${harness.port}` },
1470+},
1471+);
1472+});
1473+13851474test("chat.history hard-caps single oversized nested payloads", async () => {
13861475await withGatewayChatHarness(async ({ ws, createSessionDir }) => {
13871476const historyMaxBytes = 64 * 1024;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。