Add invite onboarding network host suggestions

This commit is contained in:
Dotta
2026-03-05 12:28:27 -06:00
parent bee24e880f
commit 7a2ecff4f0
3 changed files with 73 additions and 2 deletions

View File

@@ -41,6 +41,9 @@ describe("buildInviteOnboardingTextDocument", () => {
expect(text).toContain("/api/invites/token-123/accept");
expect(text).toContain("/api/join-requests/{requestId}/claim-api-key");
expect(text).toContain("/api/invites/token-123/onboarding.txt");
expect(text).toContain("Suggested Paperclip base URLs to try");
expect(text).toContain("http://localhost:3100");
expect(text).toContain("host.docker.internal");
});
it("includes loopback diagnostics for authenticated/private onboarding", () => {
@@ -69,6 +72,7 @@ describe("buildInviteOnboardingTextDocument", () => {
expect(text).toContain("Connectivity diagnostics");
expect(text).toContain("loopback hostname");
expect(text).toContain("If none are reachable");
});
it("includes inviter message in the onboarding text when provided", () => {

View File

@@ -377,6 +377,46 @@ function buildOnboardingDiscoveryDiagnostics(input: {
return diagnostics;
}
function buildOnboardingConnectionCandidates(input: {
apiBaseUrl: string;
bindHost: string;
allowedHostnames: string[];
}): string[] {
let base: URL | null = null;
try {
if (input.apiBaseUrl) {
base = new URL(input.apiBaseUrl);
}
} catch {
base = null;
}
const protocol = base?.protocol ?? "http:";
const port = base?.port ? `:${base.port}` : "";
const candidates = new Set<string>();
if (base) {
candidates.add(base.origin);
}
const bindHost = normalizeHostname(input.bindHost);
if (bindHost && !isLoopbackHost(bindHost)) {
candidates.add(`${protocol}//${bindHost}${port}`);
}
for (const rawHost of input.allowedHostnames) {
const host = normalizeHostname(rawHost);
if (!host) continue;
candidates.add(`${protocol}//${host}${port}`);
}
if (base && isLoopbackHost(base.hostname)) {
candidates.add(`${protocol}//host.docker.internal${port}`);
}
return Array.from(candidates);
}
function buildInviteOnboardingManifest(
req: Request,
token: string,
@@ -402,6 +442,11 @@ function buildInviteOnboardingManifest(
bindHost: opts.bindHost,
allowedHostnames: opts.allowedHostnames,
});
const connectionCandidates = buildOnboardingConnectionCandidates({
apiBaseUrl: baseUrl,
bindHost: opts.bindHost,
allowedHostnames: opts.allowedHostnames,
});
return {
invite: toInviteSummaryResponse(req, token, invite),
@@ -435,6 +480,7 @@ function buildInviteOnboardingManifest(
deploymentExposure: opts.deploymentExposure,
bindHost: opts.bindHost,
allowedHostnames: opts.allowedHostnames,
connectionCandidates,
diagnostics: discoveryDiagnostics,
guidance:
opts.deploymentMode === "authenticated" && opts.deploymentExposure === "private"
@@ -474,7 +520,7 @@ export function buildInviteOnboardingTextDocument(
claimEndpointTemplate: { method: string; path: string };
textInstructions: { path: string; url: string };
skill: { path: string; url: string; installPath: string };
connectivity: { diagnostics?: JoinDiagnostic[]; guidance?: string };
connectivity: { diagnostics?: JoinDiagnostic[]; guidance?: string; connectionCandidates?: string[] };
};
const diagnostics = Array.isArray(onboarding.connectivity?.diagnostics)
? onboarding.connectivity.diagnostics
@@ -546,6 +592,27 @@ export function buildInviteOnboardingTextDocument(
onboarding.connectivity?.guidance ?? "Ensure Paperclip is reachable from your OpenClaw runtime.",
);
const connectionCandidates = Array.isArray(onboarding.connectivity?.connectionCandidates)
? onboarding.connectivity.connectionCandidates.filter((entry): entry is string => Boolean(entry))
: [];
if (connectionCandidates.length > 0) {
lines.push("", "## Suggested Paperclip base URLs to try");
for (const candidate of connectionCandidates) {
lines.push(`- ${candidate}`);
}
lines.push(
"",
"Test each candidate with:",
"- GET <candidate>/api/health",
"",
"If none are reachable: ask your human operator for a reachable hostname/address and help them update network configuration.",
"For authenticated/private mode, they may need:",
"- pnpm paperclipai allowed-hostname <host>",
"- then restart Paperclip and retry onboarding.",
);
}
if (diagnostics.length > 0) {
lines.push("", "## Connectivity diagnostics");
for (const diag of diagnostics) {