Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

264 lines
7.9 KiB
JavaScript

#!/usr/bin/env node
// Cloud route e2e: provision a REAL Eliza Cloud agent (Hetzner-backed) and prove
// its runtime answers. Fails LOUDLY (non-zero exit + redacted diagnostics) when
// provisioning fails or the runtime never answers — the user's "if Hetzner
// fails to provision, we need to know". Programmatic surface only; the on-device
// cloud surface is driven by the Playwright Android suite (ELIZA_ANDROID_BACKEND
// =cloud) once a runtime URL + token are known.
//
// Usage:
// ELIZA_CLOUD_AUTH_TOKEN=... node scripts/cloud-provisioning-e2e.mjs
// [--cloud-api-base https://api.elizacloud.ai] [--agent-id <id>] [--fresh-agent]
// [--timeout-ms 600000] [--report <path>] [--print-runtime-url]
import fs from "node:fs";
const arg = (name, fb) => {
const i = process.argv.indexOf(name);
return i >= 0 ? process.argv[i + 1] : fb;
};
const has = (name) => process.argv.includes(name);
const token =
arg("--token") ??
process.env.ELIZA_CLOUD_AUTH_TOKEN ??
process.env.ELIZAOS_CLOUD_API_KEY ??
process.env.ELIZACLOUD_API_KEY;
const cloudApiBase = (
arg("--cloud-api-base") ??
process.env.ELIZA_CLOUD_API_BASE ??
"https://api.elizacloud.ai"
).replace(/\/+$/, "");
const agentIdArg = arg("--agent-id") ?? process.env.ELIZA_CLOUD_AGENT_ID;
const agentName =
arg("--agent-name") ??
process.env.ELIZA_CLOUD_AGENT_NAME ??
"Eliza Android Cloud E2E";
const timeoutMs = Number(arg("--timeout-ms") ?? 600_000);
const pollMs = Number(arg("--poll-ms") ?? 3_000);
const reportPath = arg("--report");
const log = (m) => console.log(`[cloud-e2e] ${m}`);
const redact = (v) =>
token ? String(v).replaceAll(token, "<redacted-token>") : String(v);
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
const tryJson = (t) => {
try {
return JSON.parse(t);
} catch {
return null;
}
};
const rec = (v) =>
typeof v === "object" && v !== null && !Array.isArray(v) ? v : null;
const data = (v) => rec(rec(v)?.data) ?? rec(v);
const str = (...xs) => {
for (const x of xs) if (typeof x === "string" && x.trim()) return x.trim();
return null;
};
function requireToken() {
if (!token?.trim()) {
throw new Error(
"Missing Cloud token. Set ELIZA_CLOUD_AUTH_TOKEN (or pass --token).",
);
}
return token.trim();
}
function runtimeUrlFrom(...vals) {
for (const v of vals) {
const o = rec(v);
if (!o) continue;
const url = str(
o.bridgeUrl,
o.bridge_url,
o.webUiUrl,
o.web_ui_url,
o.runtimeUrl,
o.runtime_url,
o.containerUrl,
o.container_url,
o.apiBase,
o.api_base,
);
if (url) return url.replace(/\/+$/, "");
}
return null;
}
function normStatus(v) {
const s = String(v ?? "")
.trim()
.toLowerCase();
if (["complete", "completed", "success", "succeeded"].includes(s))
return "completed";
if (["fail", "failed", "error"].includes(s)) return "failed";
return s || "unknown";
}
async function cloud(path, init = {}) {
const url = `${cloudApiBase}${path}`;
const res = await fetch(url, {
...init,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${requireToken()}`,
...(init.headers ?? {}),
},
});
const text = await res.text();
const body = tryJson(text) ?? text;
const ok =
(res.status >= 200 && res.status < 300) || rec(body)?.success === true;
if (!ok) {
const detail =
str(rec(body)?.error, rec(body)?.message, rec(body)?.reason) ??
text.slice(0, 300);
const err = new Error(
`Cloud request failed (${res.status}) ${url}: ${detail}`,
);
err.status = res.status;
err.body = body;
throw err;
}
return { status: res.status, body, text };
}
async function resolveAgent() {
if (agentIdArg?.trim()) {
const res = await cloud(
`/api/v1/eliza/agents/${encodeURIComponent(agentIdArg.trim())}`,
);
const d = data(res.body);
if (!d) throw new Error(`Cloud agent not found: ${agentIdArg}`);
return { ...d, id: str(d.id, d.agentId) ?? agentIdArg.trim() };
}
if (!has("--fresh-agent")) {
const res = await cloud("/api/v1/eliza/agents");
const list = Array.isArray(rec(res.body)?.data)
? rec(res.body).data
: Array.isArray(res.body)
? res.body
: [];
const existing = list.map((x) => data(x) ?? x).filter(Boolean);
const named = existing.find(
(a) => str(a.agentName, a.name, a.agent_name) === agentName,
);
const chosen = named ?? existing[0];
if (chosen) return chosen;
}
const res = await cloud("/api/v1/eliza/agents", {
method: "POST",
body: JSON.stringify({ agentName }),
});
const d = data(res.body);
const id = str(d?.id, d?.agentId, d?.agent_id);
if (!id) throw new Error(`Cloud create returned no agent id: ${res.text}`);
return { ...d, id };
}
async function provision(agentId) {
log(`provisioning agent=${agentId}`);
const res = await cloud(
`/api/v1/eliza/agents/${encodeURIComponent(agentId)}/provision`,
{ method: "POST" },
);
const d = data(res.body) ?? {};
return {
jobId: str(d.jobId, d.job_id, d.id),
runtimeUrl: runtimeUrlFrom(d, res.body),
status: normStatus(str(d.status, d.state)),
};
}
async function waitForRuntimeUrl(agentId, p) {
if (p.runtimeUrl) return p.runtimeUrl;
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
if (p.jobId) {
const job = data(
(await cloud(`/api/v1/jobs/${encodeURIComponent(p.jobId)}`)).body,
);
const url = runtimeUrlFrom(rec(job?.result), job);
if (url) return url;
if (normStatus(str(job?.status, job?.state, job?.phase)) === "failed") {
throw new Error(
`Cloud provision job failed: ${str(job?.error, job?.message) ?? "unknown error"}`,
);
}
}
const agent = await cloud(
`/api/v1/eliza/agents/${encodeURIComponent(agentId)}`,
).catch(() => null);
const url = runtimeUrlFrom(data(agent?.body));
if (url) return url;
await delay(pollMs);
}
throw new Error(
`Timed out after ${timeoutMs}ms waiting for Cloud runtime URL.`,
);
}
async function probe(runtimeUrl) {
const failures = [];
for (const endpoint of ["/api/status", "/api/health", "/api/auth/me"]) {
const url = `${runtimeUrl}${endpoint}`;
try {
const res = await fetch(url, {
headers: {
Accept: "application/json",
Authorization: `Bearer ${requireToken()}`,
"X-ElizaOS-Client-Id": "cloud-provisioning-e2e",
},
});
const text = await res.text();
if (res.ok)
return {
ok: true,
url,
status: res.status,
body: tryJson(text) ?? text.slice(0, 300),
};
failures.push({ url, status: res.status, body: text.slice(0, 300) });
} catch (error) {
failures.push({ url, error: String(error) });
}
}
throw new Error(
`Provisioned runtime did not answer: ${JSON.stringify(failures)}`,
);
}
async function main() {
requireToken();
const agent = await resolveAgent();
const agentId = str(agent.id, agent.agentId, agent.agent_id);
if (!agentId) throw new Error("Selected Cloud agent has no id.");
const p = await provision(agentId);
const runtimeUrl = await waitForRuntimeUrl(agentId, p);
log(`runtime URL: ${runtimeUrl}`);
const probeResult = await probe(runtimeUrl);
const report = {
ok: true,
agentId,
runtimeUrl,
provision: p,
probe: probeResult,
};
if (reportPath)
fs.writeFileSync(
reportPath,
`${redact(JSON.stringify(report, null, 2))}\n`,
);
if (has("--print-runtime-url")) console.log(`RUNTIME_URL=${runtimeUrl}`);
log("CLOUD PROVISIONING OK ✅");
console.log(redact(JSON.stringify(report, null, 2)));
}
main().catch((error) => {
console.error(`[cloud-e2e] FAILED: ${redact(error?.message ?? error)}`);
if (error?.body) console.error(redact(JSON.stringify(error.body, null, 2)));
process.exit(1);
});