clawdentity 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/bin.js +228 -15
- package/dist/index.js +228 -15
- package/dist/postinstall.js +0 -0
- package/package.json +16 -17
- package/skill-bundle/openclaw-skill/skill/SKILL.md +84 -26
- package/skill-bundle/openclaw-skill/skill/examples/openclaw-relay-sample.json +5 -0
- package/skill-bundle/openclaw-skill/skill/examples/peers-sample.json +10 -0
- package/skill-bundle/openclaw-skill/skill/references/clawdentity-environment.md +40 -0
- package/skill-bundle/openclaw-skill/skill/references/clawdentity-protocol.md +105 -44
- package/skill-bundle/openclaw-skill/skill/references/clawdentity-registry.md +11 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ravi Kiran Vemula
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/bin.js
CHANGED
|
@@ -15719,20 +15719,56 @@ var registryConfigSchema = external_exports.object({
|
|
|
15719
15719
|
REGISTRY_SIGNING_KEY: external_exports.string().min(1).optional(),
|
|
15720
15720
|
REGISTRY_SIGNING_KEYS: registrySigningKeysEnvSchema.optional()
|
|
15721
15721
|
});
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15722
|
+
var REQUIRED_REGISTRY_RUNTIME_KEYS = [
|
|
15723
|
+
"PROXY_URL",
|
|
15724
|
+
"REGISTRY_ISSUER_URL",
|
|
15725
|
+
"EVENT_BUS_BACKEND",
|
|
15726
|
+
"BOOTSTRAP_SECRET",
|
|
15727
|
+
"REGISTRY_SIGNING_KEY",
|
|
15728
|
+
"REGISTRY_SIGNING_KEYS"
|
|
15729
|
+
];
|
|
15730
|
+
function throwRegistryConfigValidationError(details) {
|
|
15727
15731
|
throw new AppError({
|
|
15728
15732
|
code: "CONFIG_VALIDATION_FAILED",
|
|
15729
15733
|
message: "Registry configuration is invalid",
|
|
15730
15734
|
status: 500,
|
|
15731
15735
|
expose: true,
|
|
15732
|
-
details
|
|
15733
|
-
|
|
15734
|
-
|
|
15736
|
+
details
|
|
15737
|
+
});
|
|
15738
|
+
}
|
|
15739
|
+
function assertRequiredRegistryRuntimeKeys(input) {
|
|
15740
|
+
if (input.ENVIRONMENT === "test") {
|
|
15741
|
+
return;
|
|
15742
|
+
}
|
|
15743
|
+
const fieldErrors = {};
|
|
15744
|
+
for (const key of REQUIRED_REGISTRY_RUNTIME_KEYS) {
|
|
15745
|
+
const value = input[key];
|
|
15746
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
15747
|
+
continue;
|
|
15748
|
+
}
|
|
15749
|
+
if (value !== void 0 && value !== null && !(typeof value === "string" && value.trim().length === 0)) {
|
|
15750
|
+
continue;
|
|
15735
15751
|
}
|
|
15752
|
+
fieldErrors[key] = [`${key} is required`];
|
|
15753
|
+
}
|
|
15754
|
+
if (Object.keys(fieldErrors).length > 0) {
|
|
15755
|
+
throwRegistryConfigValidationError({
|
|
15756
|
+
fieldErrors,
|
|
15757
|
+
formErrors: []
|
|
15758
|
+
});
|
|
15759
|
+
}
|
|
15760
|
+
}
|
|
15761
|
+
function parseRegistryConfig(env, options = {}) {
|
|
15762
|
+
const parsed = registryConfigSchema.safeParse(env);
|
|
15763
|
+
if (parsed.success) {
|
|
15764
|
+
if (options.requireRuntimeKeys === true) {
|
|
15765
|
+
assertRequiredRegistryRuntimeKeys(parsed.data);
|
|
15766
|
+
}
|
|
15767
|
+
return parsed.data;
|
|
15768
|
+
}
|
|
15769
|
+
throwRegistryConfigValidationError({
|
|
15770
|
+
fieldErrors: parsed.error.flatten().fieldErrors,
|
|
15771
|
+
formErrors: parsed.error.flatten().formErrors
|
|
15736
15772
|
});
|
|
15737
15773
|
}
|
|
15738
15774
|
|
|
@@ -21760,6 +21796,7 @@ var OPENCLAW_SETUP_RESTART_COMMAND_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} and re
|
|
|
21760
21796
|
var OPENCLAW_SETUP_WITH_BASE_URL_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} --openclaw-base-url <url>`;
|
|
21761
21797
|
var OPENCLAW_PAIRING_COMMAND_HINT = "Run QR pairing first: clawdentity pair start <agentName> --qr and clawdentity pair confirm <agentName> --qr-file <path>";
|
|
21762
21798
|
var OPENCLAW_DEVICE_APPROVAL_RECOVERY_HINT = "Run: clawdentity openclaw setup <agentName> (auto-recovers pending OpenClaw gateway device approvals)";
|
|
21799
|
+
var OPENCLAW_GATEWAY_AUTH_RECOVERY_HINT = "Run: clawdentity openclaw setup <agentName> (ensures gateway auth mode/token are configured)";
|
|
21763
21800
|
var OPENCLAW_GATEWAY_APPROVAL_COMMAND = "openclaw";
|
|
21764
21801
|
var OPENCLAW_GATEWAY_APPROVAL_TIMEOUT_MS = 1e4;
|
|
21765
21802
|
var textEncoder2 = new TextEncoder();
|
|
@@ -22691,6 +22728,28 @@ function isCanonicalAgentSessionKey(value) {
|
|
|
22691
22728
|
function generateOpenclawHookToken() {
|
|
22692
22729
|
return randomBytes3(OPENCLAW_HOOK_TOKEN_BYTES).toString("hex");
|
|
22693
22730
|
}
|
|
22731
|
+
function generateOpenclawGatewayToken() {
|
|
22732
|
+
return randomBytes3(OPENCLAW_HOOK_TOKEN_BYTES).toString("hex");
|
|
22733
|
+
}
|
|
22734
|
+
function parseGatewayAuthMode(value) {
|
|
22735
|
+
if (typeof value !== "string") {
|
|
22736
|
+
return void 0;
|
|
22737
|
+
}
|
|
22738
|
+
const normalized = value.trim().toLowerCase();
|
|
22739
|
+
if (normalized === "token" || normalized === "password" || normalized === "trusted-proxy") {
|
|
22740
|
+
return normalized;
|
|
22741
|
+
}
|
|
22742
|
+
return void 0;
|
|
22743
|
+
}
|
|
22744
|
+
function resolveEnvOpenclawGatewayToken() {
|
|
22745
|
+
if (typeof process.env.OPENCLAW_GATEWAY_TOKEN === "string" && process.env.OPENCLAW_GATEWAY_TOKEN.trim().length > 0) {
|
|
22746
|
+
return process.env.OPENCLAW_GATEWAY_TOKEN.trim();
|
|
22747
|
+
}
|
|
22748
|
+
return void 0;
|
|
22749
|
+
}
|
|
22750
|
+
function resolveGatewayAuthToken(existingToken) {
|
|
22751
|
+
return resolveEnvOpenclawGatewayToken() ?? existingToken ?? generateOpenclawGatewayToken();
|
|
22752
|
+
}
|
|
22694
22753
|
function upsertRelayHookMapping(mappingsValue) {
|
|
22695
22754
|
const mappings = Array.isArray(mappingsValue) ? mappingsValue.filter(isRecord9).map((mapping) => ({ ...mapping })) : [];
|
|
22696
22755
|
const existingIndex = mappings.findIndex((mapping) => {
|
|
@@ -22757,9 +22816,22 @@ async function patchOpenclawConfig(openclawConfigPath, hookToken) {
|
|
|
22757
22816
|
["hook:", defaultSessionKey]
|
|
22758
22817
|
);
|
|
22759
22818
|
hooks.mappings = upsertRelayHookMapping(hooks.mappings);
|
|
22819
|
+
const gateway = isRecord9(config2.gateway) ? { ...config2.gateway } : {};
|
|
22820
|
+
const gatewayAuth = isRecord9(gateway.auth) ? { ...gateway.auth } : {};
|
|
22821
|
+
const configuredGatewayAuthMode = parseGatewayAuthMode(gatewayAuth.mode);
|
|
22822
|
+
if (configuredGatewayAuthMode === void 0) {
|
|
22823
|
+
gatewayAuth.mode = "token";
|
|
22824
|
+
}
|
|
22825
|
+
const effectiveGatewayAuthMode = parseGatewayAuthMode(gatewayAuth.mode) ?? "token";
|
|
22826
|
+
if (effectiveGatewayAuthMode === "token") {
|
|
22827
|
+
const existingGatewayAuthToken = typeof gatewayAuth.token === "string" && gatewayAuth.token.trim().length > 0 ? gatewayAuth.token.trim() : void 0;
|
|
22828
|
+
gatewayAuth.token = resolveGatewayAuthToken(existingGatewayAuthToken);
|
|
22829
|
+
}
|
|
22830
|
+
gateway.auth = gatewayAuth;
|
|
22760
22831
|
const nextConfig = {
|
|
22761
22832
|
...config2,
|
|
22762
|
-
hooks
|
|
22833
|
+
hooks,
|
|
22834
|
+
gateway
|
|
22763
22835
|
};
|
|
22764
22836
|
await writeFile6(
|
|
22765
22837
|
openclawConfigPath,
|
|
@@ -23264,6 +23336,79 @@ async function runOpenclawDoctor(options = {}) {
|
|
|
23264
23336
|
})
|
|
23265
23337
|
);
|
|
23266
23338
|
}
|
|
23339
|
+
const gateway = isRecord9(openclawConfig.gateway) ? openclawConfig.gateway : {};
|
|
23340
|
+
const gatewayAuth = isRecord9(gateway.auth) ? gateway.auth : {};
|
|
23341
|
+
const gatewayAuthMode = parseGatewayAuthMode(gatewayAuth.mode);
|
|
23342
|
+
const gatewayAuthToken = typeof gatewayAuth.token === "string" && gatewayAuth.token.trim().length > 0 ? gatewayAuth.token.trim() : void 0;
|
|
23343
|
+
const gatewayAuthPassword = typeof gatewayAuth.password === "string" && gatewayAuth.password.trim().length > 0 ? gatewayAuth.password.trim() : void 0;
|
|
23344
|
+
if (gatewayAuthMode === "token") {
|
|
23345
|
+
if (gatewayAuthToken === void 0) {
|
|
23346
|
+
checks.push(
|
|
23347
|
+
toDoctorCheck({
|
|
23348
|
+
id: "state.gatewayAuth",
|
|
23349
|
+
label: "OpenClaw gateway auth",
|
|
23350
|
+
status: "fail",
|
|
23351
|
+
message: `gateway.auth.token is missing in ${openclawConfigPath}`,
|
|
23352
|
+
remediationHint: OPENCLAW_GATEWAY_AUTH_RECOVERY_HINT,
|
|
23353
|
+
details: { openclawConfigPath, gatewayAuthMode }
|
|
23354
|
+
})
|
|
23355
|
+
);
|
|
23356
|
+
} else {
|
|
23357
|
+
checks.push(
|
|
23358
|
+
toDoctorCheck({
|
|
23359
|
+
id: "state.gatewayAuth",
|
|
23360
|
+
label: "OpenClaw gateway auth",
|
|
23361
|
+
status: "pass",
|
|
23362
|
+
message: "gateway auth is configured with token mode",
|
|
23363
|
+
details: { openclawConfigPath, gatewayAuthMode }
|
|
23364
|
+
})
|
|
23365
|
+
);
|
|
23366
|
+
}
|
|
23367
|
+
} else if (gatewayAuthMode === "password") {
|
|
23368
|
+
if (gatewayAuthPassword === void 0) {
|
|
23369
|
+
checks.push(
|
|
23370
|
+
toDoctorCheck({
|
|
23371
|
+
id: "state.gatewayAuth",
|
|
23372
|
+
label: "OpenClaw gateway auth",
|
|
23373
|
+
status: "fail",
|
|
23374
|
+
message: `gateway.auth.password is missing in ${openclawConfigPath}`,
|
|
23375
|
+
remediationHint: OPENCLAW_GATEWAY_AUTH_RECOVERY_HINT,
|
|
23376
|
+
details: { openclawConfigPath, gatewayAuthMode }
|
|
23377
|
+
})
|
|
23378
|
+
);
|
|
23379
|
+
} else {
|
|
23380
|
+
checks.push(
|
|
23381
|
+
toDoctorCheck({
|
|
23382
|
+
id: "state.gatewayAuth",
|
|
23383
|
+
label: "OpenClaw gateway auth",
|
|
23384
|
+
status: "pass",
|
|
23385
|
+
message: "gateway auth is configured with password mode",
|
|
23386
|
+
details: { openclawConfigPath, gatewayAuthMode }
|
|
23387
|
+
})
|
|
23388
|
+
);
|
|
23389
|
+
}
|
|
23390
|
+
} else if (gatewayAuthMode === "trusted-proxy") {
|
|
23391
|
+
checks.push(
|
|
23392
|
+
toDoctorCheck({
|
|
23393
|
+
id: "state.gatewayAuth",
|
|
23394
|
+
label: "OpenClaw gateway auth",
|
|
23395
|
+
status: "pass",
|
|
23396
|
+
message: "gateway auth is configured with trusted-proxy mode",
|
|
23397
|
+
details: { openclawConfigPath, gatewayAuthMode }
|
|
23398
|
+
})
|
|
23399
|
+
);
|
|
23400
|
+
} else {
|
|
23401
|
+
checks.push(
|
|
23402
|
+
toDoctorCheck({
|
|
23403
|
+
id: "state.gatewayAuth",
|
|
23404
|
+
label: "OpenClaw gateway auth",
|
|
23405
|
+
status: "fail",
|
|
23406
|
+
message: `gateway.auth.mode is missing or unsupported in ${openclawConfigPath}`,
|
|
23407
|
+
remediationHint: OPENCLAW_GATEWAY_AUTH_RECOVERY_HINT,
|
|
23408
|
+
details: { openclawConfigPath }
|
|
23409
|
+
})
|
|
23410
|
+
);
|
|
23411
|
+
}
|
|
23267
23412
|
} catch {
|
|
23268
23413
|
checks.push(
|
|
23269
23414
|
toDoctorCheck({
|
|
@@ -23295,6 +23440,16 @@ async function runOpenclawDoctor(options = {}) {
|
|
|
23295
23440
|
details: { openclawConfigPath }
|
|
23296
23441
|
})
|
|
23297
23442
|
);
|
|
23443
|
+
checks.push(
|
|
23444
|
+
toDoctorCheck({
|
|
23445
|
+
id: "state.gatewayAuth",
|
|
23446
|
+
label: "OpenClaw gateway auth",
|
|
23447
|
+
status: "fail",
|
|
23448
|
+
message: `unable to read ${openclawConfigPath}`,
|
|
23449
|
+
remediationHint: "Ensure the OpenClaw config file exists (OPENCLAW_CONFIG_PATH/CLAWDBOT_CONFIG_PATH, or state dir) and rerun openclaw setup",
|
|
23450
|
+
details: { openclawConfigPath }
|
|
23451
|
+
})
|
|
23452
|
+
);
|
|
23298
23453
|
}
|
|
23299
23454
|
const relayRuntimeConfigPath = resolveRelayRuntimeConfigPath(homeDir);
|
|
23300
23455
|
try {
|
|
@@ -24234,16 +24389,20 @@ function parsePeerProfile(payload) {
|
|
|
24234
24389
|
};
|
|
24235
24390
|
}
|
|
24236
24391
|
function parsePairingTicket(value) {
|
|
24237
|
-
|
|
24392
|
+
let ticket = parseNonEmptyString9(value);
|
|
24393
|
+
while (ticket.startsWith("`")) {
|
|
24394
|
+
ticket = ticket.slice(1);
|
|
24395
|
+
}
|
|
24396
|
+
while (ticket.endsWith("`")) {
|
|
24397
|
+
ticket = ticket.slice(0, -1);
|
|
24398
|
+
}
|
|
24399
|
+
ticket = ticket.trim().replace(/\s+/gu, "");
|
|
24238
24400
|
if (!ticket.startsWith(PAIRING_TICKET_PREFIX)) {
|
|
24239
24401
|
throw createCliError7(
|
|
24240
24402
|
"CLI_PAIR_CONFIRM_TICKET_INVALID",
|
|
24241
24403
|
"Pairing ticket is invalid"
|
|
24242
24404
|
);
|
|
24243
24405
|
}
|
|
24244
|
-
return ticket;
|
|
24245
|
-
}
|
|
24246
|
-
function parsePairingTicketIssuerOrigin(ticket) {
|
|
24247
24406
|
const encodedPayload = ticket.slice(PAIRING_TICKET_PREFIX.length);
|
|
24248
24407
|
if (encodedPayload.length === 0) {
|
|
24249
24408
|
throw createCliError7(
|
|
@@ -24251,15 +24410,26 @@ function parsePairingTicketIssuerOrigin(ticket) {
|
|
|
24251
24410
|
"Pairing ticket is invalid"
|
|
24252
24411
|
);
|
|
24253
24412
|
}
|
|
24254
|
-
let payloadRaw;
|
|
24255
24413
|
try {
|
|
24256
|
-
payloadRaw = new TextDecoder().decode(
|
|
24414
|
+
const payloadRaw = new TextDecoder().decode(
|
|
24415
|
+
decodeBase64url(encodedPayload)
|
|
24416
|
+
);
|
|
24417
|
+
const payload = JSON.parse(payloadRaw);
|
|
24418
|
+
if (!isRecord10(payload)) {
|
|
24419
|
+
throw new Error("invalid payload");
|
|
24420
|
+
}
|
|
24257
24421
|
} catch {
|
|
24258
24422
|
throw createCliError7(
|
|
24259
24423
|
"CLI_PAIR_CONFIRM_TICKET_INVALID",
|
|
24260
24424
|
"Pairing ticket is invalid"
|
|
24261
24425
|
);
|
|
24262
24426
|
}
|
|
24427
|
+
return ticket;
|
|
24428
|
+
}
|
|
24429
|
+
function parsePairingTicketIssuerOrigin(ticket) {
|
|
24430
|
+
const normalizedTicket = parsePairingTicket(ticket);
|
|
24431
|
+
const encodedPayload = normalizedTicket.slice(PAIRING_TICKET_PREFIX.length);
|
|
24432
|
+
const payloadRaw = new TextDecoder().decode(decodeBase64url(encodedPayload));
|
|
24263
24433
|
let payload;
|
|
24264
24434
|
try {
|
|
24265
24435
|
payload = JSON.parse(payloadRaw);
|
|
@@ -24292,6 +24462,26 @@ function parsePairingTicketIssuerOrigin(ticket) {
|
|
|
24292
24462
|
}
|
|
24293
24463
|
return issuerUrl.origin;
|
|
24294
24464
|
}
|
|
24465
|
+
function assertTicketIssuerMatchesProxy(input) {
|
|
24466
|
+
const issuerOrigin = parsePairingTicketIssuerOrigin(input.ticket);
|
|
24467
|
+
let proxyOrigin;
|
|
24468
|
+
try {
|
|
24469
|
+
proxyOrigin = new URL(input.proxyUrl).origin;
|
|
24470
|
+
} catch {
|
|
24471
|
+
throw createCliError7(
|
|
24472
|
+
"CLI_PAIR_PROXY_URL_INVALID",
|
|
24473
|
+
"Configured proxyUrl is invalid. Run `clawdentity config set proxyUrl <url>` and retry."
|
|
24474
|
+
);
|
|
24475
|
+
}
|
|
24476
|
+
if (issuerOrigin === proxyOrigin) {
|
|
24477
|
+
return;
|
|
24478
|
+
}
|
|
24479
|
+
const command = input.context === "confirm" ? "pair confirm" : "pair status";
|
|
24480
|
+
throw createCliError7(
|
|
24481
|
+
"CLI_PAIR_TICKET_ISSUER_MISMATCH",
|
|
24482
|
+
`Pairing ticket was issued by ${issuerOrigin}, but current proxy URL is ${proxyOrigin}. Run \`clawdentity config set proxyUrl ${issuerOrigin}\` and retry \`${command}\`.`
|
|
24483
|
+
);
|
|
24484
|
+
}
|
|
24295
24485
|
function parseAitAgentDid(ait) {
|
|
24296
24486
|
const parts = ait.split(".");
|
|
24297
24487
|
if (parts.length < 2) {
|
|
@@ -24691,6 +24881,12 @@ function mapConfirmPairError(status, payload) {
|
|
|
24691
24881
|
if (code === "PROXY_PAIR_TICKET_EXPIRED" || status === 410) {
|
|
24692
24882
|
return "Pairing ticket has expired";
|
|
24693
24883
|
}
|
|
24884
|
+
if (code === "PROXY_PAIR_TICKET_INVALID_ISSUER") {
|
|
24885
|
+
return message2 ? `Pair confirm failed: ticket issuer does not match this proxy (${message2}). Use the same proxy URL where the ticket was issued.` : "Pair confirm failed: ticket issuer does not match this proxy. Use the same proxy URL where the ticket was issued.";
|
|
24886
|
+
}
|
|
24887
|
+
if (code === "PROXY_PAIR_TICKET_INVALID_FORMAT" || code === "PROXY_PAIR_TICKET_UNSUPPORTED_VERSION") {
|
|
24888
|
+
return message2 ? `Pair confirm request is invalid (400): ${message2}. Re-copy the full ticket/QR without truncation.` : "Pair confirm request is invalid (400): pairing ticket is malformed. Re-copy the full ticket/QR without truncation.";
|
|
24889
|
+
}
|
|
24694
24890
|
if (status === 400) {
|
|
24695
24891
|
return message2 ? `Pair confirm request is invalid (400): ${message2}` : "Pair confirm request is invalid (400).";
|
|
24696
24892
|
}
|
|
@@ -24714,6 +24910,12 @@ function mapStatusPairError(status, payload) {
|
|
|
24714
24910
|
if (code === "PROXY_PAIR_STATUS_FORBIDDEN" || status === 403) {
|
|
24715
24911
|
return message2 ? `Pair status request is forbidden (403): ${message2}` : "Pair status request is forbidden (403).";
|
|
24716
24912
|
}
|
|
24913
|
+
if (code === "PROXY_PAIR_TICKET_INVALID_ISSUER") {
|
|
24914
|
+
return message2 ? `Pair status failed: ticket issuer does not match this proxy (${message2}). Use the same proxy URL where the ticket was issued.` : "Pair status failed: ticket issuer does not match this proxy. Use the same proxy URL where the ticket was issued.";
|
|
24915
|
+
}
|
|
24916
|
+
if (code === "PROXY_PAIR_TICKET_INVALID_FORMAT" || code === "PROXY_PAIR_TICKET_UNSUPPORTED_VERSION") {
|
|
24917
|
+
return message2 ? `Pair status request is invalid (400): ${message2}. Re-copy the full ticket/QR without truncation.` : "Pair status request is invalid (400): pairing ticket is malformed. Re-copy the full ticket/QR without truncation.";
|
|
24918
|
+
}
|
|
24717
24919
|
if (status === 400) {
|
|
24718
24920
|
return message2 ? `Pair status request is invalid (400): ${message2}` : "Pair status request is invalid (400).";
|
|
24719
24921
|
}
|
|
@@ -25192,6 +25394,12 @@ async function confirmPairing(agentName, options, dependencies = {}) {
|
|
|
25192
25394
|
}
|
|
25193
25395
|
ticket = parsePairingTicket(qrDecodeImpl(new Uint8Array(imageBytes)));
|
|
25194
25396
|
}
|
|
25397
|
+
ticket = parsePairingTicket(ticket);
|
|
25398
|
+
assertTicketIssuerMatchesProxy({
|
|
25399
|
+
ticket,
|
|
25400
|
+
proxyUrl,
|
|
25401
|
+
context: "confirm"
|
|
25402
|
+
});
|
|
25195
25403
|
const { ait, secretKey } = await readAgentProofMaterial(
|
|
25196
25404
|
normalizedAgentName,
|
|
25197
25405
|
dependencies
|
|
@@ -25269,6 +25477,11 @@ async function getPairingStatusOnce(agentName, options, dependencies = {}) {
|
|
|
25269
25477
|
fetchImpl
|
|
25270
25478
|
});
|
|
25271
25479
|
const ticket = parsePairingTicket(options.ticket);
|
|
25480
|
+
assertTicketIssuerMatchesProxy({
|
|
25481
|
+
ticket,
|
|
25482
|
+
proxyUrl,
|
|
25483
|
+
context: "status"
|
|
25484
|
+
});
|
|
25272
25485
|
const { ait, secretKey } = await readAgentProofMaterial(
|
|
25273
25486
|
agentName,
|
|
25274
25487
|
dependencies
|
package/dist/index.js
CHANGED
|
@@ -15723,20 +15723,56 @@ var registryConfigSchema = external_exports.object({
|
|
|
15723
15723
|
REGISTRY_SIGNING_KEY: external_exports.string().min(1).optional(),
|
|
15724
15724
|
REGISTRY_SIGNING_KEYS: registrySigningKeysEnvSchema.optional()
|
|
15725
15725
|
});
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
|
|
15730
|
-
|
|
15726
|
+
var REQUIRED_REGISTRY_RUNTIME_KEYS = [
|
|
15727
|
+
"PROXY_URL",
|
|
15728
|
+
"REGISTRY_ISSUER_URL",
|
|
15729
|
+
"EVENT_BUS_BACKEND",
|
|
15730
|
+
"BOOTSTRAP_SECRET",
|
|
15731
|
+
"REGISTRY_SIGNING_KEY",
|
|
15732
|
+
"REGISTRY_SIGNING_KEYS"
|
|
15733
|
+
];
|
|
15734
|
+
function throwRegistryConfigValidationError(details) {
|
|
15731
15735
|
throw new AppError({
|
|
15732
15736
|
code: "CONFIG_VALIDATION_FAILED",
|
|
15733
15737
|
message: "Registry configuration is invalid",
|
|
15734
15738
|
status: 500,
|
|
15735
15739
|
expose: true,
|
|
15736
|
-
details
|
|
15737
|
-
|
|
15738
|
-
|
|
15740
|
+
details
|
|
15741
|
+
});
|
|
15742
|
+
}
|
|
15743
|
+
function assertRequiredRegistryRuntimeKeys(input) {
|
|
15744
|
+
if (input.ENVIRONMENT === "test") {
|
|
15745
|
+
return;
|
|
15746
|
+
}
|
|
15747
|
+
const fieldErrors = {};
|
|
15748
|
+
for (const key of REQUIRED_REGISTRY_RUNTIME_KEYS) {
|
|
15749
|
+
const value = input[key];
|
|
15750
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
15751
|
+
continue;
|
|
15752
|
+
}
|
|
15753
|
+
if (value !== void 0 && value !== null && !(typeof value === "string" && value.trim().length === 0)) {
|
|
15754
|
+
continue;
|
|
15739
15755
|
}
|
|
15756
|
+
fieldErrors[key] = [`${key} is required`];
|
|
15757
|
+
}
|
|
15758
|
+
if (Object.keys(fieldErrors).length > 0) {
|
|
15759
|
+
throwRegistryConfigValidationError({
|
|
15760
|
+
fieldErrors,
|
|
15761
|
+
formErrors: []
|
|
15762
|
+
});
|
|
15763
|
+
}
|
|
15764
|
+
}
|
|
15765
|
+
function parseRegistryConfig(env, options = {}) {
|
|
15766
|
+
const parsed = registryConfigSchema.safeParse(env);
|
|
15767
|
+
if (parsed.success) {
|
|
15768
|
+
if (options.requireRuntimeKeys === true) {
|
|
15769
|
+
assertRequiredRegistryRuntimeKeys(parsed.data);
|
|
15770
|
+
}
|
|
15771
|
+
return parsed.data;
|
|
15772
|
+
}
|
|
15773
|
+
throwRegistryConfigValidationError({
|
|
15774
|
+
fieldErrors: parsed.error.flatten().fieldErrors,
|
|
15775
|
+
formErrors: parsed.error.flatten().formErrors
|
|
15740
15776
|
});
|
|
15741
15777
|
}
|
|
15742
15778
|
|
|
@@ -21760,6 +21796,7 @@ var OPENCLAW_SETUP_RESTART_COMMAND_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} and re
|
|
|
21760
21796
|
var OPENCLAW_SETUP_WITH_BASE_URL_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} --openclaw-base-url <url>`;
|
|
21761
21797
|
var OPENCLAW_PAIRING_COMMAND_HINT = "Run QR pairing first: clawdentity pair start <agentName> --qr and clawdentity pair confirm <agentName> --qr-file <path>";
|
|
21762
21798
|
var OPENCLAW_DEVICE_APPROVAL_RECOVERY_HINT = "Run: clawdentity openclaw setup <agentName> (auto-recovers pending OpenClaw gateway device approvals)";
|
|
21799
|
+
var OPENCLAW_GATEWAY_AUTH_RECOVERY_HINT = "Run: clawdentity openclaw setup <agentName> (ensures gateway auth mode/token are configured)";
|
|
21763
21800
|
var OPENCLAW_GATEWAY_APPROVAL_COMMAND = "openclaw";
|
|
21764
21801
|
var OPENCLAW_GATEWAY_APPROVAL_TIMEOUT_MS = 1e4;
|
|
21765
21802
|
var textEncoder2 = new TextEncoder();
|
|
@@ -22691,6 +22728,28 @@ function isCanonicalAgentSessionKey(value) {
|
|
|
22691
22728
|
function generateOpenclawHookToken() {
|
|
22692
22729
|
return randomBytes3(OPENCLAW_HOOK_TOKEN_BYTES).toString("hex");
|
|
22693
22730
|
}
|
|
22731
|
+
function generateOpenclawGatewayToken() {
|
|
22732
|
+
return randomBytes3(OPENCLAW_HOOK_TOKEN_BYTES).toString("hex");
|
|
22733
|
+
}
|
|
22734
|
+
function parseGatewayAuthMode(value) {
|
|
22735
|
+
if (typeof value !== "string") {
|
|
22736
|
+
return void 0;
|
|
22737
|
+
}
|
|
22738
|
+
const normalized = value.trim().toLowerCase();
|
|
22739
|
+
if (normalized === "token" || normalized === "password" || normalized === "trusted-proxy") {
|
|
22740
|
+
return normalized;
|
|
22741
|
+
}
|
|
22742
|
+
return void 0;
|
|
22743
|
+
}
|
|
22744
|
+
function resolveEnvOpenclawGatewayToken() {
|
|
22745
|
+
if (typeof process.env.OPENCLAW_GATEWAY_TOKEN === "string" && process.env.OPENCLAW_GATEWAY_TOKEN.trim().length > 0) {
|
|
22746
|
+
return process.env.OPENCLAW_GATEWAY_TOKEN.trim();
|
|
22747
|
+
}
|
|
22748
|
+
return void 0;
|
|
22749
|
+
}
|
|
22750
|
+
function resolveGatewayAuthToken(existingToken) {
|
|
22751
|
+
return resolveEnvOpenclawGatewayToken() ?? existingToken ?? generateOpenclawGatewayToken();
|
|
22752
|
+
}
|
|
22694
22753
|
function upsertRelayHookMapping(mappingsValue) {
|
|
22695
22754
|
const mappings = Array.isArray(mappingsValue) ? mappingsValue.filter(isRecord9).map((mapping) => ({ ...mapping })) : [];
|
|
22696
22755
|
const existingIndex = mappings.findIndex((mapping) => {
|
|
@@ -22757,9 +22816,22 @@ async function patchOpenclawConfig(openclawConfigPath, hookToken) {
|
|
|
22757
22816
|
["hook:", defaultSessionKey]
|
|
22758
22817
|
);
|
|
22759
22818
|
hooks.mappings = upsertRelayHookMapping(hooks.mappings);
|
|
22819
|
+
const gateway = isRecord9(config2.gateway) ? { ...config2.gateway } : {};
|
|
22820
|
+
const gatewayAuth = isRecord9(gateway.auth) ? { ...gateway.auth } : {};
|
|
22821
|
+
const configuredGatewayAuthMode = parseGatewayAuthMode(gatewayAuth.mode);
|
|
22822
|
+
if (configuredGatewayAuthMode === void 0) {
|
|
22823
|
+
gatewayAuth.mode = "token";
|
|
22824
|
+
}
|
|
22825
|
+
const effectiveGatewayAuthMode = parseGatewayAuthMode(gatewayAuth.mode) ?? "token";
|
|
22826
|
+
if (effectiveGatewayAuthMode === "token") {
|
|
22827
|
+
const existingGatewayAuthToken = typeof gatewayAuth.token === "string" && gatewayAuth.token.trim().length > 0 ? gatewayAuth.token.trim() : void 0;
|
|
22828
|
+
gatewayAuth.token = resolveGatewayAuthToken(existingGatewayAuthToken);
|
|
22829
|
+
}
|
|
22830
|
+
gateway.auth = gatewayAuth;
|
|
22760
22831
|
const nextConfig = {
|
|
22761
22832
|
...config2,
|
|
22762
|
-
hooks
|
|
22833
|
+
hooks,
|
|
22834
|
+
gateway
|
|
22763
22835
|
};
|
|
22764
22836
|
await writeFile6(
|
|
22765
22837
|
openclawConfigPath,
|
|
@@ -23264,6 +23336,79 @@ async function runOpenclawDoctor(options = {}) {
|
|
|
23264
23336
|
})
|
|
23265
23337
|
);
|
|
23266
23338
|
}
|
|
23339
|
+
const gateway = isRecord9(openclawConfig.gateway) ? openclawConfig.gateway : {};
|
|
23340
|
+
const gatewayAuth = isRecord9(gateway.auth) ? gateway.auth : {};
|
|
23341
|
+
const gatewayAuthMode = parseGatewayAuthMode(gatewayAuth.mode);
|
|
23342
|
+
const gatewayAuthToken = typeof gatewayAuth.token === "string" && gatewayAuth.token.trim().length > 0 ? gatewayAuth.token.trim() : void 0;
|
|
23343
|
+
const gatewayAuthPassword = typeof gatewayAuth.password === "string" && gatewayAuth.password.trim().length > 0 ? gatewayAuth.password.trim() : void 0;
|
|
23344
|
+
if (gatewayAuthMode === "token") {
|
|
23345
|
+
if (gatewayAuthToken === void 0) {
|
|
23346
|
+
checks.push(
|
|
23347
|
+
toDoctorCheck({
|
|
23348
|
+
id: "state.gatewayAuth",
|
|
23349
|
+
label: "OpenClaw gateway auth",
|
|
23350
|
+
status: "fail",
|
|
23351
|
+
message: `gateway.auth.token is missing in ${openclawConfigPath}`,
|
|
23352
|
+
remediationHint: OPENCLAW_GATEWAY_AUTH_RECOVERY_HINT,
|
|
23353
|
+
details: { openclawConfigPath, gatewayAuthMode }
|
|
23354
|
+
})
|
|
23355
|
+
);
|
|
23356
|
+
} else {
|
|
23357
|
+
checks.push(
|
|
23358
|
+
toDoctorCheck({
|
|
23359
|
+
id: "state.gatewayAuth",
|
|
23360
|
+
label: "OpenClaw gateway auth",
|
|
23361
|
+
status: "pass",
|
|
23362
|
+
message: "gateway auth is configured with token mode",
|
|
23363
|
+
details: { openclawConfigPath, gatewayAuthMode }
|
|
23364
|
+
})
|
|
23365
|
+
);
|
|
23366
|
+
}
|
|
23367
|
+
} else if (gatewayAuthMode === "password") {
|
|
23368
|
+
if (gatewayAuthPassword === void 0) {
|
|
23369
|
+
checks.push(
|
|
23370
|
+
toDoctorCheck({
|
|
23371
|
+
id: "state.gatewayAuth",
|
|
23372
|
+
label: "OpenClaw gateway auth",
|
|
23373
|
+
status: "fail",
|
|
23374
|
+
message: `gateway.auth.password is missing in ${openclawConfigPath}`,
|
|
23375
|
+
remediationHint: OPENCLAW_GATEWAY_AUTH_RECOVERY_HINT,
|
|
23376
|
+
details: { openclawConfigPath, gatewayAuthMode }
|
|
23377
|
+
})
|
|
23378
|
+
);
|
|
23379
|
+
} else {
|
|
23380
|
+
checks.push(
|
|
23381
|
+
toDoctorCheck({
|
|
23382
|
+
id: "state.gatewayAuth",
|
|
23383
|
+
label: "OpenClaw gateway auth",
|
|
23384
|
+
status: "pass",
|
|
23385
|
+
message: "gateway auth is configured with password mode",
|
|
23386
|
+
details: { openclawConfigPath, gatewayAuthMode }
|
|
23387
|
+
})
|
|
23388
|
+
);
|
|
23389
|
+
}
|
|
23390
|
+
} else if (gatewayAuthMode === "trusted-proxy") {
|
|
23391
|
+
checks.push(
|
|
23392
|
+
toDoctorCheck({
|
|
23393
|
+
id: "state.gatewayAuth",
|
|
23394
|
+
label: "OpenClaw gateway auth",
|
|
23395
|
+
status: "pass",
|
|
23396
|
+
message: "gateway auth is configured with trusted-proxy mode",
|
|
23397
|
+
details: { openclawConfigPath, gatewayAuthMode }
|
|
23398
|
+
})
|
|
23399
|
+
);
|
|
23400
|
+
} else {
|
|
23401
|
+
checks.push(
|
|
23402
|
+
toDoctorCheck({
|
|
23403
|
+
id: "state.gatewayAuth",
|
|
23404
|
+
label: "OpenClaw gateway auth",
|
|
23405
|
+
status: "fail",
|
|
23406
|
+
message: `gateway.auth.mode is missing or unsupported in ${openclawConfigPath}`,
|
|
23407
|
+
remediationHint: OPENCLAW_GATEWAY_AUTH_RECOVERY_HINT,
|
|
23408
|
+
details: { openclawConfigPath }
|
|
23409
|
+
})
|
|
23410
|
+
);
|
|
23411
|
+
}
|
|
23267
23412
|
} catch {
|
|
23268
23413
|
checks.push(
|
|
23269
23414
|
toDoctorCheck({
|
|
@@ -23295,6 +23440,16 @@ async function runOpenclawDoctor(options = {}) {
|
|
|
23295
23440
|
details: { openclawConfigPath }
|
|
23296
23441
|
})
|
|
23297
23442
|
);
|
|
23443
|
+
checks.push(
|
|
23444
|
+
toDoctorCheck({
|
|
23445
|
+
id: "state.gatewayAuth",
|
|
23446
|
+
label: "OpenClaw gateway auth",
|
|
23447
|
+
status: "fail",
|
|
23448
|
+
message: `unable to read ${openclawConfigPath}`,
|
|
23449
|
+
remediationHint: "Ensure the OpenClaw config file exists (OPENCLAW_CONFIG_PATH/CLAWDBOT_CONFIG_PATH, or state dir) and rerun openclaw setup",
|
|
23450
|
+
details: { openclawConfigPath }
|
|
23451
|
+
})
|
|
23452
|
+
);
|
|
23298
23453
|
}
|
|
23299
23454
|
const relayRuntimeConfigPath = resolveRelayRuntimeConfigPath(homeDir);
|
|
23300
23455
|
try {
|
|
@@ -24234,16 +24389,20 @@ function parsePeerProfile(payload) {
|
|
|
24234
24389
|
};
|
|
24235
24390
|
}
|
|
24236
24391
|
function parsePairingTicket(value) {
|
|
24237
|
-
|
|
24392
|
+
let ticket = parseNonEmptyString9(value);
|
|
24393
|
+
while (ticket.startsWith("`")) {
|
|
24394
|
+
ticket = ticket.slice(1);
|
|
24395
|
+
}
|
|
24396
|
+
while (ticket.endsWith("`")) {
|
|
24397
|
+
ticket = ticket.slice(0, -1);
|
|
24398
|
+
}
|
|
24399
|
+
ticket = ticket.trim().replace(/\s+/gu, "");
|
|
24238
24400
|
if (!ticket.startsWith(PAIRING_TICKET_PREFIX)) {
|
|
24239
24401
|
throw createCliError7(
|
|
24240
24402
|
"CLI_PAIR_CONFIRM_TICKET_INVALID",
|
|
24241
24403
|
"Pairing ticket is invalid"
|
|
24242
24404
|
);
|
|
24243
24405
|
}
|
|
24244
|
-
return ticket;
|
|
24245
|
-
}
|
|
24246
|
-
function parsePairingTicketIssuerOrigin(ticket) {
|
|
24247
24406
|
const encodedPayload = ticket.slice(PAIRING_TICKET_PREFIX.length);
|
|
24248
24407
|
if (encodedPayload.length === 0) {
|
|
24249
24408
|
throw createCliError7(
|
|
@@ -24251,15 +24410,26 @@ function parsePairingTicketIssuerOrigin(ticket) {
|
|
|
24251
24410
|
"Pairing ticket is invalid"
|
|
24252
24411
|
);
|
|
24253
24412
|
}
|
|
24254
|
-
let payloadRaw;
|
|
24255
24413
|
try {
|
|
24256
|
-
payloadRaw = new TextDecoder().decode(
|
|
24414
|
+
const payloadRaw = new TextDecoder().decode(
|
|
24415
|
+
decodeBase64url(encodedPayload)
|
|
24416
|
+
);
|
|
24417
|
+
const payload = JSON.parse(payloadRaw);
|
|
24418
|
+
if (!isRecord10(payload)) {
|
|
24419
|
+
throw new Error("invalid payload");
|
|
24420
|
+
}
|
|
24257
24421
|
} catch {
|
|
24258
24422
|
throw createCliError7(
|
|
24259
24423
|
"CLI_PAIR_CONFIRM_TICKET_INVALID",
|
|
24260
24424
|
"Pairing ticket is invalid"
|
|
24261
24425
|
);
|
|
24262
24426
|
}
|
|
24427
|
+
return ticket;
|
|
24428
|
+
}
|
|
24429
|
+
function parsePairingTicketIssuerOrigin(ticket) {
|
|
24430
|
+
const normalizedTicket = parsePairingTicket(ticket);
|
|
24431
|
+
const encodedPayload = normalizedTicket.slice(PAIRING_TICKET_PREFIX.length);
|
|
24432
|
+
const payloadRaw = new TextDecoder().decode(decodeBase64url(encodedPayload));
|
|
24263
24433
|
let payload;
|
|
24264
24434
|
try {
|
|
24265
24435
|
payload = JSON.parse(payloadRaw);
|
|
@@ -24292,6 +24462,26 @@ function parsePairingTicketIssuerOrigin(ticket) {
|
|
|
24292
24462
|
}
|
|
24293
24463
|
return issuerUrl.origin;
|
|
24294
24464
|
}
|
|
24465
|
+
function assertTicketIssuerMatchesProxy(input) {
|
|
24466
|
+
const issuerOrigin = parsePairingTicketIssuerOrigin(input.ticket);
|
|
24467
|
+
let proxyOrigin;
|
|
24468
|
+
try {
|
|
24469
|
+
proxyOrigin = new URL(input.proxyUrl).origin;
|
|
24470
|
+
} catch {
|
|
24471
|
+
throw createCliError7(
|
|
24472
|
+
"CLI_PAIR_PROXY_URL_INVALID",
|
|
24473
|
+
"Configured proxyUrl is invalid. Run `clawdentity config set proxyUrl <url>` and retry."
|
|
24474
|
+
);
|
|
24475
|
+
}
|
|
24476
|
+
if (issuerOrigin === proxyOrigin) {
|
|
24477
|
+
return;
|
|
24478
|
+
}
|
|
24479
|
+
const command = input.context === "confirm" ? "pair confirm" : "pair status";
|
|
24480
|
+
throw createCliError7(
|
|
24481
|
+
"CLI_PAIR_TICKET_ISSUER_MISMATCH",
|
|
24482
|
+
`Pairing ticket was issued by ${issuerOrigin}, but current proxy URL is ${proxyOrigin}. Run \`clawdentity config set proxyUrl ${issuerOrigin}\` and retry \`${command}\`.`
|
|
24483
|
+
);
|
|
24484
|
+
}
|
|
24295
24485
|
function parseAitAgentDid(ait) {
|
|
24296
24486
|
const parts = ait.split(".");
|
|
24297
24487
|
if (parts.length < 2) {
|
|
@@ -24691,6 +24881,12 @@ function mapConfirmPairError(status, payload) {
|
|
|
24691
24881
|
if (code === "PROXY_PAIR_TICKET_EXPIRED" || status === 410) {
|
|
24692
24882
|
return "Pairing ticket has expired";
|
|
24693
24883
|
}
|
|
24884
|
+
if (code === "PROXY_PAIR_TICKET_INVALID_ISSUER") {
|
|
24885
|
+
return message2 ? `Pair confirm failed: ticket issuer does not match this proxy (${message2}). Use the same proxy URL where the ticket was issued.` : "Pair confirm failed: ticket issuer does not match this proxy. Use the same proxy URL where the ticket was issued.";
|
|
24886
|
+
}
|
|
24887
|
+
if (code === "PROXY_PAIR_TICKET_INVALID_FORMAT" || code === "PROXY_PAIR_TICKET_UNSUPPORTED_VERSION") {
|
|
24888
|
+
return message2 ? `Pair confirm request is invalid (400): ${message2}. Re-copy the full ticket/QR without truncation.` : "Pair confirm request is invalid (400): pairing ticket is malformed. Re-copy the full ticket/QR without truncation.";
|
|
24889
|
+
}
|
|
24694
24890
|
if (status === 400) {
|
|
24695
24891
|
return message2 ? `Pair confirm request is invalid (400): ${message2}` : "Pair confirm request is invalid (400).";
|
|
24696
24892
|
}
|
|
@@ -24714,6 +24910,12 @@ function mapStatusPairError(status, payload) {
|
|
|
24714
24910
|
if (code === "PROXY_PAIR_STATUS_FORBIDDEN" || status === 403) {
|
|
24715
24911
|
return message2 ? `Pair status request is forbidden (403): ${message2}` : "Pair status request is forbidden (403).";
|
|
24716
24912
|
}
|
|
24913
|
+
if (code === "PROXY_PAIR_TICKET_INVALID_ISSUER") {
|
|
24914
|
+
return message2 ? `Pair status failed: ticket issuer does not match this proxy (${message2}). Use the same proxy URL where the ticket was issued.` : "Pair status failed: ticket issuer does not match this proxy. Use the same proxy URL where the ticket was issued.";
|
|
24915
|
+
}
|
|
24916
|
+
if (code === "PROXY_PAIR_TICKET_INVALID_FORMAT" || code === "PROXY_PAIR_TICKET_UNSUPPORTED_VERSION") {
|
|
24917
|
+
return message2 ? `Pair status request is invalid (400): ${message2}. Re-copy the full ticket/QR without truncation.` : "Pair status request is invalid (400): pairing ticket is malformed. Re-copy the full ticket/QR without truncation.";
|
|
24918
|
+
}
|
|
24717
24919
|
if (status === 400) {
|
|
24718
24920
|
return message2 ? `Pair status request is invalid (400): ${message2}` : "Pair status request is invalid (400).";
|
|
24719
24921
|
}
|
|
@@ -25192,6 +25394,12 @@ async function confirmPairing(agentName, options, dependencies = {}) {
|
|
|
25192
25394
|
}
|
|
25193
25395
|
ticket = parsePairingTicket(qrDecodeImpl(new Uint8Array(imageBytes)));
|
|
25194
25396
|
}
|
|
25397
|
+
ticket = parsePairingTicket(ticket);
|
|
25398
|
+
assertTicketIssuerMatchesProxy({
|
|
25399
|
+
ticket,
|
|
25400
|
+
proxyUrl,
|
|
25401
|
+
context: "confirm"
|
|
25402
|
+
});
|
|
25195
25403
|
const { ait, secretKey } = await readAgentProofMaterial(
|
|
25196
25404
|
normalizedAgentName,
|
|
25197
25405
|
dependencies
|
|
@@ -25269,6 +25477,11 @@ async function getPairingStatusOnce(agentName, options, dependencies = {}) {
|
|
|
25269
25477
|
fetchImpl
|
|
25270
25478
|
});
|
|
25271
25479
|
const ticket = parsePairingTicket(options.ticket);
|
|
25480
|
+
assertTicketIssuerMatchesProxy({
|
|
25481
|
+
ticket,
|
|
25482
|
+
proxyUrl,
|
|
25483
|
+
context: "status"
|
|
25484
|
+
});
|
|
25272
25485
|
const { ait, secretKey } = await readAgentProofMaterial(
|
|
25273
25486
|
agentName,
|
|
25274
25487
|
dependencies
|
package/dist/postinstall.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawdentity",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -21,17 +21,6 @@
|
|
|
21
21
|
"postinstall.mjs",
|
|
22
22
|
"skill-bundle"
|
|
23
23
|
],
|
|
24
|
-
"scripts": {
|
|
25
|
-
"build": "pnpm -F @clawdentity/openclaw-skill build && pnpm run sync:skill-bundle && pnpm run verify:skill-bundle && tsup",
|
|
26
|
-
"format": "biome format .",
|
|
27
|
-
"lint": "biome lint .",
|
|
28
|
-
"prepack": "pnpm run build",
|
|
29
|
-
"postinstall": "node ./postinstall.mjs",
|
|
30
|
-
"sync:skill-bundle": "node ./scripts/sync-skill-bundle.mjs",
|
|
31
|
-
"verify:skill-bundle": "node ./scripts/verify-skill-bundle.mjs",
|
|
32
|
-
"test": "vitest run",
|
|
33
|
-
"typecheck": "tsc --noEmit"
|
|
34
|
-
},
|
|
35
24
|
"dependencies": {
|
|
36
25
|
"commander": "^13.1.0",
|
|
37
26
|
"jsqr": "^1.4.0",
|
|
@@ -40,11 +29,21 @@
|
|
|
40
29
|
"ws": "^8.19.0"
|
|
41
30
|
},
|
|
42
31
|
"devDependencies": {
|
|
43
|
-
"@clawdentity/connector": "workspace:*",
|
|
44
|
-
"@clawdentity/protocol": "workspace:*",
|
|
45
|
-
"@clawdentity/sdk": "workspace:*",
|
|
46
32
|
"@types/node": "^22.18.11",
|
|
47
33
|
"@types/pngjs": "^6.0.5",
|
|
48
|
-
"@types/qrcode": "^1.5.6"
|
|
34
|
+
"@types/qrcode": "^1.5.6",
|
|
35
|
+
"@clawdentity/protocol": "0.0.0",
|
|
36
|
+
"@clawdentity/connector": "0.0.0",
|
|
37
|
+
"@clawdentity/sdk": "0.0.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "pnpm -F @clawdentity/openclaw-skill build && pnpm run sync:skill-bundle && pnpm run verify:skill-bundle && tsup",
|
|
41
|
+
"format": "biome format .",
|
|
42
|
+
"lint": "biome lint .",
|
|
43
|
+
"postinstall": "node ./postinstall.mjs",
|
|
44
|
+
"sync:skill-bundle": "node ./scripts/sync-skill-bundle.mjs",
|
|
45
|
+
"verify:skill-bundle": "node ./scripts/verify-skill-bundle.mjs",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"typecheck": "tsc --noEmit"
|
|
49
48
|
}
|
|
50
|
-
}
|
|
49
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clawdentity_openclaw_relay
|
|
3
|
-
description: This skill should be used when the user asks to "set up Clawdentity relay", "pair two agents", "verify an agent token", "rotate API key", "refresh agent auth", "revoke an agent", "troubleshoot relay", "uninstall connector service", or needs OpenClaw relay onboarding, lifecycle management, or pairing workflows.
|
|
3
|
+
description: This skill should be used when the user asks to "set up Clawdentity relay", "pair two agents", "verify an agent token", "rotate API key", "refresh agent auth", "revoke an agent", "troubleshoot relay", "uninstall connector service", "check relay health", "run relay doctor", "test relay connection", "send relay test", "install relay skill", "bootstrap registry", "create onboarding invite", "decommission agent", or needs OpenClaw relay onboarding, lifecycle management, or pairing workflows.
|
|
4
4
|
version: 0.3.0
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -104,9 +104,17 @@ Note: Registry operators must run `admin bootstrap` before creating invites. See
|
|
|
104
104
|
|
|
105
105
|
### OpenClaw relay setup
|
|
106
106
|
- `clawdentity skill install`
|
|
107
|
+
- `clawdentity skill install --openclaw-dir <path>`
|
|
108
|
+
- `clawdentity skill install --skill-package-root <path>`
|
|
109
|
+
- `clawdentity skill install --json`
|
|
107
110
|
- `clawdentity openclaw setup <agent-name>`
|
|
108
111
|
- `clawdentity openclaw setup <agent-name> --transform-source <path>`
|
|
109
112
|
- `clawdentity openclaw setup <agent-name> --openclaw-dir <path> --openclaw-base-url <url>`
|
|
113
|
+
- `clawdentity openclaw setup <agent-name> --runtime-mode <auto|service|detached>`
|
|
114
|
+
- `clawdentity openclaw setup <agent-name> --wait-timeout-seconds <seconds>` (default 30)
|
|
115
|
+
- `clawdentity openclaw setup <agent-name> --no-runtime-start`
|
|
116
|
+
|
|
117
|
+
Use `--no-runtime-start` when the connector runs as a separate container or process.
|
|
110
118
|
|
|
111
119
|
### OpenClaw diagnostics
|
|
112
120
|
- `clawdentity openclaw doctor`
|
|
@@ -143,6 +151,20 @@ Note: Registry operators must run `admin bootstrap` before creating invites. See
|
|
|
143
151
|
- `clawdentity admin bootstrap --bootstrap-secret <secret>`
|
|
144
152
|
- `clawdentity admin bootstrap --bootstrap-secret <secret> --display-name <name> --api-key-name <name> --registry-url <url>`
|
|
145
153
|
|
|
154
|
+
### Command idempotency
|
|
155
|
+
|
|
156
|
+
| Command | Idempotent? | Note |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `config init` | Yes | Safe to re-run |
|
|
159
|
+
| `invite redeem` | **No** | One-time; invite consumed on success |
|
|
160
|
+
| `agent create` | No | Fails if agent directory exists |
|
|
161
|
+
| `openclaw setup` | Yes | Primary reconciliation re-entry point |
|
|
162
|
+
| `skill install` | Yes | Reports: installed/updated/unchanged |
|
|
163
|
+
| `pair start` | No | Creates new ticket each time; old ticket expires |
|
|
164
|
+
| `pair confirm` | No | Ticket consumed on success |
|
|
165
|
+
| `connector service install` | Yes | Idempotent |
|
|
166
|
+
| `connector service uninstall` | Yes | Idempotent |
|
|
167
|
+
|
|
146
168
|
## Journey (strict order)
|
|
147
169
|
|
|
148
170
|
1. Validate prerequisites.
|
|
@@ -178,11 +200,13 @@ Note: Registry operators must run `admin bootstrap` before creating invites. See
|
|
|
178
200
|
- `API key saved to local config`
|
|
179
201
|
- `Human name: <human-name>`
|
|
180
202
|
- Stop and fix if this step fails. Do not proceed to pairing.
|
|
203
|
+
- **Validate:** `clawdentity config get apiKey` returns a non-empty value.
|
|
181
204
|
|
|
182
205
|
5. Create local OpenClaw agent identity.
|
|
183
206
|
- Run `clawdentity agent create <agent-name> --framework openclaw`.
|
|
184
207
|
- Optionally add `--ttl-days <days>` to control token lifetime.
|
|
185
208
|
- Run `clawdentity agent inspect <agent-name>`.
|
|
209
|
+
- **Validate:** `~/.clawdentity/agents/<agent-name>/ait.jwt` and `secret.key` exist and are non-empty.
|
|
186
210
|
|
|
187
211
|
6. Configure relay setup.
|
|
188
212
|
- Run:
|
|
@@ -197,30 +221,15 @@ Note: Registry operators must run `admin bootstrap` before creating invites. See
|
|
|
197
221
|
- runtime mode/status
|
|
198
222
|
- websocket status `connected`
|
|
199
223
|
- setup checklist is healthy (fails fast when hook/device/runtime prerequisites drift)
|
|
224
|
+
- **Validate:** run `clawdentity openclaw doctor --json` and confirm all check entries have `status: "pass"`. If any check has `status: "fail"`, use `checkId` to look up remediation in `references/clawdentity-protocol.md` § Doctor Check Reference.
|
|
225
|
+
- If setup throws `CLI_OPENCLAW_SETUP_CHECKLIST_FAILED`, parse `details.firstFailedCheckId` for targeted remediation.
|
|
200
226
|
|
|
201
227
|
7. Validate readiness.
|
|
202
|
-
- `clawdentity openclaw setup` already runs an internal checklist and auto-recovers pending OpenClaw gateway device approvals when possible.
|
|
228
|
+
- `clawdentity openclaw setup` already runs an internal checklist, stabilizes OpenClaw gateway auth token mode, and auto-recovers pending OpenClaw gateway device approvals when possible.
|
|
203
229
|
- Run `clawdentity openclaw doctor` only for diagnostics or CI reporting.
|
|
204
230
|
- Use `--json` for machine-readable output.
|
|
205
231
|
- Use `--peer <alias>` to validate a specific peer exists after pairing.
|
|
206
|
-
- Doctor check IDs and remediation
|
|
207
|
-
|
|
208
|
-
| Check ID | Validates | Remediation on Failure |
|
|
209
|
-
|----------|-----------|----------------------|
|
|
210
|
-
| `config.registry` | `registryUrl`, `apiKey`, and `proxyUrl` in config (or proxy env override) | `clawdentity config init` or `invite redeem` |
|
|
211
|
-
| `state.selectedAgent` | Agent marker at `~/.clawdentity/openclaw-agent-name` | `clawdentity openclaw setup <agent-name>` |
|
|
212
|
-
| `state.credentials` | `ait.jwt` and `secret.key` exist and non-empty | `clawdentity agent create <agent-name>` or `agent auth refresh <agent-name>` |
|
|
213
|
-
| `state.peers` | Peers config valid; requested `--peer` alias exists | `clawdentity pair start` / `pair confirm` (optional until pairing) |
|
|
214
|
-
| `state.transform` | Relay transform artifacts in OpenClaw hooks dir | Reinstall skill package or `openclaw setup <agent-name>` |
|
|
215
|
-
| `state.hookMapping` | `send-to-peer` hook mapping in OpenClaw config | `clawdentity openclaw setup <agent-name>` |
|
|
216
|
-
| `state.hookToken` | Hooks enabled with token in OpenClaw config | `clawdentity openclaw setup <agent-name>` then restart OpenClaw |
|
|
217
|
-
| `state.hookSessionRouting` | `hooks.defaultSessionKey`, `hooks.allowRequestSessionKey=false`, and required prefixes (`hook:`, default session key) | `clawdentity openclaw setup <agent-name>` then restart OpenClaw |
|
|
218
|
-
| `state.gatewayDevicePairing` | Pending OpenClaw device approvals (prevents `pairing required` websocket errors) | Re-run `clawdentity openclaw setup <agent-name>` so setup auto-recovers approvals |
|
|
219
|
-
| `state.openclawBaseUrl` | OpenClaw base URL resolvable | `clawdentity openclaw setup <agent-name> --openclaw-base-url <url>` |
|
|
220
|
-
| `state.connectorRuntime` | Local connector runtime reachable and websocket-connected | `clawdentity openclaw setup <agent-name>` |
|
|
221
|
-
| `state.connectorInboundInbox` | Connector local inbound inbox backlog and replay queue state (`/v1/status`) | Re-run `clawdentity openclaw setup <agent-name>` and verify connector runtime health |
|
|
222
|
-
| `state.openclawHookHealth` | Connector replay status for local OpenClaw hook delivery (`/v1/status`) | Re-run `clawdentity openclaw setup <agent-name>` and restart OpenClaw if hook replay stays failed |
|
|
223
|
-
|
|
232
|
+
- Doctor check IDs and remediation are in `references/clawdentity-protocol.md` § Doctor Check Reference.
|
|
224
233
|
- At this point the agent is ready to start pairing or accept pairing.
|
|
225
234
|
|
|
226
235
|
8. Pairing phase (separate from onboarding).
|
|
@@ -246,7 +255,10 @@ Note: Registry operators must run `admin bootstrap` before creating invites. See
|
|
|
246
255
|
- If initiator started without `--wait`, initiator must run:
|
|
247
256
|
- `clawdentity pair status <agent-name> --ticket <clwpair1_...> --wait`
|
|
248
257
|
- This persists the peer on initiator after responder confirmation.
|
|
258
|
+
- Default wait timeout is 300 seconds with 3-second polling.
|
|
259
|
+
- If `CLI_PAIR_STATUS_WAIT_TIMEOUT` is thrown: the responder did not confirm in time. Recovery: re-run `clawdentity pair start <agent-name> --qr --wait` to generate a new ticket.
|
|
249
260
|
- Confirm pairing success, then run `clawdentity openclaw relay test`.
|
|
261
|
+
- **Validate:** `~/.clawdentity/peers.json` contains the new peer alias entry.
|
|
250
262
|
|
|
251
263
|
9. Post-pairing verification.
|
|
252
264
|
- Run `clawdentity verify <path-to-ait.jwt>` to confirm the local agent token is valid.
|
|
@@ -257,6 +269,35 @@ Note: Registry operators must run `admin bootstrap` before creating invites. See
|
|
|
257
269
|
- `state=queued` is not a pairing failure. The proxy retries delivery automatically while the message is within queue TTL/retry limits.
|
|
258
270
|
- Note: `relay test` runs preflight doctor checks before sending the probe.
|
|
259
271
|
|
|
272
|
+
## Peer Recognition & Messaging
|
|
273
|
+
|
|
274
|
+
After pairing, agents receive relayed messages from their paired peers.
|
|
275
|
+
Identity injection is enabled by default (`INJECT_IDENTITY_INTO_MESSAGE=true` on proxy).
|
|
276
|
+
|
|
277
|
+
### Recognizing incoming messages
|
|
278
|
+
- Relayed messages contain a `[Clawdentity Identity]` block prepended to the message body.
|
|
279
|
+
- Extract `agentDid` from this block to identify the sender.
|
|
280
|
+
- See `references/clawdentity-protocol.md` § Identity Injection for the exact block format and field definitions.
|
|
281
|
+
- The connector `deliver` frame also exposes `fromAgentDid` for programmatic access.
|
|
282
|
+
|
|
283
|
+
### Looking up peer identity
|
|
284
|
+
- Map sender `agentDid` to a known peer by matching against `did` in `~/.clawdentity/peers.json`.
|
|
285
|
+
- Each peer entry contains:
|
|
286
|
+
- `did` — the peer's agent DID (match key)
|
|
287
|
+
- `agentName` — the peer's agent name (use for addressing)
|
|
288
|
+
- `humanName` — the human behind the peer agent (use for context)
|
|
289
|
+
- `proxyUrl` — the peer's proxy endpoint
|
|
290
|
+
- Address the peer by `agentName` or `humanName` in responses for natural conversation.
|
|
291
|
+
|
|
292
|
+
### Sending messages to a peer
|
|
293
|
+
- Include `"peer": "<alias>"` in the webhook payload to route via relay.
|
|
294
|
+
- The alias is the key in `peers.json` (e.g., `"beta"`).
|
|
295
|
+
- The relay transform strips `peer` from the payload and routes to the connector.
|
|
296
|
+
|
|
297
|
+
### Peer validation
|
|
298
|
+
- `clawdentity openclaw doctor --peer <alias>` confirms a specific peer is reachable.
|
|
299
|
+
- `clawdentity openclaw relay test --peer <alias>` sends a test probe to the peer.
|
|
300
|
+
|
|
260
301
|
## Lifecycle Management
|
|
261
302
|
|
|
262
303
|
### Token expiry recovery
|
|
@@ -287,6 +328,11 @@ Note: Registry operators must run `admin bootstrap` before creating invites. See
|
|
|
287
328
|
- Uses cached registry keys (1h TTL) and CRL (15min TTL).
|
|
288
329
|
- Exit code 1 on verification failure or revocation.
|
|
289
330
|
|
|
331
|
+
### Periodic health checks
|
|
332
|
+
- Run `clawdentity openclaw doctor` periodically to detect stale credentials, expired AIT, or drifted runtime.
|
|
333
|
+
- Run `clawdentity agent inspect <agent-name>` to check token expiry.
|
|
334
|
+
- If AIT is within 24 hours of expiry, proactively run `clawdentity agent auth refresh <agent-name>`.
|
|
335
|
+
|
|
290
336
|
## Required Question Policy
|
|
291
337
|
|
|
292
338
|
Ask only when missing:
|
|
@@ -308,24 +354,33 @@ Do not suggest switching endpoints unless user explicitly asks for endpoint chan
|
|
|
308
354
|
- `CLI_CONNECTOR_MISSING_AGENT_MATERIAL`: agent credentials missing. Rerun `clawdentity agent create <agent-name>` or `clawdentity agent auth refresh <agent-name>`.
|
|
309
355
|
|
|
310
356
|
### Pairing errors
|
|
311
|
-
- `
|
|
312
|
-
- `
|
|
313
|
-
- `
|
|
314
|
-
- `pair confirm` 410 (`PROXY_PAIR_TICKET_EXPIRED`): ticket has expired. Request a new ticket.
|
|
357
|
+
- `PROXY_PAIR_TICKET_NOT_FOUND`: ticket invalid or expired. Request a new ticket from initiator.
|
|
358
|
+
- `PROXY_PAIR_TICKET_EXPIRED`: ticket has expired. Request a new ticket.
|
|
359
|
+
- `CLI_PAIR_STATUS_WAIT_TIMEOUT`: responder did not confirm in time. Re-run `pair start`.
|
|
315
360
|
- `CLI_PAIR_CONFIRM_INPUT_CONFLICT`: cannot provide both `--ticket` and `--qr-file`. Use one path only.
|
|
316
361
|
- `CLI_PAIR_PROXY_URL_MISMATCH`: local `proxyUrl` does not match registry metadata. Rerun `clawdentity invite redeem <clw_inv_...>`.
|
|
317
362
|
- Responder shows peer but initiator does not:
|
|
318
363
|
- Cause: initiator started pairing without `--wait`.
|
|
319
364
|
- Fix: run `clawdentity pair status <initiator-agent> --ticket <clwpair1_...> --wait` on initiator.
|
|
365
|
+
- For complete pairing error codes, read `references/clawdentity-protocol.md` § Pairing Error Codes.
|
|
320
366
|
|
|
321
367
|
### Setup errors
|
|
322
368
|
- `405 Method Not Allowed` on hook path: rerun `clawdentity openclaw setup <agent-name>` and restart OpenClaw.
|
|
323
369
|
- `CLI_OPENCLAW_MISSING_AGENT_CREDENTIALS` or `CLI_OPENCLAW_EMPTY_AGENT_CREDENTIALS`: agent credentials missing or empty. Rerun `agent create` or `agent auth refresh`.
|
|
370
|
+
- `CLI_OPENCLAW_SETUP_CHECKLIST_FAILED`: post-setup checklist reported a failing check. Parse `details.firstFailedCheckId` and apply remediation from the doctor check table in `references/clawdentity-protocol.md`. Common failing checks:
|
|
371
|
+
- `state.connectorRuntime` → rerun `openclaw setup <agent-name>`
|
|
372
|
+
- `state.gatewayDevicePairing` → rerun `openclaw setup <agent-name>` (auto-approval)
|
|
373
|
+
- `state.gatewayAuth` → rerun `openclaw setup <agent-name>` (auto-configures gateway auth mode/token)
|
|
374
|
+
- `state.hookToken` → rerun `openclaw setup <agent-name>` then restart OpenClaw
|
|
324
375
|
|
|
325
376
|
### Credential expiry
|
|
326
377
|
- Agent AIT expired: run `clawdentity agent auth refresh <agent-name>`, then rerun `clawdentity openclaw setup <agent-name>`.
|
|
327
378
|
- API key invalid (401 on registry calls): rotate with `api-key create` then `config set apiKey`.
|
|
328
379
|
|
|
380
|
+
### Network connectivity
|
|
381
|
+
- `CLI_PAIR_REQUEST_FAILED` or `CLI_ADMIN_BOOTSTRAP_REQUEST_FAILED`: proxy/registry unreachable. Check DNS, firewall rules, and URL with `clawdentity config show`.
|
|
382
|
+
- If running on an air-gapped machine, confirm proxy/registry URLs resolve to reachable endpoints.
|
|
383
|
+
|
|
329
384
|
### General recovery
|
|
330
385
|
- Report exact missing file/value.
|
|
331
386
|
- Fix only failing input/config.
|
|
@@ -337,7 +392,10 @@ Do not suggest switching endpoints unless user explicitly asks for endpoint chan
|
|
|
337
392
|
|
|
338
393
|
| File | Purpose |
|
|
339
394
|
|------|---------|
|
|
340
|
-
| `references/clawdentity-protocol.md` | Peer-map schema, pairing contract, connector handoff
|
|
341
|
-
| `references/clawdentity-registry.md` | Admin bootstrap, API key lifecycle, agent revocation, auth refresh |
|
|
395
|
+
| `references/clawdentity-protocol.md` | Peer-map schema, pairing contract, connector handoff, error codes, Docker guidance, doctor checks, identity injection |
|
|
396
|
+
| `references/clawdentity-registry.md` | Admin bootstrap, API key lifecycle, agent revocation, auth refresh, connector errors |
|
|
397
|
+
| `references/clawdentity-environment.md` | Complete environment variable reference for all CLI overrides |
|
|
398
|
+
| `examples/peers-sample.json` | Valid peers.json example with one peer entry |
|
|
399
|
+
| `examples/openclaw-relay-sample.json` | Relay runtime config example |
|
|
342
400
|
|
|
343
401
|
Directive: read the reference files before troubleshooting relay contract, connector handoff failures, or registry/admin operations.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Clawdentity Environment Variable Reference
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Complete reference for CLI environment variable overrides. When env overrides are present, config-file URL mismatches are not blockers.
|
|
6
|
+
|
|
7
|
+
## CLI Environment Variables
|
|
8
|
+
|
|
9
|
+
| Variable | Purpose | Used By |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `CLAWDENTITY_PROXY_URL` | Override proxy URL | pair, connector |
|
|
12
|
+
| `CLAWDENTITY_PROXY_WS_URL` | Override proxy WebSocket URL | connector |
|
|
13
|
+
| `CLAWDENTITY_REGISTRY_URL` | Override registry URL | config |
|
|
14
|
+
| `CLAWDENTITY_CONNECTOR_BASE_URL` | Override connector bind URL | connector |
|
|
15
|
+
| `CLAWDENTITY_CONNECTOR_OUTBOUND_PATH` | Override outbound path | relay transform |
|
|
16
|
+
| `CLAWDENTITY_AGENT_NAME` | Override agent name resolution | openclaw, transform |
|
|
17
|
+
| `OPENCLAW_BASE_URL` | Override OpenClaw upstream URL | openclaw setup |
|
|
18
|
+
| `OPENCLAW_HOOK_TOKEN` | Override hook auth token | openclaw setup |
|
|
19
|
+
| `OPENCLAW_GATEWAY_TOKEN` | Override gateway auth token | openclaw setup |
|
|
20
|
+
| `OPENCLAW_CONFIG_PATH` | Override OpenClaw config file path | openclaw |
|
|
21
|
+
| `OPENCLAW_STATE_DIR` | Override OpenClaw state directory | openclaw |
|
|
22
|
+
| `OPENCLAW_HOME` | Override OpenClaw home directory (used when explicit config/state overrides are unset) | openclaw |
|
|
23
|
+
|
|
24
|
+
## Legacy Environment Variables
|
|
25
|
+
|
|
26
|
+
| Variable | Replaced By |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `CLAWDBOT_CONFIG_PATH` | `OPENCLAW_CONFIG_PATH` |
|
|
29
|
+
| `CLAWDBOT_STATE_DIR` | `OPENCLAW_STATE_DIR` |
|
|
30
|
+
|
|
31
|
+
## Proxy Server Environment Variables
|
|
32
|
+
|
|
33
|
+
These variables configure the Clawdentity proxy server (operator-facing, not CLI):
|
|
34
|
+
|
|
35
|
+
| Variable | Purpose | Default |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `INJECT_IDENTITY_INTO_MESSAGE` | Enable/disable identity block injection into relayed messages | `true` |
|
|
38
|
+
| `RELAY_QUEUE_MAX_MESSAGES_PER_AGENT` | Max queued messages per agent | `500` |
|
|
39
|
+
| `RELAY_QUEUE_TTL_SECONDS` | Queue message time-to-live | `3600` |
|
|
40
|
+
| `RELAY_RETRY_INITIAL_MS` | Initial retry delay for relay delivery | `1000` |
|
|
@@ -6,31 +6,7 @@ Define the exact runtime contract used by `relay-to-peer.mjs`.
|
|
|
6
6
|
|
|
7
7
|
## Filesystem Paths
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- `<resolved-openclaw-state>/openclaw.json` (legacy filenames may exist: `clawdbot.json`, `moldbot.json`, `moltbot.json`)
|
|
11
|
-
- `<resolved-openclaw-state>/hooks/transforms/relay-to-peer.mjs`
|
|
12
|
-
- `<resolved-openclaw-state>/hooks/transforms/clawdentity-relay.json`
|
|
13
|
-
- `<resolved-openclaw-state>/hooks/transforms/clawdentity-peers.json`
|
|
14
|
-
- `<resolved-openclaw-state>/skills/clawdentity-openclaw-relay/SKILL.md`
|
|
15
|
-
- env overrides:
|
|
16
|
-
- `OPENCLAW_CONFIG_PATH`, `CLAWDBOT_CONFIG_PATH`
|
|
17
|
-
- `OPENCLAW_STATE_DIR`, `CLAWDBOT_STATE_DIR`
|
|
18
|
-
- `OPENCLAW_HOME` (used when explicit config/state overrides are unset)
|
|
19
|
-
|
|
20
|
-
### Clawdentity files
|
|
21
|
-
- `~/.clawdentity/config.json`
|
|
22
|
-
- `~/.clawdentity/agents/<agent-name>/secret.key`
|
|
23
|
-
- `~/.clawdentity/agents/<agent-name>/public.key`
|
|
24
|
-
- `~/.clawdentity/agents/<agent-name>/identity.json`
|
|
25
|
-
- `~/.clawdentity/agents/<agent-name>/registry-auth.json`
|
|
26
|
-
- `~/.clawdentity/agents/<agent-name>/ait.jwt`
|
|
27
|
-
- `~/.clawdentity/peers.json`
|
|
28
|
-
- `~/.clawdentity/openclaw-agent-name`
|
|
29
|
-
- `~/.clawdentity/openclaw-relay.json`
|
|
30
|
-
- `~/.clawdentity/openclaw-connectors.json`
|
|
31
|
-
- `~/.clawdentity/pairing/` (ephemeral QR PNG storage, auto-cleaned after 900s)
|
|
32
|
-
- `~/.clawdentity/cache/registry-keys.json` (1-hour TTL, used by `verify`)
|
|
33
|
-
- `~/.clawdentity/cache/crl-claims.json` (15-minute TTL, used by `verify`)
|
|
9
|
+
Canonical paths are defined in SKILL.md § Filesystem Truth. Refer there for all path contracts.
|
|
34
10
|
|
|
35
11
|
## Setup Input Contract
|
|
36
12
|
|
|
@@ -214,32 +190,82 @@ Known defaults:
|
|
|
214
190
|
|
|
215
191
|
Recovery: rerun onboarding (`clawdentity invite redeem <clw_inv_...> --display-name <human-name>`) so local config aligns to registry metadata.
|
|
216
192
|
|
|
193
|
+
## Identity Injection
|
|
194
|
+
|
|
195
|
+
When identity injection is enabled (proxy env `INJECT_IDENTITY_INTO_MESSAGE`, default `true`), the proxy prepends an identity block to the `message` field of relayed payloads.
|
|
196
|
+
|
|
197
|
+
### Block format
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
[Clawdentity Identity]
|
|
201
|
+
agentDid: did:claw:agent:01H...
|
|
202
|
+
ownerDid: did:claw:human:01H...
|
|
203
|
+
issuer: https://registry.clawdentity.com
|
|
204
|
+
aitJti: 01H...
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The block is separated from the original message by a blank line (`\n\n`).
|
|
208
|
+
|
|
209
|
+
### Field definitions
|
|
210
|
+
|
|
211
|
+
| Field | Description |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `agentDid` | Sender agent DID — use to identify the peer |
|
|
214
|
+
| `ownerDid` | DID of the human who owns the sender agent |
|
|
215
|
+
| `issuer` | Registry URL that issued the sender's AIT |
|
|
216
|
+
| `aitJti` | Unique JTI claim from the sender's AIT |
|
|
217
|
+
|
|
218
|
+
### Programmatic access
|
|
219
|
+
|
|
220
|
+
The connector `deliver` frame includes `fromAgentDid` as a top-level field. Inbound inbox items (`ConnectorInboundInboxItem`) also expose `fromAgentDid` for programmatic sender identification without parsing the identity block.
|
|
221
|
+
|
|
217
222
|
## Pairing Error Codes
|
|
218
223
|
|
|
219
224
|
### `pair start` errors
|
|
220
225
|
|
|
221
|
-
| HTTP Status | Error Code | Meaning |
|
|
222
|
-
|
|
223
|
-
| 403 | `PROXY_PAIR_OWNERSHIP_FORBIDDEN` | Initiator ownership check failed |
|
|
224
|
-
| 503 | `PROXY_PAIR_OWNERSHIP_UNAVAILABLE` | Registry ownership lookup unavailable |
|
|
225
|
-
| — | `CLI_PAIR_AGENT_NOT_FOUND` | Agent ait.jwt or secret.key missing/empty |
|
|
226
|
-
| — | `CLI_PAIR_HUMAN_NAME_MISSING` | Local config is missing `humanName
|
|
227
|
-
| — | `CLI_PAIR_PROXY_URL_REQUIRED` | Proxy URL could not be resolved |
|
|
228
|
-
| — | `CLI_PAIR_START_INVALID_TTL` | ttlSeconds must be a positive integer |
|
|
229
|
-
| — | `CLI_PAIR_INVALID_PROXY_URL` | Proxy URL is invalid |
|
|
230
|
-
| — | `CLI_PAIR_REQUEST_FAILED` | Unable to connect to proxy URL |
|
|
226
|
+
| HTTP Status | Error Code | Meaning | Recovery |
|
|
227
|
+
|---|---|---|---|
|
|
228
|
+
| 403 | `PROXY_PAIR_OWNERSHIP_FORBIDDEN` | Initiator ownership check failed | Recreate/refresh the local agent identity |
|
|
229
|
+
| 503 | `PROXY_PAIR_OWNERSHIP_UNAVAILABLE` | Registry ownership lookup unavailable | Check proxy/registry service auth configuration |
|
|
230
|
+
| — | `CLI_PAIR_AGENT_NOT_FOUND` | Agent ait.jwt or secret.key missing/empty | Run `agent create` or `agent auth refresh` |
|
|
231
|
+
| — | `CLI_PAIR_HUMAN_NAME_MISSING` | Local config is missing `humanName` | Set via `invite redeem` or config |
|
|
232
|
+
| — | `CLI_PAIR_PROXY_URL_REQUIRED` | Proxy URL could not be resolved | Run `invite redeem` or set `CLAWDENTITY_PROXY_URL` |
|
|
233
|
+
| — | `CLI_PAIR_START_INVALID_TTL` | ttlSeconds must be a positive integer | Use valid `--ttl-seconds` value |
|
|
234
|
+
| — | `CLI_PAIR_INVALID_PROXY_URL` | Proxy URL is invalid | Fix proxy URL in config |
|
|
235
|
+
| — | `CLI_PAIR_REQUEST_FAILED` | Unable to connect to proxy URL | Check DNS, firewall, proxy URL |
|
|
236
|
+
| — | `CLI_PAIR_START_FAILED` | Generic pair start failure | Retry; check proxy connectivity |
|
|
237
|
+
| — | `CLI_PAIR_PROFILE_INVALID` | Name too long, contains control characters, or empty | Fix agent or human name |
|
|
231
238
|
|
|
232
239
|
### `pair confirm` errors
|
|
233
240
|
|
|
234
|
-
| HTTP Status | Error Code | Meaning |
|
|
235
|
-
|
|
236
|
-
| 404 | `PROXY_PAIR_TICKET_NOT_FOUND` | Pairing ticket is invalid or expired |
|
|
237
|
-
| 410 | `PROXY_PAIR_TICKET_EXPIRED` | Pairing ticket has expired |
|
|
238
|
-
| — | `CLI_PAIR_CONFIRM_TICKET_REQUIRED` | Either --ticket or --qr-file is required |
|
|
239
|
-
| — | `CLI_PAIR_CONFIRM_INPUT_CONFLICT` | Cannot provide both --ticket and --qr-file |
|
|
240
|
-
| — | `CLI_PAIR_CONFIRM_TICKET_INVALID` | Pairing ticket is invalid |
|
|
241
|
-
| — | `CLI_PAIR_CONFIRM_QR_FILE_NOT_FOUND` | QR file not found |
|
|
242
|
-
| — | `CLI_PAIR_CONFIRM_QR_NOT_FOUND` | No pairing QR code found in image |
|
|
241
|
+
| HTTP Status | Error Code | Meaning | Recovery |
|
|
242
|
+
|---|---|---|---|
|
|
243
|
+
| 404 | `PROXY_PAIR_TICKET_NOT_FOUND` | Pairing ticket is invalid or expired | Request new ticket from initiator |
|
|
244
|
+
| 410 | `PROXY_PAIR_TICKET_EXPIRED` | Pairing ticket has expired | Request new ticket |
|
|
245
|
+
| — | `CLI_PAIR_CONFIRM_TICKET_REQUIRED` | Either --ticket or --qr-file is required | Provide one input path |
|
|
246
|
+
| — | `CLI_PAIR_CONFIRM_INPUT_CONFLICT` | Cannot provide both --ticket and --qr-file | Use one input path only |
|
|
247
|
+
| — | `CLI_PAIR_CONFIRM_TICKET_INVALID` | Pairing ticket is invalid | Get new ticket from initiator |
|
|
248
|
+
| — | `CLI_PAIR_CONFIRM_QR_FILE_NOT_FOUND` | QR file not found | Verify file path |
|
|
249
|
+
| — | `CLI_PAIR_CONFIRM_QR_NOT_FOUND` | No pairing QR code found in image | Request new QR from initiator |
|
|
250
|
+
| — | `CLI_PAIR_CONFIRM_FAILED` | Generic pair confirm failure | Retry with new ticket |
|
|
251
|
+
| — | `CLI_PAIR_CONFIRM_QR_FILE_INVALID` | QR image file corrupt or unsupported | Request new QR from initiator |
|
|
252
|
+
| — | `CLI_PAIR_CONFIRM_QR_FILE_REQUIRED` | QR path unusable | Verify file path and format |
|
|
253
|
+
|
|
254
|
+
### `pair status` errors
|
|
255
|
+
|
|
256
|
+
| HTTP Status | Error Code | Meaning | Recovery |
|
|
257
|
+
|---|---|---|---|
|
|
258
|
+
| — | `CLI_PAIR_STATUS_FAILED` | Generic pair status failure | Retry |
|
|
259
|
+
| — | `CLI_PAIR_STATUS_WAIT_TIMEOUT` | Wait polling timed out | Generate new ticket via `pair start` |
|
|
260
|
+
| — | `CLI_PAIR_STATUS_FORBIDDEN` | 403 on status check — ownership mismatch | Verify correct agent |
|
|
261
|
+
| — | `CLI_PAIR_STATUS_TICKET_REQUIRED` | Missing ticket argument | Provide `--ticket <clwpair1_...>` |
|
|
262
|
+
|
|
263
|
+
### Peer persistence errors
|
|
264
|
+
|
|
265
|
+
| Error Code | Meaning | Recovery |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| `CLI_PAIR_PEERS_CONFIG_INVALID` | `peers.json` corrupt or invalid structure | Delete `peers.json` and re-pair |
|
|
268
|
+
| `CLI_PAIR_PEER_ALIAS_INVALID` | Derived alias fails validation | Re-pair with valid agent DID |
|
|
243
269
|
|
|
244
270
|
## Cache Files
|
|
245
271
|
|
|
@@ -261,3 +287,38 @@ When `pair confirm` saves a new peer, alias is derived automatically:
|
|
|
261
287
|
5. Fallback alias is `peer` if DID is not a valid agent DID.
|
|
262
288
|
|
|
263
289
|
Alias validation: `[a-zA-Z0-9._-]`, max 128 characters.
|
|
290
|
+
|
|
291
|
+
## Container Environments
|
|
292
|
+
|
|
293
|
+
When running in Docker or similar container runtimes:
|
|
294
|
+
|
|
295
|
+
- `openclaw setup` writes Docker-aware endpoint candidates into `clawdentity-relay.json`:
|
|
296
|
+
- `host.docker.internal`, `gateway.docker.internal`, Linux bridge (`172.17.0.1`), default gateway, and loopback.
|
|
297
|
+
- Candidates are attempted in order by the relay transform.
|
|
298
|
+
- Use `--no-runtime-start` when the connector runs as a separate container or process.
|
|
299
|
+
- Required env overrides for container networking:
|
|
300
|
+
- `OPENCLAW_BASE_URL` — point to OpenClaw inside/outside the container network.
|
|
301
|
+
- `CLAWDENTITY_CONNECTOR_BASE_URL` — point to the connector's bind address from the transform's perspective.
|
|
302
|
+
- Port allocation: each agent gets its own connector port starting from `19400`.
|
|
303
|
+
- Port assignment is tracked in `~/.clawdentity/openclaw-connectors.json`.
|
|
304
|
+
|
|
305
|
+
## Doctor Check Reference
|
|
306
|
+
|
|
307
|
+
Run `clawdentity openclaw doctor --json` for machine-readable diagnostics.
|
|
308
|
+
|
|
309
|
+
| Check ID | Validates | Remediation on Failure |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `config.registry` | `registryUrl`, `apiKey`, and `proxyUrl` in config (or proxy env override) | `clawdentity config init` or `invite redeem` |
|
|
312
|
+
| `state.selectedAgent` | Agent marker at `~/.clawdentity/openclaw-agent-name` | `clawdentity openclaw setup <agent-name>` |
|
|
313
|
+
| `state.credentials` | `ait.jwt` and `secret.key` exist and non-empty | `clawdentity agent create <agent-name>` or `agent auth refresh <agent-name>` |
|
|
314
|
+
| `state.peers` | Peers config valid; requested `--peer` alias exists | `clawdentity pair start` / `pair confirm` (optional until pairing) |
|
|
315
|
+
| `state.transform` | Relay transform artifacts in OpenClaw hooks dir | Reinstall skill package or `openclaw setup <agent-name>` |
|
|
316
|
+
| `state.hookMapping` | `send-to-peer` hook mapping in OpenClaw config | `clawdentity openclaw setup <agent-name>` |
|
|
317
|
+
| `state.hookToken` | Hooks enabled with token in OpenClaw config | `clawdentity openclaw setup <agent-name>` then restart OpenClaw |
|
|
318
|
+
| `state.hookSessionRouting` | `hooks.defaultSessionKey`, `hooks.allowRequestSessionKey=false`, and required prefixes | `clawdentity openclaw setup <agent-name>` then restart OpenClaw |
|
|
319
|
+
| `state.gatewayAuth` | OpenClaw `gateway.auth` readiness (`mode` + required credential) | `clawdentity openclaw setup <agent-name>` to re-sync gateway auth |
|
|
320
|
+
| `state.gatewayDevicePairing` | Pending OpenClaw device approvals | Re-run `clawdentity openclaw setup <agent-name>` so setup auto-recovers approvals |
|
|
321
|
+
| `state.openclawBaseUrl` | OpenClaw base URL resolvable | `clawdentity openclaw setup <agent-name> --openclaw-base-url <url>` |
|
|
322
|
+
| `state.connectorRuntime` | Local connector runtime reachable and websocket-connected | `clawdentity openclaw setup <agent-name>` |
|
|
323
|
+
| `state.connectorInboundInbox` | Connector local inbound inbox backlog and replay queue state | Re-run `clawdentity openclaw setup <agent-name>` and verify connector runtime health |
|
|
324
|
+
| `state.openclawHookHealth` | Connector replay status for local OpenClaw hook delivery | Re-run `clawdentity openclaw setup <agent-name>` and restart OpenClaw if hook replay stays failed |
|
|
@@ -173,3 +173,14 @@ Admin-only. Creates a registry invite code (`clw_inv_...`) for onboarding new us
|
|
|
173
173
|
| 401 | Authentication failed |
|
|
174
174
|
| 403 | Requires admin access |
|
|
175
175
|
| 400 | Invalid request |
|
|
176
|
+
|
|
177
|
+
## Connector Errors
|
|
178
|
+
|
|
179
|
+
| Error Code | Meaning | Recovery |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `CLI_CONNECTOR_SERVICE_PLATFORM_INVALID` | Invalid platform argument | Use `auto`, `launchd`, or `systemd` |
|
|
182
|
+
| `CLI_CONNECTOR_SERVICE_PLATFORM_UNSUPPORTED` | OS unsupported for selected platform | Use a supported platform (macOS: launchd, Linux: systemd) |
|
|
183
|
+
| `CLI_CONNECTOR_SERVICE_INSTALL_FAILED` | Service install failed | Check permissions, systemd/launchd status |
|
|
184
|
+
| `CLI_CONNECTOR_PROXY_URL_REQUIRED` | Proxy URL unresolvable | Run `invite redeem` or set `CLAWDENTITY_PROXY_URL` / `CLAWDENTITY_PROXY_WS_URL` |
|
|
185
|
+
| `CLI_CONNECTOR_INVALID_REGISTRY_AUTH` | `registry-auth.json` corrupt or invalid | Run `clawdentity agent auth refresh <agent-name>` |
|
|
186
|
+
| `CLI_CONNECTOR_INVALID_AGENT_IDENTITY` | `identity.json` corrupt or invalid | Re-create agent with `clawdentity agent create <agent-name>` |
|