fix(openclaw): make invite snippet/onboarding gateway-first

This commit is contained in:
Dotta
2026-03-07 15:39:12 -06:00
parent 1c1b86f495
commit 271a632f1c
3 changed files with 39 additions and 61 deletions

View File

@@ -37,17 +37,17 @@ describe("buildInviteOnboardingTextDocument", () => {
allowedHostnames: [],
});
expect(text).toContain("Paperclip OpenClaw Onboarding");
expect(text).toContain("Paperclip OpenClaw Gateway Onboarding");
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("/api/invites/token-123/test-resolution");
expect(text).toContain("Suggested Paperclip base URLs to try");
expect(text).toContain("http://localhost:3100");
expect(text).toContain("host.docker.internal");
expect(text).toContain("paperclipApiUrl");
expect(text).toContain("You MUST include agentDefaultsPayload.headers.x-openclaw-auth");
expect(text).toContain("will fail with 401 Unauthorized");
expect(text).toContain("adapterType \"openclaw_gateway\"");
expect(text).toContain("headers.x-openclaw-token");
expect(text).toContain("Do NOT use /v1/responses or /hooks/*");
expect(text).toContain("set the first reachable candidate as agentDefaultsPayload.paperclipApiUrl");
expect(text).toContain("~/.openclaw/workspace/paperclip-claimed-api-key.json");
expect(text).toContain("PAPERCLIP_API_KEY");

View File

@@ -950,10 +950,6 @@ function buildInviteOnboardingManifest(
const onboardingTextUrl = baseUrl
? `${baseUrl}${onboardingTextPath}`
: onboardingTextPath;
const testResolutionPath = `/api/invites/${token}/test-resolution`;
const testResolutionUrl = baseUrl
? `${baseUrl}${testResolutionPath}`
: testResolutionPath;
const discoveryDiagnostics = buildOnboardingDiscoveryDiagnostics({
apiBaseUrl: baseUrl,
deploymentMode: opts.deploymentMode,
@@ -971,16 +967,16 @@ function buildInviteOnboardingManifest(
invite: toInviteSummaryResponse(req, token, invite),
onboarding: {
instructions:
"Join as an OpenClaw agent, save your one-time claim secret, wait for board approval, then claim your API key. Save the claim response token to ~/.openclaw/workspace/paperclip-claimed-api-key.json and load PAPERCLIP_API_KEY from that file before starting heartbeat loops. You MUST include agentDefaultsPayload.headers.x-openclaw-auth in your join request so Paperclip can authenticate callback requests.",
"Join as an OpenClaw Gateway agent, save your one-time claim secret, wait for board approval, then claim your API key. Save the claim response token to ~/.openclaw/workspace/paperclip-claimed-api-key.json and load PAPERCLIP_API_KEY from that file before starting heartbeat loops. You MUST submit adapterType='openclaw_gateway', set agentDefaultsPayload.url to your ws:// or wss:// OpenClaw gateway endpoint, and include agentDefaultsPayload.headers.x-openclaw-token (or legacy x-openclaw-auth).",
inviteMessage: extractInviteMessage(invite),
recommendedAdapterType: "openclaw",
recommendedAdapterType: "openclaw_gateway",
requiredFields: {
requestType: "agent",
agentName: "Display name for this agent",
adapterType: "Use 'openclaw' for OpenClaw agents",
adapterType: "Use 'openclaw_gateway' for OpenClaw Gateway agents",
capabilities: "Optional capability summary",
agentDefaultsPayload:
"Adapter config for OpenClaw endpoint. MUST include headers.x-openclaw-auth; include streamTransport ('sse' or 'webhook') plus url/method/paperclipApiUrl (and optional webhookAuthHeader/timeoutSec/payloadTemplate)."
"Adapter config for OpenClaw gateway. MUST include url (ws:// or wss://) and headers.x-openclaw-token (or legacy x-openclaw-auth). Optional fields: paperclipApiUrl, waitTimeoutMs, sessionKeyStrategy, sessionKey, role, scopes, disableDeviceAuth."
},
registrationEndpoint: {
method: "POST",
@@ -1001,21 +997,12 @@ function buildInviteOnboardingManifest(
bindHost: opts.bindHost,
allowedHostnames: opts.allowedHostnames,
connectionCandidates,
testResolutionEndpoint: {
method: "GET",
path: testResolutionPath,
url: testResolutionUrl,
query: {
url: "https://your-openclaw-agent.example/v1/responses",
timeoutMs: 5000
}
},
diagnostics: discoveryDiagnostics,
guidance:
opts.deploymentMode === "authenticated" &&
opts.deploymentExposure === "private"
? "If OpenClaw runs on another machine, ensure the Paperclip hostname is reachable and allowed via `pnpm paperclipai allowed-hostname <host>`."
: "Ensure OpenClaw can reach this Paperclip API base URL for callbacks and claims."
: "Ensure OpenClaw can reach this Paperclip API base URL for invite, claim, and skill bootstrap calls."
},
textInstructions: {
path: onboardingTextPath,
@@ -1076,7 +1063,7 @@ export function buildInviteOnboardingTextDocument(
};
appendBlock(`
# Paperclip OpenClaw Onboarding
# Paperclip OpenClaw Gateway Onboarding
This document is meant to be readable by both humans and agents.
@@ -1116,15 +1103,17 @@ export function buildInviteOnboardingTextDocument(
const body = {
requestType: "agent",
agentName: "OpenClaw",
adapterType: "openclaw",
adapterType: "openclaw_gateway",
capabilities: "OpenClaw agent adapter",
agentDefaultsPayload: {
url: "http://127.0.0.1:18789/v1/responses",
url: "ws://127.0.0.1:18789",
paperclipApiUrl: "http://host.docker.internal:3100",
streamTransport: "sse",
method: "POST",
headers: { "x-openclaw-auth": token },
timeoutSec: 0
headers: { "x-openclaw-token": token },
waitTimeoutMs: 120000,
sessionKeyStrategy: "fixed",
sessionKey: "paperclip",
role: "operator",
scopes: ["operator.admin"]
}
};
process.stdout.write(JSON.stringify(body));
@@ -1135,23 +1124,26 @@ export function buildInviteOnboardingTextDocument(
onboarding.registrationEndpoint.url
}
IMPORTANT: You MUST include agentDefaultsPayload.headers.x-openclaw-auth with your gateway token.
Without this token, Paperclip callback requests to your OpenClaw endpoint will fail with 401 Unauthorized.
Set "streamTransport" to "sse" for streaming /v1/responses endpoints, or "webhook" for wake-style callbacks.
IMPORTANT: You MUST include agentDefaultsPayload.headers.x-openclaw-token with your gateway token.
Legacy x-openclaw-auth is also accepted, but x-openclaw-token is preferred.
Use adapterType "openclaw_gateway" and a ws:// or wss:// gateway URL.
Do NOT use /v1/responses or /hooks/* in this gateway join flow.
Body (JSON):
{
"requestType": "agent",
"agentName": "My OpenClaw Agent",
"adapterType": "openclaw",
"adapterType": "openclaw_gateway",
"capabilities": "Optional summary",
"agentDefaultsPayload": {
"url": "https://your-openclaw-agent.example/v1/responses",
"url": "wss://your-openclaw-gateway.example",
"paperclipApiUrl": "https://paperclip-hostname-your-agent-can-reach:3100",
"streamTransport": "sse",
"method": "POST",
"headers": { "x-openclaw-auth": "replace-me" },
"timeoutSec": 0
"headers": { "x-openclaw-token": "replace-me" },
"waitTimeoutMs": 120000,
"sessionKeyStrategy": "fixed",
"sessionKey": "paperclip",
"role": "operator",
"scopes": ["operator.admin"]
}
}
@@ -1160,11 +1152,6 @@ export function buildInviteOnboardingTextDocument(
- one-time claimSecret
- claimApiKeyPath
Verify the response diagnostics include:
'openclaw_auth_header_configured'
and do not include:
'openclaw_auth_header_missing'
## Step 2: Wait for board approval
The board approves the join request in Paperclip before key claim is allowed.
@@ -1218,17 +1205,6 @@ export function buildInviteOnboardingTextDocument(
}
`);
if (onboarding.connectivity?.testResolutionEndpoint?.url) {
appendBlock(`
## Optional: test callback resolution from Paperclip
${onboarding.connectivity.testResolutionEndpoint.method ?? "GET"} ${
onboarding.connectivity.testResolutionEndpoint.url
}?url=https%3A%2F%2Fyour-openclaw-agent.example%2Fv1%2Fresponses
This endpoint checks whether Paperclip can reach your OpenClaw endpoint and reports reachable, timeout, or unreachable.
`);
}
const connectionCandidates = Array.isArray(
onboarding.connectivity?.connectionCandidates
)
@@ -1271,9 +1247,6 @@ export function buildInviteOnboardingTextDocument(
${onboarding.skill.path}
${manifest.invite.onboardingPath}
`);
if (onboarding.connectivity?.testResolutionEndpoint?.path) {
lines.push(`${onboarding.connectivity.testResolutionEndpoint.path}`);
}
return `${lines.join("\n")}\n`;
}