clawdentity 0.0.18 → 0.0.19

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/dist/bin.js CHANGED
@@ -21090,7 +21090,6 @@ var HOOK_PATH_SEND_TO_PEER = "send-to-peer";
21090
21090
  var OPENCLAW_SEND_TO_PEER_HOOK_PATH = "hooks/send-to-peer";
21091
21091
  var DEFAULT_OPENCLAW_BASE_URL2 = "http://127.0.0.1:18789";
21092
21092
  var DEFAULT_OPENCLAW_MAIN_SESSION_KEY = "main";
21093
- var DEFAULT_OPENCLAW_AGENT_ID = "main";
21094
21093
  var DEFAULT_CONNECTOR_PORT = 19400;
21095
21094
  var DEFAULT_CONNECTOR_OUTBOUND_PATH3 = "/v1/outbound";
21096
21095
  var DEFAULT_CONNECTOR_STATUS_PATH2 = "/v1/status";
@@ -21107,6 +21106,7 @@ var OPENCLAW_SETUP_COMMAND_HINT = "Run: clawdentity openclaw setup <agentName>";
21107
21106
  var OPENCLAW_SETUP_RESTART_COMMAND_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} and restart OpenClaw`;
21108
21107
  var OPENCLAW_SETUP_WITH_BASE_URL_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} --openclaw-base-url <url>`;
21109
21108
  var OPENCLAW_PAIRING_COMMAND_HINT = "Run QR pairing first: clawdentity pair start <agentName> --qr and clawdentity pair confirm <agentName> --qr-file <path>";
21109
+ var OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT = "Run: openclaw devices list and openclaw devices approve <requestId>";
21110
21110
  var textEncoder2 = new TextEncoder();
21111
21111
  var textDecoder = new TextDecoder();
21112
21112
  function isRecord8(value) {
@@ -21773,9 +21773,11 @@ function parseRelayRuntimeConfig(value, relayRuntimeConfigPath) {
21773
21773
  }
21774
21774
  const updatedAt = typeof value.updatedAt === "string" && value.updatedAt.trim().length > 0 ? value.updatedAt.trim() : void 0;
21775
21775
  const openclawHookToken = typeof value.openclawHookToken === "string" && value.openclawHookToken.trim().length > 0 ? value.openclawHookToken.trim() : void 0;
21776
+ const relayTransformPeersPath = typeof value.relayTransformPeersPath === "string" && value.relayTransformPeersPath.trim().length > 0 ? value.relayTransformPeersPath.trim() : void 0;
21776
21777
  return {
21777
21778
  openclawBaseUrl: parseOpenclawBaseUrl(value.openclawBaseUrl),
21778
21779
  openclawHookToken,
21780
+ relayTransformPeersPath,
21779
21781
  updatedAt
21780
21782
  };
21781
21783
  }
@@ -21791,10 +21793,11 @@ async function loadRelayRuntimeConfig(relayRuntimeConfigPath) {
21791
21793
  }
21792
21794
  return parseRelayRuntimeConfig(parsed, relayRuntimeConfigPath);
21793
21795
  }
21794
- async function saveRelayRuntimeConfig(relayRuntimeConfigPath, openclawBaseUrl, openclawHookToken) {
21796
+ async function saveRelayRuntimeConfig(relayRuntimeConfigPath, openclawBaseUrl, openclawHookToken, relayTransformPeersPath) {
21795
21797
  const config2 = {
21796
21798
  openclawBaseUrl,
21797
21799
  ...openclawHookToken ? { openclawHookToken } : {},
21800
+ ...relayTransformPeersPath ? { relayTransformPeersPath } : {},
21798
21801
  updatedAt: nowIso()
21799
21802
  };
21800
21803
  await writeSecureFile3(
@@ -21841,33 +21844,40 @@ function normalizeStringArrayWithValues(value, requiredValues) {
21841
21844
  return Array.from(normalized);
21842
21845
  }
21843
21846
  function resolveHookDefaultSessionKey(config2, hooks) {
21844
- if (typeof hooks.defaultSessionKey === "string" && hooks.defaultSessionKey.trim().length > 0) {
21845
- return hooks.defaultSessionKey.trim();
21846
- }
21847
21847
  const session = isRecord8(config2.session) ? config2.session : {};
21848
21848
  const scope = typeof session.scope === "string" ? session.scope.trim().toLowerCase() : "";
21849
+ const configuredMainSessionKey = resolveConfiguredOpenclawMainSessionKey(session);
21850
+ if (typeof hooks.defaultSessionKey === "string" && hooks.defaultSessionKey.trim().length > 0) {
21851
+ return normalizeLegacyHookDefaultSessionKey(
21852
+ hooks.defaultSessionKey,
21853
+ configuredMainSessionKey
21854
+ );
21855
+ }
21849
21856
  if (scope === "global") {
21850
21857
  return "global";
21851
21858
  }
21852
- const agents = isRecord8(config2.agents) ? config2.agents : {};
21853
- const agentList = Array.isArray(agents.list) ? agents.list : [];
21854
- const defaultAgentId = resolveDefaultOpenclawAgentId(agentList);
21855
- return `agent:${defaultAgentId}:${DEFAULT_OPENCLAW_MAIN_SESSION_KEY}`;
21859
+ return configuredMainSessionKey;
21856
21860
  }
21857
- function resolveDefaultOpenclawAgentId(agentList) {
21858
- const preferred = agentList.find(
21859
- (agent) => isRecord8(agent) && agent.default === true && typeof agent.id === "string" && agent.id.trim().length > 0
21860
- ) ?? agentList.find(
21861
- (agent) => isRecord8(agent) && typeof agent.id === "string" && agent.id.trim().length > 0
21862
- );
21863
- if (isRecord8(preferred) && typeof preferred.id === "string" && preferred.id.trim().length > 0) {
21864
- return normalizeOpenclawIdToken(preferred.id, DEFAULT_OPENCLAW_AGENT_ID);
21861
+ function resolveConfiguredOpenclawMainSessionKey(session) {
21862
+ if (typeof session.mainKey === "string" && session.mainKey.trim().length > 0) {
21863
+ return session.mainKey.trim();
21865
21864
  }
21866
- return DEFAULT_OPENCLAW_AGENT_ID;
21865
+ return DEFAULT_OPENCLAW_MAIN_SESSION_KEY;
21867
21866
  }
21868
- function normalizeOpenclawIdToken(value, fallback) {
21869
- const normalized = value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+/g, "").replace(/-+$/g, "").slice(0, 64);
21870
- return normalized.length > 0 ? normalized : fallback;
21867
+ function normalizeLegacyHookDefaultSessionKey(value, fallbackSessionKey) {
21868
+ const trimmed = value.trim();
21869
+ const legacyMatch = /^agent:[^:]+:(.+)$/i.exec(trimmed);
21870
+ if (!legacyMatch) {
21871
+ return trimmed;
21872
+ }
21873
+ const routedSessionKey = legacyMatch[1]?.trim();
21874
+ if (typeof routedSessionKey === "string" && routedSessionKey.length > 0) {
21875
+ return routedSessionKey;
21876
+ }
21877
+ return fallbackSessionKey;
21878
+ }
21879
+ function isCanonicalAgentSessionKey(value) {
21880
+ return /^agent:[^:]+:.+/i.test(value.trim());
21871
21881
  }
21872
21882
  function generateOpenclawHookToken() {
21873
21883
  return randomBytes3(OPENCLAW_HOOK_TOKEN_BYTES).toString("hex");
@@ -22051,103 +22061,105 @@ async function runOpenclawDoctor(options = {}) {
22051
22061
  const openclawDir = resolveOpenclawDir(options.openclawDir, homeDir);
22052
22062
  const peerAlias = parseDoctorPeerAlias(options.peerAlias);
22053
22063
  const checks = [];
22054
- const resolveConfigImpl = options.resolveConfigImpl ?? resolveConfig;
22055
- try {
22056
- const resolvedConfig = await resolveConfigImpl();
22057
- const envProxyUrl = typeof process.env.CLAWDENTITY_PROXY_URL === "string" ? process.env.CLAWDENTITY_PROXY_URL.trim() : "";
22058
- if (typeof resolvedConfig.registryUrl !== "string" || resolvedConfig.registryUrl.trim().length === 0) {
22059
- checks.push(
22060
- toDoctorCheck({
22061
- id: "config.registry",
22062
- label: "CLI config",
22063
- status: "fail",
22064
- message: "registryUrl is missing",
22065
- remediationHint: "Run: clawdentity config set registryUrl <REGISTRY_URL>"
22066
- })
22067
- );
22068
- } else if (typeof resolvedConfig.apiKey !== "string" || resolvedConfig.apiKey.trim().length === 0) {
22069
- checks.push(
22070
- toDoctorCheck({
22071
- id: "config.registry",
22072
- label: "CLI config",
22073
- status: "fail",
22074
- message: "apiKey is missing",
22075
- remediationHint: "Run: clawdentity config set apiKey <API_KEY>"
22076
- })
22077
- );
22078
- } else if (envProxyUrl.length > 0) {
22079
- let hasValidEnvProxyUrl = true;
22080
- try {
22081
- parseProxyUrl(envProxyUrl);
22082
- } catch {
22083
- hasValidEnvProxyUrl = false;
22064
+ if (options.includeConfigCheck !== false) {
22065
+ const resolveConfigImpl = options.resolveConfigImpl ?? resolveConfig;
22066
+ try {
22067
+ const resolvedConfig = await resolveConfigImpl();
22068
+ const envProxyUrl = typeof process.env.CLAWDENTITY_PROXY_URL === "string" ? process.env.CLAWDENTITY_PROXY_URL.trim() : "";
22069
+ if (typeof resolvedConfig.registryUrl !== "string" || resolvedConfig.registryUrl.trim().length === 0) {
22084
22070
  checks.push(
22085
22071
  toDoctorCheck({
22086
22072
  id: "config.registry",
22087
22073
  label: "CLI config",
22088
22074
  status: "fail",
22089
- message: "CLAWDENTITY_PROXY_URL is invalid",
22090
- remediationHint: "Set CLAWDENTITY_PROXY_URL to a valid http(s) URL or unset it"
22075
+ message: "registryUrl is missing",
22076
+ remediationHint: "Run: clawdentity config set registryUrl <REGISTRY_URL>"
22091
22077
  })
22092
22078
  );
22093
- }
22094
- if (hasValidEnvProxyUrl) {
22079
+ } else if (typeof resolvedConfig.apiKey !== "string" || resolvedConfig.apiKey.trim().length === 0) {
22095
22080
  checks.push(
22096
22081
  toDoctorCheck({
22097
22082
  id: "config.registry",
22098
22083
  label: "CLI config",
22099
- status: "pass",
22100
- message: "registryUrl and apiKey are configured (proxy URL override is active via CLAWDENTITY_PROXY_URL)"
22084
+ status: "fail",
22085
+ message: "apiKey is missing",
22086
+ remediationHint: "Run: clawdentity config set apiKey <API_KEY>"
22101
22087
  })
22102
22088
  );
22103
- }
22104
- } else if (typeof resolvedConfig.proxyUrl !== "string" || resolvedConfig.proxyUrl.trim().length === 0) {
22105
- checks.push(
22106
- toDoctorCheck({
22107
- id: "config.registry",
22108
- label: "CLI config",
22109
- status: "fail",
22110
- message: "proxyUrl is missing",
22111
- remediationHint: "Run: clawdentity invite redeem <clw_inv_...> or clawdentity config init"
22112
- })
22113
- );
22114
- } else {
22115
- let hasValidConfigProxyUrl = true;
22116
- try {
22117
- parseProxyUrl(resolvedConfig.proxyUrl);
22118
- } catch {
22119
- hasValidConfigProxyUrl = false;
22089
+ } else if (envProxyUrl.length > 0) {
22090
+ let hasValidEnvProxyUrl = true;
22091
+ try {
22092
+ parseProxyUrl(envProxyUrl);
22093
+ } catch {
22094
+ hasValidEnvProxyUrl = false;
22095
+ checks.push(
22096
+ toDoctorCheck({
22097
+ id: "config.registry",
22098
+ label: "CLI config",
22099
+ status: "fail",
22100
+ message: "CLAWDENTITY_PROXY_URL is invalid",
22101
+ remediationHint: "Set CLAWDENTITY_PROXY_URL to a valid http(s) URL or unset it"
22102
+ })
22103
+ );
22104
+ }
22105
+ if (hasValidEnvProxyUrl) {
22106
+ checks.push(
22107
+ toDoctorCheck({
22108
+ id: "config.registry",
22109
+ label: "CLI config",
22110
+ status: "pass",
22111
+ message: "registryUrl and apiKey are configured (proxy URL override is active via CLAWDENTITY_PROXY_URL)"
22112
+ })
22113
+ );
22114
+ }
22115
+ } else if (typeof resolvedConfig.proxyUrl !== "string" || resolvedConfig.proxyUrl.trim().length === 0) {
22120
22116
  checks.push(
22121
22117
  toDoctorCheck({
22122
22118
  id: "config.registry",
22123
22119
  label: "CLI config",
22124
22120
  status: "fail",
22125
- message: "proxyUrl is invalid",
22121
+ message: "proxyUrl is missing",
22126
22122
  remediationHint: "Run: clawdentity invite redeem <clw_inv_...> or clawdentity config init"
22127
22123
  })
22128
22124
  );
22125
+ } else {
22126
+ let hasValidConfigProxyUrl = true;
22127
+ try {
22128
+ parseProxyUrl(resolvedConfig.proxyUrl);
22129
+ } catch {
22130
+ hasValidConfigProxyUrl = false;
22131
+ checks.push(
22132
+ toDoctorCheck({
22133
+ id: "config.registry",
22134
+ label: "CLI config",
22135
+ status: "fail",
22136
+ message: "proxyUrl is invalid",
22137
+ remediationHint: "Run: clawdentity invite redeem <clw_inv_...> or clawdentity config init"
22138
+ })
22139
+ );
22140
+ }
22141
+ if (hasValidConfigProxyUrl) {
22142
+ checks.push(
22143
+ toDoctorCheck({
22144
+ id: "config.registry",
22145
+ label: "CLI config",
22146
+ status: "pass",
22147
+ message: "registryUrl, apiKey, and proxyUrl are configured"
22148
+ })
22149
+ );
22150
+ }
22129
22151
  }
22130
- if (hasValidConfigProxyUrl) {
22131
- checks.push(
22132
- toDoctorCheck({
22133
- id: "config.registry",
22134
- label: "CLI config",
22135
- status: "pass",
22136
- message: "registryUrl, apiKey, and proxyUrl are configured"
22137
- })
22138
- );
22139
- }
22152
+ } catch {
22153
+ checks.push(
22154
+ toDoctorCheck({
22155
+ id: "config.registry",
22156
+ label: "CLI config",
22157
+ status: "fail",
22158
+ message: "unable to resolve CLI config",
22159
+ remediationHint: "Fix ~/.clawdentity/config.json or rerun: clawdentity config init"
22160
+ })
22161
+ );
22140
22162
  }
22141
- } catch {
22142
- checks.push(
22143
- toDoctorCheck({
22144
- id: "config.registry",
22145
- label: "CLI config",
22146
- status: "fail",
22147
- message: "unable to resolve CLI config",
22148
- remediationHint: "Fix ~/.clawdentity/config.json or rerun: clawdentity config init"
22149
- })
22150
- );
22151
22163
  }
22152
22164
  const selectedAgentPath = resolveOpenclawAgentNamePath(homeDir);
22153
22165
  let selectedAgentName;
@@ -22336,6 +22348,15 @@ async function runOpenclawDoctor(options = {}) {
22336
22348
  const hooks = isRecord8(openclawConfig.hooks) ? openclawConfig.hooks : {};
22337
22349
  const hooksEnabled = hooks.enabled === true;
22338
22350
  const hookToken = typeof hooks.token === "string" && hooks.token.trim().length > 0 ? hooks.token.trim() : void 0;
22351
+ const defaultSessionKey = typeof hooks.defaultSessionKey === "string" && hooks.defaultSessionKey.trim().length > 0 ? hooks.defaultSessionKey.trim() : void 0;
22352
+ const allowRequestSessionKey = hooks.allowRequestSessionKey === false;
22353
+ const allowedSessionKeyPrefixes = normalizeStringArrayWithValues(
22354
+ hooks.allowedSessionKeyPrefixes,
22355
+ []
22356
+ );
22357
+ const missingRequiredSessionPrefixes = defaultSessionKey === void 0 ? ["hook:"] : ["hook:", defaultSessionKey].filter(
22358
+ (prefix) => !allowedSessionKeyPrefixes.includes(prefix)
22359
+ );
22339
22360
  const mappings = Array.isArray(hooks.mappings) ? hooks.mappings.filter(isRecord8) : [];
22340
22361
  const relayMapping = mappings.find(
22341
22362
  (mapping) => isRelayHookMapping(mapping)
@@ -22395,6 +22416,45 @@ async function runOpenclawDoctor(options = {}) {
22395
22416
  })
22396
22417
  );
22397
22418
  }
22419
+ const sessionRoutingIssues = [];
22420
+ if (defaultSessionKey === void 0) {
22421
+ sessionRoutingIssues.push("hooks.defaultSessionKey is missing");
22422
+ }
22423
+ if (!allowRequestSessionKey) {
22424
+ sessionRoutingIssues.push("hooks.allowRequestSessionKey is not false");
22425
+ }
22426
+ if (missingRequiredSessionPrefixes.length > 0) {
22427
+ sessionRoutingIssues.push(
22428
+ `hooks.allowedSessionKeyPrefixes is missing: ${missingRequiredSessionPrefixes.join(", ")}`
22429
+ );
22430
+ }
22431
+ if (defaultSessionKey !== void 0 && isCanonicalAgentSessionKey(defaultSessionKey)) {
22432
+ sessionRoutingIssues.push(
22433
+ "hooks.defaultSessionKey uses canonical agent format (agent:<id>:...); use OpenClaw request session keys like main, global, or subagent:*"
22434
+ );
22435
+ }
22436
+ if (sessionRoutingIssues.length > 0) {
22437
+ checks.push(
22438
+ toDoctorCheck({
22439
+ id: "state.hookSessionRouting",
22440
+ label: "OpenClaw hook session routing",
22441
+ status: "fail",
22442
+ message: sessionRoutingIssues.join("; "),
22443
+ remediationHint: OPENCLAW_SETUP_RESTART_COMMAND_HINT,
22444
+ details: { openclawConfigPath }
22445
+ })
22446
+ );
22447
+ } else {
22448
+ checks.push(
22449
+ toDoctorCheck({
22450
+ id: "state.hookSessionRouting",
22451
+ label: "OpenClaw hook session routing",
22452
+ status: "pass",
22453
+ message: "hooks default session and allowed session prefixes are configured",
22454
+ details: { openclawConfigPath }
22455
+ })
22456
+ );
22457
+ }
22398
22458
  } catch {
22399
22459
  checks.push(
22400
22460
  toDoctorCheck({
@@ -22416,6 +22476,16 @@ async function runOpenclawDoctor(options = {}) {
22416
22476
  details: { openclawConfigPath }
22417
22477
  })
22418
22478
  );
22479
+ checks.push(
22480
+ toDoctorCheck({
22481
+ id: "state.hookSessionRouting",
22482
+ label: "OpenClaw hook session routing",
22483
+ status: "fail",
22484
+ message: `unable to read ${openclawConfigPath}`,
22485
+ remediationHint: "Ensure the OpenClaw config file exists (OPENCLAW_CONFIG_PATH/CLAWDBOT_CONFIG_PATH, or state dir) and rerun openclaw setup",
22486
+ details: { openclawConfigPath }
22487
+ })
22488
+ );
22419
22489
  }
22420
22490
  const relayRuntimeConfigPath = resolveRelayRuntimeConfigPath(homeDir);
22421
22491
  try {
@@ -22441,6 +22511,72 @@ async function runOpenclawDoctor(options = {}) {
22441
22511
  })
22442
22512
  );
22443
22513
  }
22514
+ const gatewayDevicePendingPath = join6(openclawDir, "devices", "pending.json");
22515
+ try {
22516
+ const pendingPayload = await readJsonFile(gatewayDevicePendingPath);
22517
+ if (!isRecord8(pendingPayload)) {
22518
+ checks.push(
22519
+ toDoctorCheck({
22520
+ id: "state.gatewayDevicePairing",
22521
+ label: "OpenClaw gateway device pairing",
22522
+ status: "fail",
22523
+ message: `invalid pending device approvals file: ${gatewayDevicePendingPath}`,
22524
+ remediationHint: OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT,
22525
+ details: { gatewayDevicePendingPath }
22526
+ })
22527
+ );
22528
+ } else {
22529
+ const pendingRequestIds = Object.keys(pendingPayload);
22530
+ if (pendingRequestIds.length === 0) {
22531
+ checks.push(
22532
+ toDoctorCheck({
22533
+ id: "state.gatewayDevicePairing",
22534
+ label: "OpenClaw gateway device pairing",
22535
+ status: "pass",
22536
+ message: "no pending gateway device approvals",
22537
+ details: { gatewayDevicePendingPath }
22538
+ })
22539
+ );
22540
+ } else {
22541
+ checks.push(
22542
+ toDoctorCheck({
22543
+ id: "state.gatewayDevicePairing",
22544
+ label: "OpenClaw gateway device pairing",
22545
+ status: "fail",
22546
+ message: `pending gateway device approvals: ${pendingRequestIds.length}`,
22547
+ remediationHint: OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT,
22548
+ details: {
22549
+ gatewayDevicePendingPath,
22550
+ pendingRequestIds
22551
+ }
22552
+ })
22553
+ );
22554
+ }
22555
+ }
22556
+ } catch (error48) {
22557
+ if (getErrorCode2(error48) === "ENOENT") {
22558
+ checks.push(
22559
+ toDoctorCheck({
22560
+ id: "state.gatewayDevicePairing",
22561
+ label: "OpenClaw gateway device pairing",
22562
+ status: "pass",
22563
+ message: "no pending gateway device approvals file was found",
22564
+ details: { gatewayDevicePendingPath }
22565
+ })
22566
+ );
22567
+ } else {
22568
+ checks.push(
22569
+ toDoctorCheck({
22570
+ id: "state.gatewayDevicePairing",
22571
+ label: "OpenClaw gateway device pairing",
22572
+ status: "fail",
22573
+ message: `unable to read pending device approvals at ${gatewayDevicePendingPath}`,
22574
+ remediationHint: OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT,
22575
+ details: { gatewayDevicePendingPath }
22576
+ })
22577
+ );
22578
+ }
22579
+ }
22444
22580
  if (options.includeConnectorRuntimeCheck !== false) {
22445
22581
  if (selectedAgentName === void 0) {
22446
22582
  checks.push(
@@ -22803,7 +22939,8 @@ async function setupOpenclawRelay(agentName, options) {
22803
22939
  await saveRelayRuntimeConfig(
22804
22940
  relayRuntimeConfigPath,
22805
22941
  openclawBaseUrl,
22806
- patchedOpenclawConfig.hookToken
22942
+ patchedOpenclawConfig.hookToken,
22943
+ relayTransformPeersPath
22807
22944
  );
22808
22945
  logger8.info("cli.openclaw_setup_completed", {
22809
22946
  agentName: normalizedAgentName,
@@ -22825,9 +22962,45 @@ async function setupOpenclawRelay(agentName, options) {
22825
22962
  relayRuntimeConfigPath
22826
22963
  };
22827
22964
  }
22965
+ async function assertSetupChecklistHealthy(input) {
22966
+ const checklist = await runOpenclawDoctor({
22967
+ homeDir: input.homeDir,
22968
+ openclawDir: input.openclawDir,
22969
+ includeConfigCheck: false,
22970
+ includeConnectorRuntimeCheck: input.includeConnectorRuntimeCheck
22971
+ });
22972
+ if (checklist.status === "healthy") {
22973
+ return;
22974
+ }
22975
+ const firstFailure = checklist.checks.find((check2) => check2.status === "fail");
22976
+ throw createCliError6(
22977
+ "CLI_OPENCLAW_SETUP_CHECKLIST_FAILED",
22978
+ "OpenClaw setup checklist failed",
22979
+ {
22980
+ firstFailedCheckId: firstFailure?.id,
22981
+ firstFailedCheckMessage: firstFailure?.message,
22982
+ remediationHint: firstFailure?.remediationHint,
22983
+ checks: checklist.checks
22984
+ }
22985
+ );
22986
+ }
22828
22987
  async function setupOpenclawSelfReady(agentName, options) {
22829
- const setup = await setupOpenclawRelay(agentName, options);
22988
+ const resolvedHomeDir = resolveHomeDir(options.homeDir);
22989
+ const resolvedOpenclawDir = resolveOpenclawDir(
22990
+ options.openclawDir,
22991
+ resolvedHomeDir
22992
+ );
22993
+ const setup = await setupOpenclawRelay(agentName, {
22994
+ ...options,
22995
+ homeDir: resolvedHomeDir,
22996
+ openclawDir: resolvedOpenclawDir
22997
+ });
22830
22998
  if (options.noRuntimeStart === true) {
22999
+ await assertSetupChecklistHealthy({
23000
+ homeDir: resolvedHomeDir,
23001
+ openclawDir: resolvedOpenclawDir,
23002
+ includeConnectorRuntimeCheck: false
23003
+ });
22831
23004
  return {
22832
23005
  ...setup,
22833
23006
  runtimeMode: "none",
@@ -22846,7 +23019,6 @@ async function setupOpenclawSelfReady(agentName, options) {
22846
23019
  const waitTimeoutSeconds = parseWaitTimeoutSeconds(
22847
23020
  options.waitTimeoutSeconds
22848
23021
  );
22849
- const resolvedHomeDir = resolveHomeDir(options.homeDir);
22850
23022
  const runtime = await startSetupConnectorRuntime({
22851
23023
  agentName: assertValidAgentName(agentName),
22852
23024
  homeDir: resolvedHomeDir,
@@ -22856,6 +23028,11 @@ async function setupOpenclawSelfReady(agentName, options) {
22856
23028
  waitTimeoutSeconds,
22857
23029
  fetchImpl
22858
23030
  });
23031
+ await assertSetupChecklistHealthy({
23032
+ homeDir: resolvedHomeDir,
23033
+ openclawDir: resolvedOpenclawDir,
23034
+ includeConnectorRuntimeCheck: true
23035
+ });
22859
23036
  return {
22860
23037
  ...setup,
22861
23038
  ...runtime
@@ -23001,6 +23178,7 @@ var AIT_FILE_NAME4 = "ait.jwt";
23001
23178
  var SECRET_KEY_FILE_NAME3 = "secret.key";
23002
23179
  var PAIRING_QR_DIR_NAME = "pairing";
23003
23180
  var PEERS_FILE_NAME2 = "peers.json";
23181
+ var OPENCLAW_RELAY_RUNTIME_FILE_NAME3 = "openclaw-relay.json";
23004
23182
  var PAIR_START_PATH = "/pair/start";
23005
23183
  var PAIR_CONFIRM_PATH = "/pair/confirm";
23006
23184
  var PAIR_STATUS_PATH = "/pair/status";
@@ -23303,6 +23481,86 @@ async function savePeersConfig2(input) {
23303
23481
  );
23304
23482
  await input.chmodImpl(peersPath, FILE_MODE4);
23305
23483
  }
23484
+ function resolveRelayRuntimeConfigPath2(getConfigDirImpl) {
23485
+ return join7(getConfigDirImpl(), OPENCLAW_RELAY_RUNTIME_FILE_NAME3);
23486
+ }
23487
+ async function loadRelayTransformPeersPath(input) {
23488
+ const relayRuntimeConfigPath = resolveRelayRuntimeConfigPath2(
23489
+ input.getConfigDirImpl
23490
+ );
23491
+ let raw;
23492
+ try {
23493
+ raw = await input.readFileImpl(relayRuntimeConfigPath, "utf8");
23494
+ } catch (error48) {
23495
+ const nodeError = error48;
23496
+ if (nodeError.code === "ENOENT") {
23497
+ return void 0;
23498
+ }
23499
+ logger9.warn("cli.pair.relay_runtime_read_failed", {
23500
+ relayRuntimeConfigPath,
23501
+ reason: error48 instanceof Error && error48.message.length > 0 ? error48.message : "unknown"
23502
+ });
23503
+ return void 0;
23504
+ }
23505
+ let parsed;
23506
+ try {
23507
+ parsed = JSON.parse(raw);
23508
+ } catch {
23509
+ logger9.warn("cli.pair.relay_runtime_invalid_json", {
23510
+ relayRuntimeConfigPath
23511
+ });
23512
+ return void 0;
23513
+ }
23514
+ if (!isRecord9(parsed)) {
23515
+ return void 0;
23516
+ }
23517
+ const relayTransformPeersPath = parseNonEmptyString9(
23518
+ parsed.relayTransformPeersPath
23519
+ );
23520
+ if (relayTransformPeersPath.length === 0) {
23521
+ return void 0;
23522
+ }
23523
+ return resolve(relayTransformPeersPath);
23524
+ }
23525
+ async function syncOpenclawRelayPeersSnapshot(input) {
23526
+ const relayTransformPeersPath = await loadRelayTransformPeersPath({
23527
+ getConfigDirImpl: input.getConfigDirImpl,
23528
+ readFileImpl: input.readFileImpl
23529
+ });
23530
+ if (relayTransformPeersPath === void 0) {
23531
+ return;
23532
+ }
23533
+ try {
23534
+ await input.readFileImpl(relayTransformPeersPath, "utf8");
23535
+ } catch (error48) {
23536
+ const nodeError = error48;
23537
+ if (nodeError.code === "ENOENT") {
23538
+ return;
23539
+ }
23540
+ logger9.warn("cli.pair.relay_peers_snapshot_probe_failed", {
23541
+ relayTransformPeersPath,
23542
+ reason: error48 instanceof Error && error48.message.length > 0 ? error48.message : "unknown"
23543
+ });
23544
+ return;
23545
+ }
23546
+ try {
23547
+ await input.mkdirImpl(dirname5(relayTransformPeersPath), {
23548
+ recursive: true
23549
+ });
23550
+ await input.writeFileImpl(
23551
+ relayTransformPeersPath,
23552
+ `${JSON.stringify(input.config, null, 2)}
23553
+ `,
23554
+ "utf8"
23555
+ );
23556
+ await input.chmodImpl(relayTransformPeersPath, FILE_MODE4);
23557
+ } catch (error48) {
23558
+ logger9.warn("cli.pair.relay_peers_snapshot_write_failed", {
23559
+ relayTransformPeersPath,
23560
+ reason: error48 instanceof Error && error48.message.length > 0 ? error48.message : "unknown"
23561
+ });
23562
+ }
23563
+ }
23306
23564
  function parseTtlSeconds(value) {
23307
23565
  const raw = parseNonEmptyString9(value);
23308
23566
  if (raw.length === 0) {
@@ -23828,6 +24086,14 @@ async function persistPairedPeer(input) {
23828
24086
  writeFileImpl,
23829
24087
  chmodImpl
23830
24088
  });
24089
+ await syncOpenclawRelayPeersSnapshot({
24090
+ config: peersConfig,
24091
+ getConfigDirImpl,
24092
+ readFileImpl,
24093
+ mkdirImpl,
24094
+ writeFileImpl,
24095
+ chmodImpl
24096
+ });
23831
24097
  return alias;
23832
24098
  }
23833
24099
  async function startPairing(agentName, options, dependencies = {}) {
package/dist/index.js CHANGED
@@ -21090,7 +21090,6 @@ var HOOK_PATH_SEND_TO_PEER = "send-to-peer";
21090
21090
  var OPENCLAW_SEND_TO_PEER_HOOK_PATH = "hooks/send-to-peer";
21091
21091
  var DEFAULT_OPENCLAW_BASE_URL2 = "http://127.0.0.1:18789";
21092
21092
  var DEFAULT_OPENCLAW_MAIN_SESSION_KEY = "main";
21093
- var DEFAULT_OPENCLAW_AGENT_ID = "main";
21094
21093
  var DEFAULT_CONNECTOR_PORT = 19400;
21095
21094
  var DEFAULT_CONNECTOR_OUTBOUND_PATH3 = "/v1/outbound";
21096
21095
  var DEFAULT_CONNECTOR_STATUS_PATH2 = "/v1/status";
@@ -21107,6 +21106,7 @@ var OPENCLAW_SETUP_COMMAND_HINT = "Run: clawdentity openclaw setup <agentName>";
21107
21106
  var OPENCLAW_SETUP_RESTART_COMMAND_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} and restart OpenClaw`;
21108
21107
  var OPENCLAW_SETUP_WITH_BASE_URL_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} --openclaw-base-url <url>`;
21109
21108
  var OPENCLAW_PAIRING_COMMAND_HINT = "Run QR pairing first: clawdentity pair start <agentName> --qr and clawdentity pair confirm <agentName> --qr-file <path>";
21109
+ var OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT = "Run: openclaw devices list and openclaw devices approve <requestId>";
21110
21110
  var textEncoder2 = new TextEncoder();
21111
21111
  var textDecoder = new TextDecoder();
21112
21112
  function isRecord8(value) {
@@ -21773,9 +21773,11 @@ function parseRelayRuntimeConfig(value, relayRuntimeConfigPath) {
21773
21773
  }
21774
21774
  const updatedAt = typeof value.updatedAt === "string" && value.updatedAt.trim().length > 0 ? value.updatedAt.trim() : void 0;
21775
21775
  const openclawHookToken = typeof value.openclawHookToken === "string" && value.openclawHookToken.trim().length > 0 ? value.openclawHookToken.trim() : void 0;
21776
+ const relayTransformPeersPath = typeof value.relayTransformPeersPath === "string" && value.relayTransformPeersPath.trim().length > 0 ? value.relayTransformPeersPath.trim() : void 0;
21776
21777
  return {
21777
21778
  openclawBaseUrl: parseOpenclawBaseUrl(value.openclawBaseUrl),
21778
21779
  openclawHookToken,
21780
+ relayTransformPeersPath,
21779
21781
  updatedAt
21780
21782
  };
21781
21783
  }
@@ -21791,10 +21793,11 @@ async function loadRelayRuntimeConfig(relayRuntimeConfigPath) {
21791
21793
  }
21792
21794
  return parseRelayRuntimeConfig(parsed, relayRuntimeConfigPath);
21793
21795
  }
21794
- async function saveRelayRuntimeConfig(relayRuntimeConfigPath, openclawBaseUrl, openclawHookToken) {
21796
+ async function saveRelayRuntimeConfig(relayRuntimeConfigPath, openclawBaseUrl, openclawHookToken, relayTransformPeersPath) {
21795
21797
  const config2 = {
21796
21798
  openclawBaseUrl,
21797
21799
  ...openclawHookToken ? { openclawHookToken } : {},
21800
+ ...relayTransformPeersPath ? { relayTransformPeersPath } : {},
21798
21801
  updatedAt: nowIso()
21799
21802
  };
21800
21803
  await writeSecureFile3(
@@ -21841,33 +21844,40 @@ function normalizeStringArrayWithValues(value, requiredValues) {
21841
21844
  return Array.from(normalized);
21842
21845
  }
21843
21846
  function resolveHookDefaultSessionKey(config2, hooks) {
21844
- if (typeof hooks.defaultSessionKey === "string" && hooks.defaultSessionKey.trim().length > 0) {
21845
- return hooks.defaultSessionKey.trim();
21846
- }
21847
21847
  const session = isRecord8(config2.session) ? config2.session : {};
21848
21848
  const scope = typeof session.scope === "string" ? session.scope.trim().toLowerCase() : "";
21849
+ const configuredMainSessionKey = resolveConfiguredOpenclawMainSessionKey(session);
21850
+ if (typeof hooks.defaultSessionKey === "string" && hooks.defaultSessionKey.trim().length > 0) {
21851
+ return normalizeLegacyHookDefaultSessionKey(
21852
+ hooks.defaultSessionKey,
21853
+ configuredMainSessionKey
21854
+ );
21855
+ }
21849
21856
  if (scope === "global") {
21850
21857
  return "global";
21851
21858
  }
21852
- const agents = isRecord8(config2.agents) ? config2.agents : {};
21853
- const agentList = Array.isArray(agents.list) ? agents.list : [];
21854
- const defaultAgentId = resolveDefaultOpenclawAgentId(agentList);
21855
- return `agent:${defaultAgentId}:${DEFAULT_OPENCLAW_MAIN_SESSION_KEY}`;
21859
+ return configuredMainSessionKey;
21856
21860
  }
21857
- function resolveDefaultOpenclawAgentId(agentList) {
21858
- const preferred = agentList.find(
21859
- (agent) => isRecord8(agent) && agent.default === true && typeof agent.id === "string" && agent.id.trim().length > 0
21860
- ) ?? agentList.find(
21861
- (agent) => isRecord8(agent) && typeof agent.id === "string" && agent.id.trim().length > 0
21862
- );
21863
- if (isRecord8(preferred) && typeof preferred.id === "string" && preferred.id.trim().length > 0) {
21864
- return normalizeOpenclawIdToken(preferred.id, DEFAULT_OPENCLAW_AGENT_ID);
21861
+ function resolveConfiguredOpenclawMainSessionKey(session) {
21862
+ if (typeof session.mainKey === "string" && session.mainKey.trim().length > 0) {
21863
+ return session.mainKey.trim();
21865
21864
  }
21866
- return DEFAULT_OPENCLAW_AGENT_ID;
21865
+ return DEFAULT_OPENCLAW_MAIN_SESSION_KEY;
21867
21866
  }
21868
- function normalizeOpenclawIdToken(value, fallback) {
21869
- const normalized = value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+/g, "").replace(/-+$/g, "").slice(0, 64);
21870
- return normalized.length > 0 ? normalized : fallback;
21867
+ function normalizeLegacyHookDefaultSessionKey(value, fallbackSessionKey) {
21868
+ const trimmed = value.trim();
21869
+ const legacyMatch = /^agent:[^:]+:(.+)$/i.exec(trimmed);
21870
+ if (!legacyMatch) {
21871
+ return trimmed;
21872
+ }
21873
+ const routedSessionKey = legacyMatch[1]?.trim();
21874
+ if (typeof routedSessionKey === "string" && routedSessionKey.length > 0) {
21875
+ return routedSessionKey;
21876
+ }
21877
+ return fallbackSessionKey;
21878
+ }
21879
+ function isCanonicalAgentSessionKey(value) {
21880
+ return /^agent:[^:]+:.+/i.test(value.trim());
21871
21881
  }
21872
21882
  function generateOpenclawHookToken() {
21873
21883
  return randomBytes3(OPENCLAW_HOOK_TOKEN_BYTES).toString("hex");
@@ -22051,103 +22061,105 @@ async function runOpenclawDoctor(options = {}) {
22051
22061
  const openclawDir = resolveOpenclawDir(options.openclawDir, homeDir);
22052
22062
  const peerAlias = parseDoctorPeerAlias(options.peerAlias);
22053
22063
  const checks = [];
22054
- const resolveConfigImpl = options.resolveConfigImpl ?? resolveConfig;
22055
- try {
22056
- const resolvedConfig = await resolveConfigImpl();
22057
- const envProxyUrl = typeof process.env.CLAWDENTITY_PROXY_URL === "string" ? process.env.CLAWDENTITY_PROXY_URL.trim() : "";
22058
- if (typeof resolvedConfig.registryUrl !== "string" || resolvedConfig.registryUrl.trim().length === 0) {
22059
- checks.push(
22060
- toDoctorCheck({
22061
- id: "config.registry",
22062
- label: "CLI config",
22063
- status: "fail",
22064
- message: "registryUrl is missing",
22065
- remediationHint: "Run: clawdentity config set registryUrl <REGISTRY_URL>"
22066
- })
22067
- );
22068
- } else if (typeof resolvedConfig.apiKey !== "string" || resolvedConfig.apiKey.trim().length === 0) {
22069
- checks.push(
22070
- toDoctorCheck({
22071
- id: "config.registry",
22072
- label: "CLI config",
22073
- status: "fail",
22074
- message: "apiKey is missing",
22075
- remediationHint: "Run: clawdentity config set apiKey <API_KEY>"
22076
- })
22077
- );
22078
- } else if (envProxyUrl.length > 0) {
22079
- let hasValidEnvProxyUrl = true;
22080
- try {
22081
- parseProxyUrl(envProxyUrl);
22082
- } catch {
22083
- hasValidEnvProxyUrl = false;
22064
+ if (options.includeConfigCheck !== false) {
22065
+ const resolveConfigImpl = options.resolveConfigImpl ?? resolveConfig;
22066
+ try {
22067
+ const resolvedConfig = await resolveConfigImpl();
22068
+ const envProxyUrl = typeof process.env.CLAWDENTITY_PROXY_URL === "string" ? process.env.CLAWDENTITY_PROXY_URL.trim() : "";
22069
+ if (typeof resolvedConfig.registryUrl !== "string" || resolvedConfig.registryUrl.trim().length === 0) {
22084
22070
  checks.push(
22085
22071
  toDoctorCheck({
22086
22072
  id: "config.registry",
22087
22073
  label: "CLI config",
22088
22074
  status: "fail",
22089
- message: "CLAWDENTITY_PROXY_URL is invalid",
22090
- remediationHint: "Set CLAWDENTITY_PROXY_URL to a valid http(s) URL or unset it"
22075
+ message: "registryUrl is missing",
22076
+ remediationHint: "Run: clawdentity config set registryUrl <REGISTRY_URL>"
22091
22077
  })
22092
22078
  );
22093
- }
22094
- if (hasValidEnvProxyUrl) {
22079
+ } else if (typeof resolvedConfig.apiKey !== "string" || resolvedConfig.apiKey.trim().length === 0) {
22095
22080
  checks.push(
22096
22081
  toDoctorCheck({
22097
22082
  id: "config.registry",
22098
22083
  label: "CLI config",
22099
- status: "pass",
22100
- message: "registryUrl and apiKey are configured (proxy URL override is active via CLAWDENTITY_PROXY_URL)"
22084
+ status: "fail",
22085
+ message: "apiKey is missing",
22086
+ remediationHint: "Run: clawdentity config set apiKey <API_KEY>"
22101
22087
  })
22102
22088
  );
22103
- }
22104
- } else if (typeof resolvedConfig.proxyUrl !== "string" || resolvedConfig.proxyUrl.trim().length === 0) {
22105
- checks.push(
22106
- toDoctorCheck({
22107
- id: "config.registry",
22108
- label: "CLI config",
22109
- status: "fail",
22110
- message: "proxyUrl is missing",
22111
- remediationHint: "Run: clawdentity invite redeem <clw_inv_...> or clawdentity config init"
22112
- })
22113
- );
22114
- } else {
22115
- let hasValidConfigProxyUrl = true;
22116
- try {
22117
- parseProxyUrl(resolvedConfig.proxyUrl);
22118
- } catch {
22119
- hasValidConfigProxyUrl = false;
22089
+ } else if (envProxyUrl.length > 0) {
22090
+ let hasValidEnvProxyUrl = true;
22091
+ try {
22092
+ parseProxyUrl(envProxyUrl);
22093
+ } catch {
22094
+ hasValidEnvProxyUrl = false;
22095
+ checks.push(
22096
+ toDoctorCheck({
22097
+ id: "config.registry",
22098
+ label: "CLI config",
22099
+ status: "fail",
22100
+ message: "CLAWDENTITY_PROXY_URL is invalid",
22101
+ remediationHint: "Set CLAWDENTITY_PROXY_URL to a valid http(s) URL or unset it"
22102
+ })
22103
+ );
22104
+ }
22105
+ if (hasValidEnvProxyUrl) {
22106
+ checks.push(
22107
+ toDoctorCheck({
22108
+ id: "config.registry",
22109
+ label: "CLI config",
22110
+ status: "pass",
22111
+ message: "registryUrl and apiKey are configured (proxy URL override is active via CLAWDENTITY_PROXY_URL)"
22112
+ })
22113
+ );
22114
+ }
22115
+ } else if (typeof resolvedConfig.proxyUrl !== "string" || resolvedConfig.proxyUrl.trim().length === 0) {
22120
22116
  checks.push(
22121
22117
  toDoctorCheck({
22122
22118
  id: "config.registry",
22123
22119
  label: "CLI config",
22124
22120
  status: "fail",
22125
- message: "proxyUrl is invalid",
22121
+ message: "proxyUrl is missing",
22126
22122
  remediationHint: "Run: clawdentity invite redeem <clw_inv_...> or clawdentity config init"
22127
22123
  })
22128
22124
  );
22125
+ } else {
22126
+ let hasValidConfigProxyUrl = true;
22127
+ try {
22128
+ parseProxyUrl(resolvedConfig.proxyUrl);
22129
+ } catch {
22130
+ hasValidConfigProxyUrl = false;
22131
+ checks.push(
22132
+ toDoctorCheck({
22133
+ id: "config.registry",
22134
+ label: "CLI config",
22135
+ status: "fail",
22136
+ message: "proxyUrl is invalid",
22137
+ remediationHint: "Run: clawdentity invite redeem <clw_inv_...> or clawdentity config init"
22138
+ })
22139
+ );
22140
+ }
22141
+ if (hasValidConfigProxyUrl) {
22142
+ checks.push(
22143
+ toDoctorCheck({
22144
+ id: "config.registry",
22145
+ label: "CLI config",
22146
+ status: "pass",
22147
+ message: "registryUrl, apiKey, and proxyUrl are configured"
22148
+ })
22149
+ );
22150
+ }
22129
22151
  }
22130
- if (hasValidConfigProxyUrl) {
22131
- checks.push(
22132
- toDoctorCheck({
22133
- id: "config.registry",
22134
- label: "CLI config",
22135
- status: "pass",
22136
- message: "registryUrl, apiKey, and proxyUrl are configured"
22137
- })
22138
- );
22139
- }
22152
+ } catch {
22153
+ checks.push(
22154
+ toDoctorCheck({
22155
+ id: "config.registry",
22156
+ label: "CLI config",
22157
+ status: "fail",
22158
+ message: "unable to resolve CLI config",
22159
+ remediationHint: "Fix ~/.clawdentity/config.json or rerun: clawdentity config init"
22160
+ })
22161
+ );
22140
22162
  }
22141
- } catch {
22142
- checks.push(
22143
- toDoctorCheck({
22144
- id: "config.registry",
22145
- label: "CLI config",
22146
- status: "fail",
22147
- message: "unable to resolve CLI config",
22148
- remediationHint: "Fix ~/.clawdentity/config.json or rerun: clawdentity config init"
22149
- })
22150
- );
22151
22163
  }
22152
22164
  const selectedAgentPath = resolveOpenclawAgentNamePath(homeDir);
22153
22165
  let selectedAgentName;
@@ -22336,6 +22348,15 @@ async function runOpenclawDoctor(options = {}) {
22336
22348
  const hooks = isRecord8(openclawConfig.hooks) ? openclawConfig.hooks : {};
22337
22349
  const hooksEnabled = hooks.enabled === true;
22338
22350
  const hookToken = typeof hooks.token === "string" && hooks.token.trim().length > 0 ? hooks.token.trim() : void 0;
22351
+ const defaultSessionKey = typeof hooks.defaultSessionKey === "string" && hooks.defaultSessionKey.trim().length > 0 ? hooks.defaultSessionKey.trim() : void 0;
22352
+ const allowRequestSessionKey = hooks.allowRequestSessionKey === false;
22353
+ const allowedSessionKeyPrefixes = normalizeStringArrayWithValues(
22354
+ hooks.allowedSessionKeyPrefixes,
22355
+ []
22356
+ );
22357
+ const missingRequiredSessionPrefixes = defaultSessionKey === void 0 ? ["hook:"] : ["hook:", defaultSessionKey].filter(
22358
+ (prefix) => !allowedSessionKeyPrefixes.includes(prefix)
22359
+ );
22339
22360
  const mappings = Array.isArray(hooks.mappings) ? hooks.mappings.filter(isRecord8) : [];
22340
22361
  const relayMapping = mappings.find(
22341
22362
  (mapping) => isRelayHookMapping(mapping)
@@ -22395,6 +22416,45 @@ async function runOpenclawDoctor(options = {}) {
22395
22416
  })
22396
22417
  );
22397
22418
  }
22419
+ const sessionRoutingIssues = [];
22420
+ if (defaultSessionKey === void 0) {
22421
+ sessionRoutingIssues.push("hooks.defaultSessionKey is missing");
22422
+ }
22423
+ if (!allowRequestSessionKey) {
22424
+ sessionRoutingIssues.push("hooks.allowRequestSessionKey is not false");
22425
+ }
22426
+ if (missingRequiredSessionPrefixes.length > 0) {
22427
+ sessionRoutingIssues.push(
22428
+ `hooks.allowedSessionKeyPrefixes is missing: ${missingRequiredSessionPrefixes.join(", ")}`
22429
+ );
22430
+ }
22431
+ if (defaultSessionKey !== void 0 && isCanonicalAgentSessionKey(defaultSessionKey)) {
22432
+ sessionRoutingIssues.push(
22433
+ "hooks.defaultSessionKey uses canonical agent format (agent:<id>:...); use OpenClaw request session keys like main, global, or subagent:*"
22434
+ );
22435
+ }
22436
+ if (sessionRoutingIssues.length > 0) {
22437
+ checks.push(
22438
+ toDoctorCheck({
22439
+ id: "state.hookSessionRouting",
22440
+ label: "OpenClaw hook session routing",
22441
+ status: "fail",
22442
+ message: sessionRoutingIssues.join("; "),
22443
+ remediationHint: OPENCLAW_SETUP_RESTART_COMMAND_HINT,
22444
+ details: { openclawConfigPath }
22445
+ })
22446
+ );
22447
+ } else {
22448
+ checks.push(
22449
+ toDoctorCheck({
22450
+ id: "state.hookSessionRouting",
22451
+ label: "OpenClaw hook session routing",
22452
+ status: "pass",
22453
+ message: "hooks default session and allowed session prefixes are configured",
22454
+ details: { openclawConfigPath }
22455
+ })
22456
+ );
22457
+ }
22398
22458
  } catch {
22399
22459
  checks.push(
22400
22460
  toDoctorCheck({
@@ -22416,6 +22476,16 @@ async function runOpenclawDoctor(options = {}) {
22416
22476
  details: { openclawConfigPath }
22417
22477
  })
22418
22478
  );
22479
+ checks.push(
22480
+ toDoctorCheck({
22481
+ id: "state.hookSessionRouting",
22482
+ label: "OpenClaw hook session routing",
22483
+ status: "fail",
22484
+ message: `unable to read ${openclawConfigPath}`,
22485
+ remediationHint: "Ensure the OpenClaw config file exists (OPENCLAW_CONFIG_PATH/CLAWDBOT_CONFIG_PATH, or state dir) and rerun openclaw setup",
22486
+ details: { openclawConfigPath }
22487
+ })
22488
+ );
22419
22489
  }
22420
22490
  const relayRuntimeConfigPath = resolveRelayRuntimeConfigPath(homeDir);
22421
22491
  try {
@@ -22441,6 +22511,72 @@ async function runOpenclawDoctor(options = {}) {
22441
22511
  })
22442
22512
  );
22443
22513
  }
22514
+ const gatewayDevicePendingPath = join6(openclawDir, "devices", "pending.json");
22515
+ try {
22516
+ const pendingPayload = await readJsonFile(gatewayDevicePendingPath);
22517
+ if (!isRecord8(pendingPayload)) {
22518
+ checks.push(
22519
+ toDoctorCheck({
22520
+ id: "state.gatewayDevicePairing",
22521
+ label: "OpenClaw gateway device pairing",
22522
+ status: "fail",
22523
+ message: `invalid pending device approvals file: ${gatewayDevicePendingPath}`,
22524
+ remediationHint: OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT,
22525
+ details: { gatewayDevicePendingPath }
22526
+ })
22527
+ );
22528
+ } else {
22529
+ const pendingRequestIds = Object.keys(pendingPayload);
22530
+ if (pendingRequestIds.length === 0) {
22531
+ checks.push(
22532
+ toDoctorCheck({
22533
+ id: "state.gatewayDevicePairing",
22534
+ label: "OpenClaw gateway device pairing",
22535
+ status: "pass",
22536
+ message: "no pending gateway device approvals",
22537
+ details: { gatewayDevicePendingPath }
22538
+ })
22539
+ );
22540
+ } else {
22541
+ checks.push(
22542
+ toDoctorCheck({
22543
+ id: "state.gatewayDevicePairing",
22544
+ label: "OpenClaw gateway device pairing",
22545
+ status: "fail",
22546
+ message: `pending gateway device approvals: ${pendingRequestIds.length}`,
22547
+ remediationHint: OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT,
22548
+ details: {
22549
+ gatewayDevicePendingPath,
22550
+ pendingRequestIds
22551
+ }
22552
+ })
22553
+ );
22554
+ }
22555
+ }
22556
+ } catch (error48) {
22557
+ if (getErrorCode2(error48) === "ENOENT") {
22558
+ checks.push(
22559
+ toDoctorCheck({
22560
+ id: "state.gatewayDevicePairing",
22561
+ label: "OpenClaw gateway device pairing",
22562
+ status: "pass",
22563
+ message: "no pending gateway device approvals file was found",
22564
+ details: { gatewayDevicePendingPath }
22565
+ })
22566
+ );
22567
+ } else {
22568
+ checks.push(
22569
+ toDoctorCheck({
22570
+ id: "state.gatewayDevicePairing",
22571
+ label: "OpenClaw gateway device pairing",
22572
+ status: "fail",
22573
+ message: `unable to read pending device approvals at ${gatewayDevicePendingPath}`,
22574
+ remediationHint: OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT,
22575
+ details: { gatewayDevicePendingPath }
22576
+ })
22577
+ );
22578
+ }
22579
+ }
22444
22580
  if (options.includeConnectorRuntimeCheck !== false) {
22445
22581
  if (selectedAgentName === void 0) {
22446
22582
  checks.push(
@@ -22803,7 +22939,8 @@ async function setupOpenclawRelay(agentName, options) {
22803
22939
  await saveRelayRuntimeConfig(
22804
22940
  relayRuntimeConfigPath,
22805
22941
  openclawBaseUrl,
22806
- patchedOpenclawConfig.hookToken
22942
+ patchedOpenclawConfig.hookToken,
22943
+ relayTransformPeersPath
22807
22944
  );
22808
22945
  logger8.info("cli.openclaw_setup_completed", {
22809
22946
  agentName: normalizedAgentName,
@@ -22825,9 +22962,45 @@ async function setupOpenclawRelay(agentName, options) {
22825
22962
  relayRuntimeConfigPath
22826
22963
  };
22827
22964
  }
22965
+ async function assertSetupChecklistHealthy(input) {
22966
+ const checklist = await runOpenclawDoctor({
22967
+ homeDir: input.homeDir,
22968
+ openclawDir: input.openclawDir,
22969
+ includeConfigCheck: false,
22970
+ includeConnectorRuntimeCheck: input.includeConnectorRuntimeCheck
22971
+ });
22972
+ if (checklist.status === "healthy") {
22973
+ return;
22974
+ }
22975
+ const firstFailure = checklist.checks.find((check2) => check2.status === "fail");
22976
+ throw createCliError6(
22977
+ "CLI_OPENCLAW_SETUP_CHECKLIST_FAILED",
22978
+ "OpenClaw setup checklist failed",
22979
+ {
22980
+ firstFailedCheckId: firstFailure?.id,
22981
+ firstFailedCheckMessage: firstFailure?.message,
22982
+ remediationHint: firstFailure?.remediationHint,
22983
+ checks: checklist.checks
22984
+ }
22985
+ );
22986
+ }
22828
22987
  async function setupOpenclawSelfReady(agentName, options) {
22829
- const setup = await setupOpenclawRelay(agentName, options);
22988
+ const resolvedHomeDir = resolveHomeDir(options.homeDir);
22989
+ const resolvedOpenclawDir = resolveOpenclawDir(
22990
+ options.openclawDir,
22991
+ resolvedHomeDir
22992
+ );
22993
+ const setup = await setupOpenclawRelay(agentName, {
22994
+ ...options,
22995
+ homeDir: resolvedHomeDir,
22996
+ openclawDir: resolvedOpenclawDir
22997
+ });
22830
22998
  if (options.noRuntimeStart === true) {
22999
+ await assertSetupChecklistHealthy({
23000
+ homeDir: resolvedHomeDir,
23001
+ openclawDir: resolvedOpenclawDir,
23002
+ includeConnectorRuntimeCheck: false
23003
+ });
22831
23004
  return {
22832
23005
  ...setup,
22833
23006
  runtimeMode: "none",
@@ -22846,7 +23019,6 @@ async function setupOpenclawSelfReady(agentName, options) {
22846
23019
  const waitTimeoutSeconds = parseWaitTimeoutSeconds(
22847
23020
  options.waitTimeoutSeconds
22848
23021
  );
22849
- const resolvedHomeDir = resolveHomeDir(options.homeDir);
22850
23022
  const runtime = await startSetupConnectorRuntime({
22851
23023
  agentName: assertValidAgentName(agentName),
22852
23024
  homeDir: resolvedHomeDir,
@@ -22856,6 +23028,11 @@ async function setupOpenclawSelfReady(agentName, options) {
22856
23028
  waitTimeoutSeconds,
22857
23029
  fetchImpl
22858
23030
  });
23031
+ await assertSetupChecklistHealthy({
23032
+ homeDir: resolvedHomeDir,
23033
+ openclawDir: resolvedOpenclawDir,
23034
+ includeConnectorRuntimeCheck: true
23035
+ });
22859
23036
  return {
22860
23037
  ...setup,
22861
23038
  ...runtime
@@ -23001,6 +23178,7 @@ var AIT_FILE_NAME4 = "ait.jwt";
23001
23178
  var SECRET_KEY_FILE_NAME3 = "secret.key";
23002
23179
  var PAIRING_QR_DIR_NAME = "pairing";
23003
23180
  var PEERS_FILE_NAME2 = "peers.json";
23181
+ var OPENCLAW_RELAY_RUNTIME_FILE_NAME3 = "openclaw-relay.json";
23004
23182
  var PAIR_START_PATH = "/pair/start";
23005
23183
  var PAIR_CONFIRM_PATH = "/pair/confirm";
23006
23184
  var PAIR_STATUS_PATH = "/pair/status";
@@ -23303,6 +23481,86 @@ async function savePeersConfig2(input) {
23303
23481
  );
23304
23482
  await input.chmodImpl(peersPath, FILE_MODE4);
23305
23483
  }
23484
+ function resolveRelayRuntimeConfigPath2(getConfigDirImpl) {
23485
+ return join7(getConfigDirImpl(), OPENCLAW_RELAY_RUNTIME_FILE_NAME3);
23486
+ }
23487
+ async function loadRelayTransformPeersPath(input) {
23488
+ const relayRuntimeConfigPath = resolveRelayRuntimeConfigPath2(
23489
+ input.getConfigDirImpl
23490
+ );
23491
+ let raw;
23492
+ try {
23493
+ raw = await input.readFileImpl(relayRuntimeConfigPath, "utf8");
23494
+ } catch (error48) {
23495
+ const nodeError = error48;
23496
+ if (nodeError.code === "ENOENT") {
23497
+ return void 0;
23498
+ }
23499
+ logger9.warn("cli.pair.relay_runtime_read_failed", {
23500
+ relayRuntimeConfigPath,
23501
+ reason: error48 instanceof Error && error48.message.length > 0 ? error48.message : "unknown"
23502
+ });
23503
+ return void 0;
23504
+ }
23505
+ let parsed;
23506
+ try {
23507
+ parsed = JSON.parse(raw);
23508
+ } catch {
23509
+ logger9.warn("cli.pair.relay_runtime_invalid_json", {
23510
+ relayRuntimeConfigPath
23511
+ });
23512
+ return void 0;
23513
+ }
23514
+ if (!isRecord9(parsed)) {
23515
+ return void 0;
23516
+ }
23517
+ const relayTransformPeersPath = parseNonEmptyString9(
23518
+ parsed.relayTransformPeersPath
23519
+ );
23520
+ if (relayTransformPeersPath.length === 0) {
23521
+ return void 0;
23522
+ }
23523
+ return resolve(relayTransformPeersPath);
23524
+ }
23525
+ async function syncOpenclawRelayPeersSnapshot(input) {
23526
+ const relayTransformPeersPath = await loadRelayTransformPeersPath({
23527
+ getConfigDirImpl: input.getConfigDirImpl,
23528
+ readFileImpl: input.readFileImpl
23529
+ });
23530
+ if (relayTransformPeersPath === void 0) {
23531
+ return;
23532
+ }
23533
+ try {
23534
+ await input.readFileImpl(relayTransformPeersPath, "utf8");
23535
+ } catch (error48) {
23536
+ const nodeError = error48;
23537
+ if (nodeError.code === "ENOENT") {
23538
+ return;
23539
+ }
23540
+ logger9.warn("cli.pair.relay_peers_snapshot_probe_failed", {
23541
+ relayTransformPeersPath,
23542
+ reason: error48 instanceof Error && error48.message.length > 0 ? error48.message : "unknown"
23543
+ });
23544
+ return;
23545
+ }
23546
+ try {
23547
+ await input.mkdirImpl(dirname5(relayTransformPeersPath), {
23548
+ recursive: true
23549
+ });
23550
+ await input.writeFileImpl(
23551
+ relayTransformPeersPath,
23552
+ `${JSON.stringify(input.config, null, 2)}
23553
+ `,
23554
+ "utf8"
23555
+ );
23556
+ await input.chmodImpl(relayTransformPeersPath, FILE_MODE4);
23557
+ } catch (error48) {
23558
+ logger9.warn("cli.pair.relay_peers_snapshot_write_failed", {
23559
+ relayTransformPeersPath,
23560
+ reason: error48 instanceof Error && error48.message.length > 0 ? error48.message : "unknown"
23561
+ });
23562
+ }
23563
+ }
23306
23564
  function parseTtlSeconds(value) {
23307
23565
  const raw = parseNonEmptyString9(value);
23308
23566
  if (raw.length === 0) {
@@ -23828,6 +24086,14 @@ async function persistPairedPeer(input) {
23828
24086
  writeFileImpl,
23829
24087
  chmodImpl
23830
24088
  });
24089
+ await syncOpenclawRelayPeersSnapshot({
24090
+ config: peersConfig,
24091
+ getConfigDirImpl,
24092
+ readFileImpl,
24093
+ mkdirImpl,
24094
+ writeFileImpl,
24095
+ chmodImpl
24096
+ });
23831
24097
  return alias;
23832
24098
  }
23833
24099
  async function startPairing(agentName, options, dependencies = {}) {
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdentity",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -21,6 +21,17 @@
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
+ },
24
35
  "dependencies": {
25
36
  "commander": "^13.1.0",
26
37
  "jsqr": "^1.4.0",
@@ -29,21 +40,11 @@
29
40
  "ws": "^8.19.0"
30
41
  },
31
42
  "devDependencies": {
43
+ "@clawdentity/connector": "workspace:*",
44
+ "@clawdentity/protocol": "workspace:*",
45
+ "@clawdentity/sdk": "workspace:*",
32
46
  "@types/node": "^22.18.11",
33
47
  "@types/pngjs": "^6.0.5",
34
- "@types/qrcode": "^1.5.6",
35
- "@clawdentity/connector": "0.0.0",
36
- "@clawdentity/protocol": "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"
48
+ "@types/qrcode": "^1.5.6"
48
49
  }
49
- }
50
+ }
@@ -196,6 +196,7 @@ Note: Registry operators must run `admin bootstrap` before creating invites. See
196
196
  - OpenClaw config path and relay runtime path
197
197
  - runtime mode/status
198
198
  - websocket status `connected`
199
+ - setup checklist is healthy (fails fast when hook/device/runtime prerequisites drift)
199
200
 
200
201
  7. Validate readiness.
201
202
  - Run `clawdentity openclaw doctor`.
@@ -212,6 +213,8 @@ Note: Registry operators must run `admin bootstrap` before creating invites. See
212
213
  | `state.transform` | Relay transform artifacts in OpenClaw hooks dir | Reinstall skill package or `openclaw setup <agent-name>` |
213
214
  | `state.hookMapping` | `send-to-peer` hook mapping in OpenClaw config | `clawdentity openclaw setup <agent-name>` |
214
215
  | `state.hookToken` | Hooks enabled with token in OpenClaw config | `clawdentity openclaw setup <agent-name>` then restart OpenClaw |
216
+ | `state.hookSessionRouting` | `hooks.defaultSessionKey`, `hooks.allowRequestSessionKey=false`, and required prefixes (`hook:`, default session key) | `clawdentity openclaw setup <agent-name>` then restart OpenClaw |
217
+ | `state.gatewayDevicePairing` | Pending OpenClaw device approvals (prevents `pairing required` websocket errors) | `openclaw devices list` then `openclaw devices approve <requestId>` |
215
218
  | `state.openclawBaseUrl` | OpenClaw base URL resolvable | `clawdentity openclaw setup <agent-name> --openclaw-base-url <url>` |
216
219
  | `state.connectorRuntime` | Local connector runtime reachable and websocket-connected | `clawdentity openclaw setup <agent-name>` |
217
220