clawdentity 0.0.3 → 0.0.5

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
@@ -19577,6 +19577,8 @@ var IDENTITY_FILE_NAME2 = "identity.json";
19577
19577
  var AIT_FILE_NAME2 = "ait.jwt";
19578
19578
  var SECRET_KEY_FILE_NAME = "secret.key";
19579
19579
  var REGISTRY_AUTH_FILE_NAME2 = "registry-auth.json";
19580
+ var OPENCLAW_RELAY_RUNTIME_FILE_NAME = "openclaw-relay.json";
19581
+ var OPENCLAW_CONNECTORS_FILE_NAME = "openclaw-connectors.json";
19580
19582
  var SERVICE_LOG_DIR_NAME = "logs";
19581
19583
  var DEFAULT_CONNECTOR_BASE_URL2 = "http://127.0.0.1:19400";
19582
19584
  var DEFAULT_CONNECTOR_OUTBOUND_PATH2 = "/v1/outbound";
@@ -19660,13 +19662,43 @@ function normalizeOutboundPath2(pathValue) {
19660
19662
  }
19661
19663
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
19662
19664
  }
19663
- function resolveConnectorBaseUrl() {
19665
+ function resolveConnectorBaseUrlFromEnv() {
19664
19666
  const value = process.env.CLAWDENTITY_CONNECTOR_BASE_URL;
19665
19667
  if (typeof value !== "string" || value.trim().length === 0) {
19666
- return DEFAULT_CONNECTOR_BASE_URL2;
19668
+ return void 0;
19667
19669
  }
19668
19670
  return parseConnectorBaseUrl(value.trim());
19669
19671
  }
19672
+ async function readConnectorAssignedBaseUrl(configDir, agentName, readFileImpl) {
19673
+ const assignmentsPath = join5(configDir, OPENCLAW_CONNECTORS_FILE_NAME);
19674
+ let raw;
19675
+ try {
19676
+ raw = await readFileImpl(assignmentsPath, "utf8");
19677
+ } catch (error48) {
19678
+ if (getErrorCode(error48) === "ENOENT") {
19679
+ return void 0;
19680
+ }
19681
+ throw error48;
19682
+ }
19683
+ let parsed;
19684
+ try {
19685
+ parsed = JSON.parse(raw);
19686
+ } catch {
19687
+ throw createCliError3(
19688
+ "CLI_CONNECTOR_INVALID_ASSIGNMENTS",
19689
+ "Connector assignments config is invalid JSON",
19690
+ { assignmentsPath }
19691
+ );
19692
+ }
19693
+ if (!isRecord5(parsed) || !isRecord5(parsed.agents)) {
19694
+ return void 0;
19695
+ }
19696
+ const entry = parsed.agents[agentName];
19697
+ if (!isRecord5(entry) || typeof entry.connectorBaseUrl !== "string") {
19698
+ return void 0;
19699
+ }
19700
+ return parseConnectorBaseUrl(entry.connectorBaseUrl);
19701
+ }
19670
19702
  function resolveConnectorOutboundPath() {
19671
19703
  const value = process.env.CLAWDENTITY_CONNECTOR_OUTBOUND_PATH;
19672
19704
  if (typeof value !== "string" || value.trim().length === 0) {
@@ -19701,6 +19733,34 @@ async function readRequiredTrimmedFile(filePath, label, readFileImpl) {
19701
19733
  }
19702
19734
  return trimmed;
19703
19735
  }
19736
+ async function readRelayRuntimeConfig(configDir, readFileImpl) {
19737
+ const filePath = join5(configDir, OPENCLAW_RELAY_RUNTIME_FILE_NAME);
19738
+ let raw;
19739
+ try {
19740
+ raw = await readFileImpl(filePath, "utf8");
19741
+ } catch (error48) {
19742
+ if (getErrorCode(error48) === "ENOENT") {
19743
+ return void 0;
19744
+ }
19745
+ throw error48;
19746
+ }
19747
+ let parsed;
19748
+ try {
19749
+ parsed = JSON.parse(raw);
19750
+ } catch {
19751
+ return void 0;
19752
+ }
19753
+ if (!isRecord5(parsed)) {
19754
+ return void 0;
19755
+ }
19756
+ const openclawHookToken = typeof parsed.openclawHookToken === "string" && parsed.openclawHookToken.trim().length > 0 ? parsed.openclawHookToken.trim() : void 0;
19757
+ if (!openclawHookToken) {
19758
+ return void 0;
19759
+ }
19760
+ return {
19761
+ openclawHookToken
19762
+ };
19763
+ }
19704
19764
  function parseJsonRecord(value, code, message2) {
19705
19765
  let parsed;
19706
19766
  try {
@@ -20090,6 +20150,8 @@ async function startConnectorForAgent(agentName, commandOptions = {}, dependenci
20090
20150
  rawSecretKey,
20091
20151
  rawIdentity,
20092
20152
  rawRegistryAuth,
20153
+ assignedConnectorBaseUrl,
20154
+ relayRuntimeConfig,
20093
20155
  config2,
20094
20156
  connectorModule
20095
20157
  ] = await Promise.all([
@@ -20113,6 +20175,8 @@ async function startConnectorForAgent(agentName, commandOptions = {}, dependenci
20113
20175
  REGISTRY_AUTH_FILE_NAME2,
20114
20176
  readFileImpl
20115
20177
  ),
20178
+ readConnectorAssignedBaseUrl(configDir, agentName, readFileImpl),
20179
+ readRelayRuntimeConfig(configDir, readFileImpl),
20116
20180
  resolveConfigImpl(),
20117
20181
  loadConnectorModule()
20118
20182
  ]);
@@ -20124,7 +20188,8 @@ async function startConnectorForAgent(agentName, commandOptions = {}, dependenci
20124
20188
  }
20125
20189
  const identity = parseAgentIdentity(rawIdentity);
20126
20190
  const registryAuth = parseRegistryAuth(rawRegistryAuth);
20127
- const outboundBaseUrl = resolveConnectorBaseUrl();
20191
+ const openclawHookToken = commandOptions.openclawHookToken ?? relayRuntimeConfig?.openclawHookToken;
20192
+ const outboundBaseUrl = resolveConnectorBaseUrlFromEnv() ?? assignedConnectorBaseUrl ?? DEFAULT_CONNECTOR_BASE_URL2;
20128
20193
  const outboundPath = resolveConnectorOutboundPath();
20129
20194
  const runtime = await connectorModule.startConnectorRuntime({
20130
20195
  agentName,
@@ -20135,7 +20200,7 @@ async function startConnectorForAgent(agentName, commandOptions = {}, dependenci
20135
20200
  proxyWebsocketUrl: commandOptions.proxyWsUrl,
20136
20201
  openclawBaseUrl: commandOptions.openclawBaseUrl,
20137
20202
  openclawHookPath: commandOptions.openclawHookPath,
20138
- openclawHookToken: commandOptions.openclawHookToken,
20203
+ openclawHookToken,
20139
20204
  credentials: {
20140
20205
  agentDid: identity.did,
20141
20206
  ait: rawAit,
@@ -20602,9 +20667,11 @@ var createInviteCommand = (dependencies = {}) => {
20602
20667
  };
20603
20668
 
20604
20669
  // src/commands/openclaw.ts
20670
+ import { randomBytes as randomBytes3 } from "crypto";
20671
+ import { existsSync } from "fs";
20605
20672
  import { chmod as chmod3, copyFile, mkdir as mkdir5, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
20606
20673
  import { homedir as homedir3 } from "os";
20607
- import { dirname as dirname4, join as join6 } from "path";
20674
+ import { dirname as dirname4, join as join6, resolve as resolvePath } from "path";
20608
20675
  import { Command as Command7 } from "commander";
20609
20676
  var logger8 = createLogger({ service: "cli", module: "openclaw" });
20610
20677
  var CLAWDENTITY_DIR_NAME = ".clawdentity";
@@ -20614,17 +20681,29 @@ var SECRET_KEY_FILE_NAME2 = "secret.key";
20614
20681
  var PEERS_FILE_NAME = "peers.json";
20615
20682
  var OPENCLAW_DIR_NAME = ".openclaw";
20616
20683
  var OPENCLAW_CONFIG_FILE_NAME = "openclaw.json";
20684
+ var LEGACY_OPENCLAW_STATE_DIR_NAMES = [".clawdbot", ".moldbot", ".moltbot"];
20685
+ var LEGACY_OPENCLAW_CONFIG_FILE_NAMES = ["clawdbot.json", "moldbot.json", "moltbot.json"];
20617
20686
  var OPENCLAW_AGENT_FILE_NAME = "openclaw-agent-name";
20618
- var OPENCLAW_RELAY_RUNTIME_FILE_NAME = "openclaw-relay.json";
20687
+ var OPENCLAW_RELAY_RUNTIME_FILE_NAME2 = "openclaw-relay.json";
20688
+ var OPENCLAW_CONNECTORS_FILE_NAME2 = "openclaw-connectors.json";
20619
20689
  var SKILL_DIR_NAME = "clawdentity-openclaw-relay";
20620
20690
  var RELAY_MODULE_FILE_NAME = "relay-to-peer.mjs";
20691
+ var RELAY_RUNTIME_FILE_NAME = "clawdentity-relay.json";
20692
+ var RELAY_PEERS_FILE_NAME = "clawdentity-peers.json";
20621
20693
  var HOOK_MAPPING_ID = "clawdentity-send-to-peer";
20622
20694
  var HOOK_PATH_SEND_TO_PEER = "send-to-peer";
20623
20695
  var OPENCLAW_SEND_TO_PEER_HOOK_PATH = "hooks/send-to-peer";
20624
20696
  var DEFAULT_OPENCLAW_BASE_URL2 = "http://127.0.0.1:18789";
20697
+ var DEFAULT_CONNECTOR_PORT = 19400;
20698
+ var DEFAULT_CONNECTOR_OUTBOUND_PATH3 = "/v1/outbound";
20699
+ var CONNECTOR_HOST_LOOPBACK = "127.0.0.1";
20700
+ var CONNECTOR_HOST_DOCKER = "host.docker.internal";
20701
+ var CONNECTOR_HOST_DOCKER_GATEWAY = "gateway.docker.internal";
20702
+ var CONNECTOR_HOST_LINUX_BRIDGE = "172.17.0.1";
20625
20703
  var INVITE_CODE_PREFIX = "clawd1_";
20626
20704
  var PEER_ALIAS_PATTERN = /^[a-zA-Z0-9._-]+$/;
20627
20705
  var FILE_MODE3 = 384;
20706
+ var OPENCLAW_HOOK_TOKEN_BYTES = 32;
20628
20707
  var textEncoder2 = new TextEncoder();
20629
20708
  var textDecoder = new TextDecoder();
20630
20709
  function isRecord7(value) {
@@ -20783,11 +20862,56 @@ function resolveHomeDir(homeDir) {
20783
20862
  }
20784
20863
  return homedir3();
20785
20864
  }
20865
+ function resolveHomePrefixedPath(input, homeDir) {
20866
+ const trimmed = input.trim();
20867
+ if (trimmed.startsWith("~")) {
20868
+ return resolvePath(trimmed.replace(/^~(?=$|[\\/])/, homeDir));
20869
+ }
20870
+ return resolvePath(trimmed);
20871
+ }
20872
+ function readNonEmptyEnvPath(value, homeDir) {
20873
+ if (typeof value !== "string" || value.trim().length === 0) {
20874
+ return void 0;
20875
+ }
20876
+ return resolveHomePrefixedPath(value, homeDir);
20877
+ }
20878
+ function resolveOpenclawHomeDir(homeDir) {
20879
+ const envOpenclawHome = readNonEmptyEnvPath(process.env.OPENCLAW_HOME, homeDir);
20880
+ return envOpenclawHome ?? homeDir;
20881
+ }
20882
+ function resolveDefaultOpenclawStateDir(openclawHomeDir) {
20883
+ const newStateDir = join6(openclawHomeDir, OPENCLAW_DIR_NAME);
20884
+ if (existsSync(newStateDir)) {
20885
+ return newStateDir;
20886
+ }
20887
+ for (const legacyDirName of LEGACY_OPENCLAW_STATE_DIR_NAMES) {
20888
+ const legacyStateDir = join6(openclawHomeDir, legacyDirName);
20889
+ if (existsSync(legacyStateDir)) {
20890
+ return legacyStateDir;
20891
+ }
20892
+ }
20893
+ return newStateDir;
20894
+ }
20786
20895
  function resolveOpenclawDir(openclawDir, homeDir) {
20787
20896
  if (typeof openclawDir === "string" && openclawDir.trim().length > 0) {
20788
- return openclawDir.trim();
20897
+ return resolveHomePrefixedPath(openclawDir, homeDir);
20898
+ }
20899
+ const envStateDir = readNonEmptyEnvPath(
20900
+ process.env.OPENCLAW_STATE_DIR ?? process.env.CLAWDBOT_STATE_DIR,
20901
+ homeDir
20902
+ );
20903
+ if (envStateDir !== void 0) {
20904
+ return envStateDir;
20905
+ }
20906
+ const envConfigPath = readNonEmptyEnvPath(
20907
+ process.env.OPENCLAW_CONFIG_PATH ?? process.env.CLAWDBOT_CONFIG_PATH,
20908
+ homeDir
20909
+ );
20910
+ if (envConfigPath !== void 0) {
20911
+ return dirname4(envConfigPath);
20789
20912
  }
20790
- return join6(homeDir, OPENCLAW_DIR_NAME);
20913
+ const openclawHomeDir = resolveOpenclawHomeDir(homeDir);
20914
+ return resolveDefaultOpenclawStateDir(openclawHomeDir);
20791
20915
  }
20792
20916
  function resolveAgentDirectory(homeDir, agentName) {
20793
20917
  return join6(homeDir, CLAWDENTITY_DIR_NAME, AGENTS_DIR_NAME4, agentName);
@@ -20795,8 +20919,26 @@ function resolveAgentDirectory(homeDir, agentName) {
20795
20919
  function resolvePeersPath(homeDir) {
20796
20920
  return join6(homeDir, CLAWDENTITY_DIR_NAME, PEERS_FILE_NAME);
20797
20921
  }
20798
- function resolveOpenclawConfigPath(openclawDir) {
20799
- return join6(openclawDir, OPENCLAW_CONFIG_FILE_NAME);
20922
+ function resolveOpenclawConfigPath(openclawDir, homeDir) {
20923
+ const envConfigPath = readNonEmptyEnvPath(
20924
+ process.env.OPENCLAW_CONFIG_PATH ?? process.env.CLAWDBOT_CONFIG_PATH,
20925
+ homeDir
20926
+ );
20927
+ if (envConfigPath !== void 0) {
20928
+ return envConfigPath;
20929
+ }
20930
+ const configCandidates = [
20931
+ join6(openclawDir, OPENCLAW_CONFIG_FILE_NAME),
20932
+ ...LEGACY_OPENCLAW_CONFIG_FILE_NAMES.map(
20933
+ (fileName) => join6(openclawDir, fileName)
20934
+ )
20935
+ ];
20936
+ for (const candidate of configCandidates) {
20937
+ if (existsSync(candidate)) {
20938
+ return candidate;
20939
+ }
20940
+ }
20941
+ return configCandidates[0];
20800
20942
  }
20801
20943
  function resolveDefaultTransformSource(openclawDir) {
20802
20944
  return join6(
@@ -20814,7 +20956,16 @@ function resolveOpenclawAgentNamePath(homeDir) {
20814
20956
  return join6(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_AGENT_FILE_NAME);
20815
20957
  }
20816
20958
  function resolveRelayRuntimeConfigPath(homeDir) {
20817
- return join6(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_RELAY_RUNTIME_FILE_NAME);
20959
+ return join6(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_RELAY_RUNTIME_FILE_NAME2);
20960
+ }
20961
+ function resolveConnectorAssignmentsPath(homeDir) {
20962
+ return join6(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_CONNECTORS_FILE_NAME2);
20963
+ }
20964
+ function resolveTransformRuntimePath(openclawDir) {
20965
+ return join6(openclawDir, "hooks", "transforms", RELAY_RUNTIME_FILE_NAME);
20966
+ }
20967
+ function resolveTransformPeersPath(openclawDir) {
20968
+ return join6(openclawDir, "hooks", "transforms", RELAY_PEERS_FILE_NAME);
20818
20969
  }
20819
20970
  async function readJsonFile(filePath) {
20820
20971
  const raw = await readFile4(filePath, "utf8");
@@ -20952,6 +21103,98 @@ async function savePeersConfig(peersPath, config2) {
20952
21103
  await writeSecureFile3(peersPath, `${JSON.stringify(config2, null, 2)}
20953
21104
  `);
20954
21105
  }
21106
+ function parseConnectorBaseUrlForAssignment(value, label) {
21107
+ return parseHttpUrl(value, {
21108
+ label,
21109
+ code: "CLI_OPENCLAW_INVALID_CONNECTOR_BASE_URL",
21110
+ message: "Connector base URL must be a valid URL"
21111
+ });
21112
+ }
21113
+ function parseConnectorAssignments(value, connectorAssignmentsPath) {
21114
+ if (!isRecord7(value)) {
21115
+ throw createCliError5(
21116
+ "CLI_OPENCLAW_INVALID_CONNECTOR_ASSIGNMENTS",
21117
+ "Connector assignments config must be an object",
21118
+ { connectorAssignmentsPath }
21119
+ );
21120
+ }
21121
+ const agentsRaw = value.agents;
21122
+ if (!isRecord7(agentsRaw)) {
21123
+ return { agents: {} };
21124
+ }
21125
+ const agents = {};
21126
+ for (const [agentName, entryValue] of Object.entries(agentsRaw)) {
21127
+ if (!isRecord7(entryValue)) {
21128
+ throw createCliError5(
21129
+ "CLI_OPENCLAW_INVALID_CONNECTOR_ASSIGNMENTS",
21130
+ "Connector assignment entry must be an object",
21131
+ { connectorAssignmentsPath, agentName }
21132
+ );
21133
+ }
21134
+ const connectorBaseUrl = parseConnectorBaseUrlForAssignment(
21135
+ entryValue.connectorBaseUrl,
21136
+ "connectorBaseUrl"
21137
+ );
21138
+ const updatedAt = typeof entryValue.updatedAt === "string" && entryValue.updatedAt.trim().length > 0 ? entryValue.updatedAt.trim() : nowIso();
21139
+ agents[assertValidAgentName(agentName)] = {
21140
+ connectorBaseUrl,
21141
+ updatedAt
21142
+ };
21143
+ }
21144
+ return { agents };
21145
+ }
21146
+ async function loadConnectorAssignments(connectorAssignmentsPath) {
21147
+ let parsed;
21148
+ try {
21149
+ parsed = await readJsonFile(connectorAssignmentsPath);
21150
+ } catch (error48) {
21151
+ if (getErrorCode2(error48) === "ENOENT") {
21152
+ return { agents: {} };
21153
+ }
21154
+ throw error48;
21155
+ }
21156
+ return parseConnectorAssignments(parsed, connectorAssignmentsPath);
21157
+ }
21158
+ async function saveConnectorAssignments(connectorAssignmentsPath, config2) {
21159
+ await writeSecureFile3(
21160
+ connectorAssignmentsPath,
21161
+ `${JSON.stringify(config2, null, 2)}
21162
+ `
21163
+ );
21164
+ }
21165
+ function parseConnectorPortFromBaseUrl(baseUrl) {
21166
+ const parsed = new URL(baseUrl);
21167
+ if (parsed.port) {
21168
+ return Number(parsed.port);
21169
+ }
21170
+ return parsed.protocol === "https:" ? 443 : 80;
21171
+ }
21172
+ function allocateConnectorPort(assignments, agentName) {
21173
+ const existing = assignments.agents[agentName];
21174
+ if (existing) {
21175
+ return parseConnectorPortFromBaseUrl(existing.connectorBaseUrl);
21176
+ }
21177
+ const usedPorts = /* @__PURE__ */ new Set();
21178
+ for (const entry of Object.values(assignments.agents)) {
21179
+ usedPorts.add(parseConnectorPortFromBaseUrl(entry.connectorBaseUrl));
21180
+ }
21181
+ let nextPort = DEFAULT_CONNECTOR_PORT;
21182
+ while (usedPorts.has(nextPort)) {
21183
+ nextPort += 1;
21184
+ }
21185
+ return nextPort;
21186
+ }
21187
+ function buildConnectorBaseUrl(host, port) {
21188
+ return `http://${host}:${port}`;
21189
+ }
21190
+ function buildRelayConnectorBaseUrls(port) {
21191
+ return [
21192
+ buildConnectorBaseUrl(CONNECTOR_HOST_DOCKER, port),
21193
+ buildConnectorBaseUrl(CONNECTOR_HOST_DOCKER_GATEWAY, port),
21194
+ buildConnectorBaseUrl(CONNECTOR_HOST_LINUX_BRIDGE, port),
21195
+ buildConnectorBaseUrl(CONNECTOR_HOST_LOOPBACK, port)
21196
+ ];
21197
+ }
20955
21198
  function parseRelayRuntimeConfig(value, relayRuntimeConfigPath) {
20956
21199
  if (!isRecord7(value)) {
20957
21200
  throw createCliError5(
@@ -20961,8 +21204,10 @@ function parseRelayRuntimeConfig(value, relayRuntimeConfigPath) {
20961
21204
  );
20962
21205
  }
20963
21206
  const updatedAt = typeof value.updatedAt === "string" && value.updatedAt.trim().length > 0 ? value.updatedAt.trim() : void 0;
21207
+ const openclawHookToken = typeof value.openclawHookToken === "string" && value.openclawHookToken.trim().length > 0 ? value.openclawHookToken.trim() : void 0;
20964
21208
  return {
20965
21209
  openclawBaseUrl: parseOpenclawBaseUrl(value.openclawBaseUrl),
21210
+ openclawHookToken,
20966
21211
  updatedAt
20967
21212
  };
20968
21213
  }
@@ -20978,9 +21223,10 @@ async function loadRelayRuntimeConfig(relayRuntimeConfigPath) {
20978
21223
  }
20979
21224
  return parseRelayRuntimeConfig(parsed, relayRuntimeConfigPath);
20980
21225
  }
20981
- async function saveRelayRuntimeConfig(relayRuntimeConfigPath, openclawBaseUrl) {
21226
+ async function saveRelayRuntimeConfig(relayRuntimeConfigPath, openclawBaseUrl, openclawHookToken) {
20982
21227
  const config2 = {
20983
21228
  openclawBaseUrl,
21229
+ ...openclawHookToken ? { openclawHookToken } : {},
20984
21230
  updatedAt: nowIso()
20985
21231
  };
20986
21232
  await writeSecureFile3(
@@ -21021,6 +21267,9 @@ function normalizeStringArrayWithValue(value, requiredValue) {
21021
21267
  normalized.add(requiredValue);
21022
21268
  return Array.from(normalized);
21023
21269
  }
21270
+ function generateOpenclawHookToken() {
21271
+ return randomBytes3(OPENCLAW_HOOK_TOKEN_BYTES).toString("hex");
21272
+ }
21024
21273
  function upsertRelayHookMapping(mappingsValue) {
21025
21274
  const mappings = Array.isArray(mappingsValue) ? mappingsValue.filter(isRecord7).map((mapping) => ({ ...mapping })) : [];
21026
21275
  const existingIndex = mappings.findIndex((mapping) => {
@@ -21052,7 +21301,7 @@ function upsertRelayHookMapping(mappingsValue) {
21052
21301
  mappings.push(relayMapping);
21053
21302
  return mappings;
21054
21303
  }
21055
- async function patchOpenclawConfig(openclawConfigPath) {
21304
+ async function patchOpenclawConfig(openclawConfigPath, hookToken) {
21056
21305
  let config2;
21057
21306
  try {
21058
21307
  config2 = await readJsonFile(openclawConfigPath);
@@ -21074,7 +21323,11 @@ async function patchOpenclawConfig(openclawConfigPath) {
21074
21323
  );
21075
21324
  }
21076
21325
  const hooks = isRecord7(config2.hooks) ? { ...config2.hooks } : {};
21326
+ const existingHookToken = typeof hooks.token === "string" && hooks.token.trim().length > 0 ? hooks.token.trim() : void 0;
21327
+ const preferredHookToken = typeof hookToken === "string" && hookToken.trim().length > 0 ? hookToken.trim() : void 0;
21328
+ const resolvedHookToken = existingHookToken ?? preferredHookToken ?? generateOpenclawHookToken();
21077
21329
  hooks.enabled = true;
21330
+ hooks.token = resolvedHookToken;
21078
21331
  hooks.allowRequestSessionKey = false;
21079
21332
  hooks.allowedSessionKeyPrefixes = normalizeStringArrayWithValue(
21080
21333
  hooks.allowedSessionKeyPrefixes,
@@ -21091,6 +21344,9 @@ async function patchOpenclawConfig(openclawConfigPath) {
21091
21344
  `,
21092
21345
  "utf8"
21093
21346
  );
21347
+ return {
21348
+ hookToken: resolvedHookToken
21349
+ };
21094
21350
  }
21095
21351
  function toDoctorCheck(input) {
21096
21352
  return input;
@@ -21126,8 +21382,8 @@ function parseDoctorPeerAlias(peerAlias) {
21126
21382
  }
21127
21383
  return parsePeerAlias(peerAlias);
21128
21384
  }
21129
- function resolveHookToken(optionValue) {
21130
- const trimmedOption = optionValue?.trim();
21385
+ async function resolveHookToken(input) {
21386
+ const trimmedOption = input.optionValue?.trim();
21131
21387
  if (trimmedOption !== void 0 && trimmedOption.length > 0) {
21132
21388
  return trimmedOption;
21133
21389
  }
@@ -21135,6 +21391,12 @@ function resolveHookToken(optionValue) {
21135
21391
  if (envValue !== void 0 && envValue.length > 0) {
21136
21392
  return envValue;
21137
21393
  }
21394
+ const existingConfig = await loadRelayRuntimeConfig(
21395
+ input.relayRuntimeConfigPath
21396
+ );
21397
+ if (existingConfig?.openclawHookToken) {
21398
+ return existingConfig.openclawHookToken;
21399
+ }
21138
21400
  return void 0;
21139
21401
  }
21140
21402
  function resolveProbeMessage(optionValue) {
@@ -21353,17 +21615,28 @@ async function runOpenclawDoctor(options = {}) {
21353
21615
  );
21354
21616
  }
21355
21617
  const transformTargetPath = resolveTransformTargetPath(openclawDir);
21618
+ const relayTransformRuntimePath = resolveTransformRuntimePath(openclawDir);
21619
+ const relayTransformPeersPath = resolveTransformPeersPath(openclawDir);
21356
21620
  try {
21357
21621
  const transformContents = await readFile4(transformTargetPath, "utf8");
21358
- if (transformContents.trim().length === 0) {
21622
+ const runtimeContents = await readFile4(relayTransformRuntimePath, "utf8");
21623
+ const peersSnapshotContents = await readFile4(
21624
+ relayTransformPeersPath,
21625
+ "utf8"
21626
+ );
21627
+ if (transformContents.trim().length === 0 || runtimeContents.trim().length === 0 || peersSnapshotContents.trim().length === 0) {
21359
21628
  checks.push(
21360
21629
  toDoctorCheck({
21361
21630
  id: "state.transform",
21362
21631
  label: "Relay transform",
21363
21632
  status: "fail",
21364
- message: `transform file is empty: ${transformTargetPath}`,
21633
+ message: "relay transform artifacts are missing or empty",
21365
21634
  remediationHint: "Run: npm install clawdentity --skill",
21366
- details: { transformTargetPath }
21635
+ details: {
21636
+ transformTargetPath,
21637
+ relayTransformRuntimePath,
21638
+ relayTransformPeersPath
21639
+ }
21367
21640
  })
21368
21641
  );
21369
21642
  } else {
@@ -21372,8 +21645,12 @@ async function runOpenclawDoctor(options = {}) {
21372
21645
  id: "state.transform",
21373
21646
  label: "Relay transform",
21374
21647
  status: "pass",
21375
- message: "relay transform file exists",
21376
- details: { transformTargetPath }
21648
+ message: "relay transform artifacts are present",
21649
+ details: {
21650
+ transformTargetPath,
21651
+ relayTransformRuntimePath,
21652
+ relayTransformPeersPath
21653
+ }
21377
21654
  })
21378
21655
  );
21379
21656
  }
@@ -21383,19 +21660,25 @@ async function runOpenclawDoctor(options = {}) {
21383
21660
  id: "state.transform",
21384
21661
  label: "Relay transform",
21385
21662
  status: "fail",
21386
- message: `missing transform file: ${transformTargetPath}`,
21663
+ message: "missing relay transform artifacts",
21387
21664
  remediationHint: "Run: npm install clawdentity --skill",
21388
- details: { transformTargetPath }
21665
+ details: {
21666
+ transformTargetPath,
21667
+ relayTransformRuntimePath,
21668
+ relayTransformPeersPath
21669
+ }
21389
21670
  })
21390
21671
  );
21391
21672
  }
21392
- const openclawConfigPath = resolveOpenclawConfigPath(openclawDir);
21673
+ const openclawConfigPath = resolveOpenclawConfigPath(openclawDir, homeDir);
21393
21674
  try {
21394
21675
  const openclawConfig = await readJsonFile(openclawConfigPath);
21395
21676
  if (!isRecord7(openclawConfig)) {
21396
21677
  throw new Error("root");
21397
21678
  }
21398
21679
  const hooks = isRecord7(openclawConfig.hooks) ? openclawConfig.hooks : {};
21680
+ const hooksEnabled = hooks.enabled === true;
21681
+ const hookToken = typeof hooks.token === "string" && hooks.token.trim().length > 0 ? hooks.token.trim() : void 0;
21399
21682
  const mappings = Array.isArray(hooks.mappings) ? hooks.mappings.filter(isRecord7) : [];
21400
21683
  const relayMapping = mappings.find(
21401
21684
  (mapping) => isRelayHookMapping(mapping)
@@ -21422,6 +21705,39 @@ async function runOpenclawDoctor(options = {}) {
21422
21705
  })
21423
21706
  );
21424
21707
  }
21708
+ if (!hooksEnabled) {
21709
+ checks.push(
21710
+ toDoctorCheck({
21711
+ id: "state.hookToken",
21712
+ label: "OpenClaw hook auth",
21713
+ status: "fail",
21714
+ message: `hooks.enabled is not true in ${openclawConfigPath}`,
21715
+ remediationHint: "Run: clawdentity openclaw setup <agentName> --invite-code <code> and restart OpenClaw",
21716
+ details: { openclawConfigPath }
21717
+ })
21718
+ );
21719
+ } else if (hookToken === void 0) {
21720
+ checks.push(
21721
+ toDoctorCheck({
21722
+ id: "state.hookToken",
21723
+ label: "OpenClaw hook auth",
21724
+ status: "fail",
21725
+ message: `hooks.token is missing in ${openclawConfigPath}`,
21726
+ remediationHint: "Run: clawdentity openclaw setup <agentName> --invite-code <code> and restart OpenClaw",
21727
+ details: { openclawConfigPath }
21728
+ })
21729
+ );
21730
+ } else {
21731
+ checks.push(
21732
+ toDoctorCheck({
21733
+ id: "state.hookToken",
21734
+ label: "OpenClaw hook auth",
21735
+ status: "pass",
21736
+ message: "hooks token is configured",
21737
+ details: { openclawConfigPath }
21738
+ })
21739
+ );
21740
+ }
21425
21741
  } catch {
21426
21742
  checks.push(
21427
21743
  toDoctorCheck({
@@ -21429,7 +21745,17 @@ async function runOpenclawDoctor(options = {}) {
21429
21745
  label: "OpenClaw hook mapping",
21430
21746
  status: "fail",
21431
21747
  message: `unable to read ${openclawConfigPath}`,
21432
- remediationHint: "Ensure ~/.openclaw/openclaw.json exists and rerun openclaw setup",
21748
+ remediationHint: "Ensure the OpenClaw config file exists (OPENCLAW_CONFIG_PATH/CLAWDBOT_CONFIG_PATH, or state dir) and rerun openclaw setup",
21749
+ details: { openclawConfigPath }
21750
+ })
21751
+ );
21752
+ checks.push(
21753
+ toDoctorCheck({
21754
+ id: "state.hookToken",
21755
+ label: "OpenClaw hook auth",
21756
+ status: "fail",
21757
+ message: `unable to read ${openclawConfigPath}`,
21758
+ remediationHint: "Ensure the OpenClaw config file exists (OPENCLAW_CONFIG_PATH/CLAWDBOT_CONFIG_PATH, or state dir) and rerun openclaw setup",
21433
21759
  details: { openclawConfigPath }
21434
21760
  })
21435
21761
  );
@@ -21473,6 +21799,12 @@ function parseRelayProbeFailure(input) {
21473
21799
  remediationHint: "Run: clawdentity openclaw setup <agentName> --invite-code <code>"
21474
21800
  };
21475
21801
  }
21802
+ if (input.status === 405) {
21803
+ return {
21804
+ message: "OpenClaw send-to-peer hook is not enabled for POST requests",
21805
+ remediationHint: "Run: clawdentity openclaw setup <agentName> --invite-code <code>, then restart OpenClaw"
21806
+ };
21807
+ }
21476
21808
  if (input.status === 500) {
21477
21809
  return {
21478
21810
  message: "Relay probe failed inside local relay pipeline",
@@ -21528,7 +21860,10 @@ async function runOpenclawRelayTest(options) {
21528
21860
  preflight
21529
21861
  };
21530
21862
  }
21531
- const hookToken = resolveHookToken(options.hookToken);
21863
+ const hookToken = await resolveHookToken({
21864
+ optionValue: options.hookToken,
21865
+ relayRuntimeConfigPath
21866
+ });
21532
21867
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
21533
21868
  if (typeof fetchImpl !== "function") {
21534
21869
  return {
@@ -21620,10 +21955,13 @@ async function setupOpenclawRelayFromInvite(agentName, options) {
21620
21955
  const normalizedAgentName = assertValidAgentName(agentName);
21621
21956
  const homeDir = resolveHomeDir(options.homeDir);
21622
21957
  const openclawDir = resolveOpenclawDir(options.openclawDir, homeDir);
21623
- const openclawConfigPath = resolveOpenclawConfigPath(openclawDir);
21958
+ const openclawConfigPath = resolveOpenclawConfigPath(openclawDir, homeDir);
21624
21959
  const transformSource = typeof options.transformSource === "string" && options.transformSource.trim().length > 0 ? options.transformSource.trim() : resolveDefaultTransformSource(openclawDir);
21625
21960
  const transformTargetPath = resolveTransformTargetPath(openclawDir);
21626
21961
  const relayRuntimeConfigPath = resolveRelayRuntimeConfigPath(homeDir);
21962
+ const existingRelayRuntimeConfig = await loadRelayRuntimeConfig(
21963
+ relayRuntimeConfigPath
21964
+ );
21627
21965
  const openclawBaseUrl = await resolveOpenclawBaseUrl2({
21628
21966
  optionValue: options.openclawBaseUrl,
21629
21967
  relayRuntimeConfigPath
@@ -21651,22 +21989,75 @@ async function setupOpenclawRelayFromInvite(agentName, options) {
21651
21989
  }
21652
21990
  throw error48;
21653
21991
  }
21654
- await patchOpenclawConfig(openclawConfigPath);
21992
+ const patchedOpenclawConfig = await patchOpenclawConfig(
21993
+ openclawConfigPath,
21994
+ existingRelayRuntimeConfig?.openclawHookToken
21995
+ );
21655
21996
  const peersPath = resolvePeersPath(homeDir);
21656
21997
  const peers = await loadPeersConfig(peersPath);
21657
21998
  peers.peers[peerAlias] = invite.name === void 0 ? { did: invite.did, proxyUrl: invite.proxyUrl } : { did: invite.did, proxyUrl: invite.proxyUrl, name: invite.name };
21658
21999
  await savePeersConfig(peersPath, peers);
22000
+ const relayTransformPeersPath = resolveTransformPeersPath(openclawDir);
22001
+ await writeSecureFile3(
22002
+ relayTransformPeersPath,
22003
+ `${JSON.stringify(peers, null, 2)}
22004
+ `
22005
+ );
22006
+ const connectorAssignmentsPath = resolveConnectorAssignmentsPath(homeDir);
22007
+ const connectorAssignments = await loadConnectorAssignments(
22008
+ connectorAssignmentsPath
22009
+ );
22010
+ const connectorPort = allocateConnectorPort(
22011
+ connectorAssignments,
22012
+ normalizedAgentName
22013
+ );
22014
+ const connectorBaseUrl = buildConnectorBaseUrl(
22015
+ CONNECTOR_HOST_LOOPBACK,
22016
+ connectorPort
22017
+ );
22018
+ connectorAssignments.agents[normalizedAgentName] = {
22019
+ connectorBaseUrl,
22020
+ updatedAt: nowIso()
22021
+ };
22022
+ await saveConnectorAssignments(
22023
+ connectorAssignmentsPath,
22024
+ connectorAssignments
22025
+ );
22026
+ const relayTransformRuntimePath = resolveTransformRuntimePath(openclawDir);
22027
+ await writeSecureFile3(
22028
+ relayTransformRuntimePath,
22029
+ `${JSON.stringify(
22030
+ {
22031
+ version: 1,
22032
+ connectorBaseUrl: buildRelayConnectorBaseUrls(connectorPort)[0],
22033
+ connectorBaseUrls: buildRelayConnectorBaseUrls(connectorPort),
22034
+ connectorPath: DEFAULT_CONNECTOR_OUTBOUND_PATH3,
22035
+ peersConfigPath: RELAY_PEERS_FILE_NAME,
22036
+ updatedAt: nowIso()
22037
+ },
22038
+ null,
22039
+ 2
22040
+ )}
22041
+ `
22042
+ );
21659
22043
  const agentNamePath = resolveOpenclawAgentNamePath(homeDir);
21660
22044
  await writeSecureFile3(agentNamePath, `${normalizedAgentName}
21661
22045
  `);
21662
- await saveRelayRuntimeConfig(relayRuntimeConfigPath, openclawBaseUrl);
22046
+ await saveRelayRuntimeConfig(
22047
+ relayRuntimeConfigPath,
22048
+ openclawBaseUrl,
22049
+ patchedOpenclawConfig.hookToken
22050
+ );
21663
22051
  logger8.info("cli.openclaw_setup_completed", {
21664
22052
  agentName: normalizedAgentName,
21665
22053
  peerAlias,
21666
22054
  peerDid: invite.did,
21667
22055
  openclawConfigPath,
21668
22056
  transformTargetPath,
22057
+ relayTransformRuntimePath,
22058
+ relayTransformPeersPath,
21669
22059
  openclawBaseUrl,
22060
+ connectorBaseUrl,
21670
22061
  relayRuntimeConfigPath
21671
22062
  });
21672
22063
  return {
@@ -21675,7 +22066,10 @@ async function setupOpenclawRelayFromInvite(agentName, options) {
21675
22066
  peerProxyUrl: invite.proxyUrl,
21676
22067
  openclawConfigPath,
21677
22068
  transformTargetPath,
22069
+ relayTransformRuntimePath,
22070
+ relayTransformPeersPath,
21678
22071
  openclawBaseUrl,
22072
+ connectorBaseUrl,
21679
22073
  relayRuntimeConfigPath
21680
22074
  };
21681
22075
  }
@@ -21724,6 +22118,13 @@ var createOpenclawCommand = () => {
21724
22118
  `Updated OpenClaw config: ${result.openclawConfigPath}`
21725
22119
  );
21726
22120
  writeStdoutLine(`Installed transform: ${result.transformTargetPath}`);
22121
+ writeStdoutLine(
22122
+ `Transform runtime config: ${result.relayTransformRuntimePath}`
22123
+ );
22124
+ writeStdoutLine(
22125
+ `Transform peers snapshot: ${result.relayTransformPeersPath}`
22126
+ );
22127
+ writeStdoutLine(`Connector base URL: ${result.connectorBaseUrl}`);
21727
22128
  writeStdoutLine(`OpenClaw base URL: ${result.openclawBaseUrl}`);
21728
22129
  writeStdoutLine(
21729
22130
  `Relay runtime config: ${result.relayRuntimeConfigPath}`
@@ -21795,8 +22196,8 @@ var createOpenclawCommand = () => {
21795
22196
  };
21796
22197
 
21797
22198
  // src/commands/pair.ts
21798
- import { randomBytes as randomBytes3 } from "crypto";
21799
- import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
22199
+ import { randomBytes as randomBytes4 } from "crypto";
22200
+ import { mkdir as mkdir6, readdir, readFile as readFile5, unlink as unlink2, writeFile as writeFile6 } from "fs/promises";
21800
22201
  import { dirname as dirname5, join as join7, resolve } from "path";
21801
22202
  import { Command as Command8 } from "commander";
21802
22203
  import jsQR from "jsqr";
@@ -21812,6 +22213,8 @@ var PAIR_CONFIRM_PATH = "/pair/confirm";
21812
22213
  var OWNER_PAT_HEADER = "x-claw-owner-pat";
21813
22214
  var NONCE_SIZE2 = 24;
21814
22215
  var PAIRING_TICKET_PREFIX = "clwpair1_";
22216
+ var PAIRING_QR_MAX_AGE_SECONDS = 900;
22217
+ var PAIRING_QR_FILENAME_PATTERN = /-pair-(\d+)\.png$/;
21815
22218
  var isRecord8 = (value) => {
21816
22219
  return typeof value === "object" && value !== null;
21817
22220
  };
@@ -22120,6 +22523,8 @@ function decodeTicketFromPng(imageBytes) {
22120
22523
  }
22121
22524
  async function persistPairingQr(input) {
22122
22525
  const mkdirImpl = input.dependencies.mkdirImpl ?? mkdir6;
22526
+ const readdirImpl = input.dependencies.readdirImpl ?? readdir;
22527
+ const unlinkImpl = input.dependencies.unlinkImpl ?? unlink2;
22123
22528
  const writeFileImpl = input.dependencies.writeFileImpl ?? writeFile6;
22124
22529
  const getConfigDirImpl = input.dependencies.getConfigDirImpl ?? getConfigDir;
22125
22530
  const qrEncodeImpl = input.dependencies.qrEncodeImpl ?? encodeTicketQrPng;
@@ -22128,6 +22533,37 @@ async function persistPairingQr(input) {
22128
22533
  baseDir,
22129
22534
  `${assertValidAgentName(input.agentName)}-pair-${input.nowSeconds}.png`
22130
22535
  );
22536
+ const existingFiles = await readdirImpl(baseDir).catch((error48) => {
22537
+ const nodeError = error48;
22538
+ if (nodeError.code === "ENOENT") {
22539
+ return [];
22540
+ }
22541
+ throw error48;
22542
+ });
22543
+ for (const fileName of existingFiles) {
22544
+ if (typeof fileName !== "string") {
22545
+ continue;
22546
+ }
22547
+ const match2 = PAIRING_QR_FILENAME_PATTERN.exec(fileName);
22548
+ if (!match2) {
22549
+ continue;
22550
+ }
22551
+ const issuedAtSeconds = Number.parseInt(match2[1] ?? "", 10);
22552
+ if (!Number.isInteger(issuedAtSeconds)) {
22553
+ continue;
22554
+ }
22555
+ if (issuedAtSeconds + PAIRING_QR_MAX_AGE_SECONDS > input.nowSeconds) {
22556
+ continue;
22557
+ }
22558
+ const stalePath = join7(baseDir, fileName);
22559
+ await unlinkImpl(stalePath).catch((error48) => {
22560
+ const nodeError = error48;
22561
+ if (nodeError.code === "ENOENT") {
22562
+ return;
22563
+ }
22564
+ throw error48;
22565
+ });
22566
+ }
22131
22567
  await mkdirImpl(dirname5(outputPath), { recursive: true });
22132
22568
  const imageBytes = await qrEncodeImpl(input.ticket);
22133
22569
  await writeFileImpl(outputPath, imageBytes);
@@ -22164,7 +22600,7 @@ async function startPairing(agentName, options, dependencies = {}) {
22164
22600
  const fetchImpl = dependencies.fetchImpl ?? fetch;
22165
22601
  const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
22166
22602
  const nowSecondsImpl = dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
22167
- const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes3(NONCE_SIZE2).toString("base64url"));
22603
+ const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
22168
22604
  const ttlSeconds = parseTtlSeconds(options.ttlSeconds);
22169
22605
  const proxyUrl = resolveProxyUrl(options.proxyUrl);
22170
22606
  const config2 = await resolveConfigImpl();
@@ -22231,7 +22667,7 @@ async function startPairing(agentName, options, dependencies = {}) {
22231
22667
  async function confirmPairing(agentName, options, dependencies = {}) {
22232
22668
  const fetchImpl = dependencies.fetchImpl ?? fetch;
22233
22669
  const nowSecondsImpl = dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
22234
- const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes3(NONCE_SIZE2).toString("base64url"));
22670
+ const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
22235
22671
  const readFileImpl = dependencies.readFileImpl ?? readFile5;
22236
22672
  const qrDecodeImpl = dependencies.qrDecodeImpl ?? decodeTicketFromPng;
22237
22673
  const ticketSource = resolveConfirmTicketSource(options);
@@ -22297,6 +22733,19 @@ async function confirmPairing(agentName, options, dependencies = {}) {
22297
22733
  );
22298
22734
  }
22299
22735
  const parsed = parsePairConfirmResponse(responseBody);
22736
+ if (ticketSource.source === "qr-file" && ticketSource.qrFilePath) {
22737
+ const unlinkImpl = dependencies.unlinkImpl ?? unlink2;
22738
+ await unlinkImpl(ticketSource.qrFilePath).catch((error48) => {
22739
+ const nodeError = error48;
22740
+ if (nodeError.code === "ENOENT") {
22741
+ return;
22742
+ }
22743
+ logger9.warn("cli.pair.confirm.qr_cleanup_failed", {
22744
+ path: ticketSource.qrFilePath,
22745
+ reason: error48 instanceof Error && error48.message.length > 0 ? error48.message : "unknown"
22746
+ });
22747
+ });
22748
+ }
22300
22749
  return {
22301
22750
  ...parsed,
22302
22751
  proxyUrl