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:real": "npm run test:integration:real",
|
||||
"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"
|
||||
},
|
||||
"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;
|
||||
relayEnabled?: boolean;
|
||||
relayEndpoint?: string;
|
||||
relayUseTls?: boolean;
|
||||
relayPublicUseTls?: boolean;
|
||||
agentClients?: Partial<Record<AgentProvider, AgentClient>>;
|
||||
providerOverrides?: PaseoDaemonConfig["providerOverrides"];
|
||||
paseoHomeRoot?: string;
|
||||
@@ -166,6 +168,8 @@ async function prepareTestDaemonConfig(
|
||||
agentStoragePath: path.join(paseoHome, "agents"),
|
||||
relayEnabled: options.relayEnabled ?? false,
|
||||
relayEndpoint: options.relayEndpoint ?? "relay.paseo.sh:443",
|
||||
relayUseTls: options.relayUseTls,
|
||||
relayPublicUseTls: options.relayPublicUseTls,
|
||||
appBaseUrl: "https://app.paseo.sh",
|
||||
auth: options.auth,
|
||||
pushNotificationSender: options.pushNotificationSender,
|
||||
|
||||
Reference in New Issue
Block a user