mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Verify real agent workflows through the hosted relay (#1998)
* test(relay): prove live daemon client workflow * test(relay): reduce live acceptance variance * test(relay): run acceptance through real codex
This commit is contained in:
@@ -60,6 +60,7 @@
|
|||||||
"test:e2e:all": "vitest run e2e.test.ts --maxWorkers=1",
|
"test:e2e:all": "vitest run e2e.test.ts --maxWorkers=1",
|
||||||
"test:e2e:real": "npm run test:integration:real",
|
"test:e2e:real": "npm run test:integration:real",
|
||||||
"test:e2e:local": "npm run test:integration:local",
|
"test:e2e:local": "npm run test:integration:local",
|
||||||
|
"test:e2e:relay:live": "cross-env RUN_LIVE_RELAY_E2E=1 vitest run src/server/daemon-e2e/live-relay.real.e2e.test.ts --maxWorkers=1",
|
||||||
"test:e2e:ui": "vitest --ui e2e.test.ts"
|
"test:e2e:ui": "vitest --ui e2e.test.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { afterEach, describe, expect, test } from "vitest";
|
||||||
|
import pino from "pino";
|
||||||
|
|
||||||
|
import { DaemonClient } from "../test-utils/daemon-client.js";
|
||||||
|
import { createTestPaseoDaemon, type TestPaseoDaemon } from "../test-utils/paseo-daemon.js";
|
||||||
|
import { generateLocalPairingOffer } from "../pairing-offer.js";
|
||||||
|
import { CodexAppServerAgentClient } from "../agent/providers/codex-app-server-agent.js";
|
||||||
|
import { buildRelayWebSocketUrl } from "@getpaseo/protocol/daemon-endpoints";
|
||||||
|
import {
|
||||||
|
parseConnectionOfferFromUrl,
|
||||||
|
type ConnectionOffer,
|
||||||
|
} from "@getpaseo/protocol/connection-offer";
|
||||||
|
|
||||||
|
const relayEndpoint = process.env.PASEO_LIVE_RELAY_ENDPOINT ?? "paseo-relay-next.fly.dev:443";
|
||||||
|
const liveTest = process.env.RUN_LIVE_RELAY_E2E === "1" ? test : test.skip;
|
||||||
|
|
||||||
|
function requireOffer(url: string): ConnectionOffer {
|
||||||
|
const offer = parseConnectionOfferFromUrl(url);
|
||||||
|
if (!offer) {
|
||||||
|
throw new Error("Pairing did not produce a relay connection offer");
|
||||||
|
}
|
||||||
|
return offer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pairingOfferFor(daemon: TestPaseoDaemon): Promise<ConnectionOffer> {
|
||||||
|
const pairing = await generateLocalPairingOffer({
|
||||||
|
paseoHome: daemon.paseoHome,
|
||||||
|
relayEnabled: true,
|
||||||
|
relayEndpoint,
|
||||||
|
relayPublicEndpoint: relayEndpoint,
|
||||||
|
relayUseTls: true,
|
||||||
|
relayPublicUseTls: true,
|
||||||
|
includeQr: false,
|
||||||
|
});
|
||||||
|
if (!pairing.url) {
|
||||||
|
throw new Error("Pairing did not produce a URL");
|
||||||
|
}
|
||||||
|
return requireOffer(pairing.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientFor(offer: ConnectionOffer): DaemonClient {
|
||||||
|
return new DaemonClient({
|
||||||
|
url: buildRelayWebSocketUrl({
|
||||||
|
endpoint: offer.relay.endpoint,
|
||||||
|
useTls: true,
|
||||||
|
serverId: offer.serverId,
|
||||||
|
role: "client",
|
||||||
|
}),
|
||||||
|
clientId: "clid_live_relay_acceptance",
|
||||||
|
clientType: "cli",
|
||||||
|
connectTimeoutMs: 30_000,
|
||||||
|
e2ee: { enabled: true, daemonPublicKeyB64: offer.daemonPublicKeyB64 },
|
||||||
|
reconnect: { enabled: false },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("live hosted relay", () => {
|
||||||
|
let daemon: TestPaseoDaemon | null = null;
|
||||||
|
let client: DaemonClient | null = null;
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await client?.close().catch(() => undefined);
|
||||||
|
await daemon?.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
liveTest(
|
||||||
|
"carries a complete DaemonClient agent workflow through the hosted relay",
|
||||||
|
async () => {
|
||||||
|
const logger = pino({ level: "silent" });
|
||||||
|
daemon = await createTestPaseoDaemon({
|
||||||
|
listen: "127.0.0.1",
|
||||||
|
relayEnabled: true,
|
||||||
|
relayEndpoint,
|
||||||
|
relayUseTls: true,
|
||||||
|
agentClients: { codex: new CodexAppServerAgentClient(logger) },
|
||||||
|
logger,
|
||||||
|
});
|
||||||
|
const offer = await pairingOfferFor(daemon);
|
||||||
|
client = clientFor(offer);
|
||||||
|
|
||||||
|
await client.connect();
|
||||||
|
const initialAgents = await client.fetchAgents();
|
||||||
|
const agent = await client.createAgent({
|
||||||
|
provider: "codex",
|
||||||
|
cwd: daemon.staticDir,
|
||||||
|
title: "Live relay acceptance",
|
||||||
|
modeId: "full-access",
|
||||||
|
});
|
||||||
|
await client.sendMessage(agent.id, "Respond with exactly: RELAY_ACCEPTANCE_OK");
|
||||||
|
const finished = await client.waitForFinish(agent.id, 120_000);
|
||||||
|
const timeline = await client.fetchAgentTimeline(agent.id, {
|
||||||
|
direction: "tail",
|
||||||
|
limit: 20,
|
||||||
|
projection: "canonical",
|
||||||
|
});
|
||||||
|
const assistantText = timeline.entries
|
||||||
|
.filter((entry) => entry.item.type === "assistant_message")
|
||||||
|
.map((entry) => entry.item.text)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
expect(initialAgents.entries).toEqual([]);
|
||||||
|
expect(agent).toMatchObject({
|
||||||
|
provider: "codex",
|
||||||
|
cwd: daemon.staticDir,
|
||||||
|
status: "idle",
|
||||||
|
});
|
||||||
|
expect(finished).toMatchObject({ status: "idle" });
|
||||||
|
expect(assistantText).toMatch(/^RELAY_ACCEPTANCE_OK\s*$/);
|
||||||
|
},
|
||||||
|
180_000,
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -23,6 +23,8 @@ interface TestPaseoDaemonOptions {
|
|||||||
isDev?: boolean;
|
isDev?: boolean;
|
||||||
relayEnabled?: boolean;
|
relayEnabled?: boolean;
|
||||||
relayEndpoint?: string;
|
relayEndpoint?: string;
|
||||||
|
relayUseTls?: boolean;
|
||||||
|
relayPublicUseTls?: boolean;
|
||||||
agentClients?: Partial<Record<AgentProvider, AgentClient>>;
|
agentClients?: Partial<Record<AgentProvider, AgentClient>>;
|
||||||
providerOverrides?: PaseoDaemonConfig["providerOverrides"];
|
providerOverrides?: PaseoDaemonConfig["providerOverrides"];
|
||||||
paseoHomeRoot?: string;
|
paseoHomeRoot?: string;
|
||||||
@@ -166,6 +168,8 @@ async function prepareTestDaemonConfig(
|
|||||||
agentStoragePath: path.join(paseoHome, "agents"),
|
agentStoragePath: path.join(paseoHome, "agents"),
|
||||||
relayEnabled: options.relayEnabled ?? false,
|
relayEnabled: options.relayEnabled ?? false,
|
||||||
relayEndpoint: options.relayEndpoint ?? "relay.paseo.sh:443",
|
relayEndpoint: options.relayEndpoint ?? "relay.paseo.sh:443",
|
||||||
|
relayUseTls: options.relayUseTls,
|
||||||
|
relayPublicUseTls: options.relayPublicUseTls,
|
||||||
appBaseUrl: "https://app.paseo.sh",
|
appBaseUrl: "https://app.paseo.sh",
|
||||||
auth: options.auth,
|
auth: options.auth,
|
||||||
pushNotificationSender: options.pushNotificationSender,
|
pushNotificationSender: options.pushNotificationSender,
|
||||||
|
|||||||
Reference in New Issue
Block a user