openclaw gateway: persist device keys on create/update and clarify pairing flow

This commit is contained in:
Dotta
2026-03-07 17:34:38 -06:00
parent df0f101fbd
commit 3479ea6e80
5 changed files with 74 additions and 21 deletions

View File

@@ -267,8 +267,11 @@ POST /api/companies/$CLA_COMPANY_ID/invites
- default path: `adapterConfig.disableDeviceAuth` is false/absent and stable `adapterConfig.devicePrivateKeyPem` is set so approvals persist across runs
- fallback path: `disableDeviceAuth=true` only for environments that cannot support pairing
5. Trigger one connectivity run. If it returns `pairing required`, approve the pending device request in OpenClaw and retry once.
- Note: Paperclip invite approval and OpenClaw device-pairing approval are separate gates.
- Local docker automation path:
- `openclaw devices approve --latest --json --url ws://127.0.0.1:18789 --token <gateway-token>`
- Optional inspection:
- `openclaw devices list --json --url ws://127.0.0.1:18789 --token <gateway-token>`
- After approval, retries should succeed using the persisted `devicePrivateKeyPem`.
6. Claim API key with `claimSecret`.
7. Save claimed token to OpenClaw expected file path (`~/.openclaw/workspace/paperclip-claimed-api-key.json`) and ensure `PAPERCLIP_API_KEY` + `PAPERCLIP_API_URL` are available for OpenClaw skill execution context.

View File

@@ -22,6 +22,7 @@ type GatewayDeviceIdentity = {
deviceId: string;
publicKeyRawBase64Url: string;
privateKeyPem: string;
source: "configured" | "ephemeral";
};
type GatewayRequestFrame = {
@@ -486,6 +487,7 @@ function resolveDeviceIdentity(config: Record<string, unknown>): GatewayDeviceId
deviceId: crypto.createHash("sha256").update(raw).digest("hex"),
publicKeyRawBase64Url: base64UrlEncode(raw),
privateKeyPem: configuredPrivateKey,
source: "configured",
};
}
@@ -497,6 +499,7 @@ function resolveDeviceIdentity(config: Record<string, unknown>): GatewayDeviceId
deviceId: crypto.createHash("sha256").update(raw).digest("hex"),
publicKeyRawBase64Url: base64UrlEncode(raw),
privateKeyPem,
source: "ephemeral",
};
}
@@ -912,6 +915,14 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
try {
const deviceIdentity = disableDeviceAuth ? null : resolveDeviceIdentity(parseObject(ctx.config));
if (deviceIdentity) {
await ctx.onLog(
"stdout",
`[openclaw-gateway] device auth enabled keySource=${deviceIdentity.source} deviceId=${deviceIdentity.deviceId}\n`,
);
} else {
await ctx.onLog("stdout", "[openclaw-gateway] device auth disabled\n");
}
await ctx.onLog("stdout", `[openclaw-gateway] connecting to ${parsedUrl.toString()}\n`);
@@ -1076,7 +1087,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
const timedOut = lower.includes("timeout");
const pairingRequired = lower.includes("pairing required");
const detailedMessage = pairingRequired
? `${message}. Configure adapterConfig.disableDeviceAuth=true for smoke/dev, or set adapterConfig.devicePrivateKeyPem so pairing persists across runs.`
? `${message}. Approve the pending device in OpenClaw (for example: openclaw devices approve --latest --url <gateway-ws-url> --token <gateway-token>) and retry. Ensure this agent has a persisted adapterConfig.devicePrivateKeyPem so approvals are reused.`
: message;
await ctx.onLog("stderr", `[openclaw-gateway] request failed: ${detailedMessage}\n`);