clawdentity 0.0.23 → 0.0.24

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/index.js CHANGED
@@ -17110,11 +17110,24 @@ var DEFAULT_NONCE_TTL_MS = 5 * 60 * 1e3;
17110
17110
  import { Command } from "commander";
17111
17111
 
17112
17112
  // src/config/manager.ts
17113
- import { chmod, mkdir, readFile, writeFile } from "fs/promises";
17113
+ import { readFileSync } from "fs";
17114
+ import {
17115
+ chmod,
17116
+ cp,
17117
+ mkdir,
17118
+ readdir,
17119
+ readFile,
17120
+ stat,
17121
+ writeFile
17122
+ } from "fs/promises";
17114
17123
  import { homedir } from "os";
17115
17124
  import { dirname, join as join2 } from "path";
17116
17125
  var DEFAULT_REGISTRY_URL = "https://registry.clawdentity.com";
17117
- var CONFIG_DIR = ".clawdentity";
17126
+ var DEFAULT_DEV_REGISTRY_URL = "https://dev.registry.clawdentity.com";
17127
+ var DEFAULT_LOCAL_REGISTRY_URL = "http://127.0.0.1:8788";
17128
+ var CONFIG_ROOT_DIR = ".clawdentity";
17129
+ var CONFIG_STATES_DIR = "states";
17130
+ var CONFIG_ROUTER_FILE = "router.json";
17118
17131
  var CONFIG_FILE = "config.json";
17119
17132
  var CACHE_DIR = "cache";
17120
17133
  var FILE_MODE = 384;
@@ -17130,9 +17143,26 @@ var LEGACY_ENV_KEY_MAP = {
17130
17143
  var DEFAULT_CONFIG = {
17131
17144
  registryUrl: DEFAULT_REGISTRY_URL
17132
17145
  };
17146
+ var STATE_KIND_BY_REGISTRY_HOST = {
17147
+ "registry.clawdentity.com": "prod",
17148
+ "dev.registry.clawdentity.com": "dev"
17149
+ };
17150
+ var LOCAL_REGISTRY_HOSTS = /* @__PURE__ */ new Set([
17151
+ "localhost",
17152
+ "127.0.0.1",
17153
+ "host.docker.internal"
17154
+ ]);
17155
+ var LEGACY_ROOT_ENTRIES = /* @__PURE__ */ new Set([CONFIG_STATES_DIR, CONFIG_ROUTER_FILE]);
17133
17156
  var isConfigObject = (value) => {
17134
17157
  return typeof value === "object" && value !== null;
17135
17158
  };
17159
+ var parseNonEmptyString2 = (value) => {
17160
+ if (typeof value !== "string") {
17161
+ return void 0;
17162
+ }
17163
+ const trimmed = value.trim();
17164
+ return trimmed.length > 0 ? trimmed : void 0;
17165
+ };
17136
17166
  var normalizeConfig = (raw) => {
17137
17167
  if (!isConfigObject(raw)) {
17138
17168
  return { ...DEFAULT_CONFIG };
@@ -17154,17 +17184,196 @@ var normalizeConfig = (raw) => {
17154
17184
  }
17155
17185
  return config2;
17156
17186
  };
17157
- var getConfigDir = () => join2(homedir(), CONFIG_DIR);
17158
- var getConfigFilePath = () => join2(getConfigDir(), CONFIG_FILE);
17159
- var getCacheDir = () => join2(getConfigDir(), CACHE_DIR);
17160
- var getCacheFilePath = (fileName) => join2(getCacheDir(), fileName);
17187
+ var isCliStateKind = (value) => {
17188
+ return value === "prod" || value === "dev" || value === "local";
17189
+ };
17190
+ var resolveHomeDir = (options) => {
17191
+ const configured = parseNonEmptyString2(options?.homeDir);
17192
+ return configured ?? homedir();
17193
+ };
17194
+ var getConfigRootDir = (options) => {
17195
+ return join2(resolveHomeDir(options), CONFIG_ROOT_DIR);
17196
+ };
17197
+ var getConfigStatesDir = (options) => {
17198
+ return join2(getConfigRootDir(options), CONFIG_STATES_DIR);
17199
+ };
17200
+ var getRouterFilePath = (options) => {
17201
+ return join2(getConfigRootDir(options), CONFIG_ROUTER_FILE);
17202
+ };
17203
+ var readRouterSync = (options) => {
17204
+ try {
17205
+ const raw = readFileSync(getRouterFilePath(options), "utf-8");
17206
+ const parsed = JSON.parse(raw);
17207
+ if (!isConfigObject(parsed)) {
17208
+ return {};
17209
+ }
17210
+ const lastRegistryUrl = parseNonEmptyString2(parsed.lastRegistryUrl);
17211
+ const lastState = isCliStateKind(parsed.lastState) ? parsed.lastState : void 0;
17212
+ const migratedLegacyState = parsed.migratedLegacyState === true;
17213
+ return {
17214
+ lastRegistryUrl,
17215
+ lastState,
17216
+ migratedLegacyState
17217
+ };
17218
+ } catch {
17219
+ return {};
17220
+ }
17221
+ };
17222
+ var getRegistryUrlOverrideFromEnv = () => {
17223
+ const envCandidates = [
17224
+ process.env.CLAWDENTITY_REGISTRY_URL,
17225
+ process.env.CLAWDENTITY_REGISTRY
17226
+ ];
17227
+ return envCandidates.find((value) => {
17228
+ return typeof value === "string" && value.trim().length > 0;
17229
+ });
17230
+ };
17231
+ var resolveStateKindFromRegistryUrl = (registryUrl) => {
17232
+ try {
17233
+ const host = new URL(registryUrl).hostname.trim().toLowerCase();
17234
+ const mapped = STATE_KIND_BY_REGISTRY_HOST[host];
17235
+ if (mapped) {
17236
+ return mapped;
17237
+ }
17238
+ if (LOCAL_REGISTRY_HOSTS.has(host)) {
17239
+ return "local";
17240
+ }
17241
+ return "prod";
17242
+ } catch {
17243
+ return "prod";
17244
+ }
17245
+ };
17246
+ var defaultRegistryUrlForState = (state) => {
17247
+ switch (state) {
17248
+ case "dev":
17249
+ return DEFAULT_DEV_REGISTRY_URL;
17250
+ case "local":
17251
+ return DEFAULT_LOCAL_REGISTRY_URL;
17252
+ default:
17253
+ return DEFAULT_REGISTRY_URL;
17254
+ }
17255
+ };
17256
+ var resolveStateSelection = (options) => {
17257
+ const hintedRegistryUrl = parseNonEmptyString2(options?.registryUrlHint);
17258
+ if (hintedRegistryUrl) {
17259
+ return {
17260
+ stateKind: resolveStateKindFromRegistryUrl(hintedRegistryUrl),
17261
+ registryUrl: hintedRegistryUrl
17262
+ };
17263
+ }
17264
+ const envRegistryUrl = getRegistryUrlOverrideFromEnv();
17265
+ if (envRegistryUrl) {
17266
+ return {
17267
+ stateKind: resolveStateKindFromRegistryUrl(envRegistryUrl),
17268
+ registryUrl: envRegistryUrl
17269
+ };
17270
+ }
17271
+ const router = readRouterSync(options);
17272
+ if (router.lastRegistryUrl) {
17273
+ return {
17274
+ stateKind: resolveStateKindFromRegistryUrl(router.lastRegistryUrl),
17275
+ registryUrl: router.lastRegistryUrl
17276
+ };
17277
+ }
17278
+ if (router.lastState) {
17279
+ return {
17280
+ stateKind: router.lastState,
17281
+ registryUrl: defaultRegistryUrlForState(router.lastState)
17282
+ };
17283
+ }
17284
+ return {
17285
+ stateKind: "prod",
17286
+ registryUrl: DEFAULT_REGISTRY_URL
17287
+ };
17288
+ };
17289
+ var pathExists = async (path) => {
17290
+ try {
17291
+ await stat(path);
17292
+ return true;
17293
+ } catch (error48) {
17294
+ const nodeError = error48;
17295
+ if (nodeError.code === "ENOENT") {
17296
+ return false;
17297
+ }
17298
+ throw error48;
17299
+ }
17300
+ };
17161
17301
  var writeSecureFile = async (filePath, value) => {
17162
17302
  const targetDirectory = dirname(filePath);
17163
17303
  await mkdir(targetDirectory, { recursive: true });
17164
17304
  await writeFile(filePath, value, "utf-8");
17165
17305
  await chmod(filePath, FILE_MODE);
17166
17306
  };
17307
+ var writeRouter = async (router, options) => {
17308
+ const payload = {};
17309
+ if (router.lastRegistryUrl) {
17310
+ payload.lastRegistryUrl = router.lastRegistryUrl;
17311
+ }
17312
+ if (router.lastState) {
17313
+ payload.lastState = router.lastState;
17314
+ }
17315
+ if (router.migratedLegacyState === true) {
17316
+ payload.migratedLegacyState = true;
17317
+ }
17318
+ await writeSecureFile(
17319
+ getRouterFilePath(options),
17320
+ `${JSON.stringify(payload)}
17321
+ `
17322
+ );
17323
+ };
17324
+ var ensureStateLayoutMigrated = async (options) => {
17325
+ const router = readRouterSync(options);
17326
+ if (router.migratedLegacyState === true) {
17327
+ return;
17328
+ }
17329
+ let entries;
17330
+ try {
17331
+ entries = await readdir(getConfigRootDir(options), {
17332
+ withFileTypes: true
17333
+ });
17334
+ } catch (error48) {
17335
+ const nodeError = error48;
17336
+ if (nodeError.code === "ENOENT") {
17337
+ return;
17338
+ }
17339
+ throw error48;
17340
+ }
17341
+ const legacyEntries = entries.filter((entry) => {
17342
+ return !LEGACY_ROOT_ENTRIES.has(entry.name);
17343
+ });
17344
+ if (legacyEntries.length > 0) {
17345
+ const prodStateDir = join2(getConfigStatesDir(options), "prod");
17346
+ await mkdir(prodStateDir, { recursive: true });
17347
+ for (const entry of legacyEntries) {
17348
+ const sourcePath = join2(getConfigRootDir(options), entry.name);
17349
+ const targetPath = join2(prodStateDir, entry.name);
17350
+ if (await pathExists(targetPath)) {
17351
+ continue;
17352
+ }
17353
+ await cp(sourcePath, targetPath, {
17354
+ recursive: true,
17355
+ errorOnExist: false
17356
+ });
17357
+ }
17358
+ }
17359
+ await writeRouter(
17360
+ {
17361
+ lastRegistryUrl: router.lastRegistryUrl ?? DEFAULT_REGISTRY_URL,
17362
+ lastState: router.lastState ?? "prod",
17363
+ migratedLegacyState: true
17364
+ },
17365
+ options
17366
+ );
17367
+ };
17368
+ var getConfigDir = (options) => {
17369
+ const selection = resolveStateSelection(options);
17370
+ return join2(getConfigStatesDir(options), selection.stateKind);
17371
+ };
17372
+ var getConfigFilePath = (options) => join2(getConfigDir(options), CONFIG_FILE);
17373
+ var getCacheDir = (options) => join2(getConfigDir(options), CACHE_DIR);
17374
+ var getCacheFilePath = (fileName, options) => join2(getCacheDir(options), fileName);
17167
17375
  var readConfig = async () => {
17376
+ await ensureStateLayoutMigrated();
17168
17377
  try {
17169
17378
  const configContents = await readFile(getConfigFilePath(), "utf-8");
17170
17379
  return normalizeConfig(JSON.parse(configContents));
@@ -17190,11 +17399,21 @@ var resolveConfig = async () => {
17190
17399
  return config2;
17191
17400
  };
17192
17401
  var writeConfig = async (config2) => {
17402
+ await ensureStateLayoutMigrated();
17403
+ const selection = resolveStateSelection({
17404
+ registryUrlHint: config2.registryUrl
17405
+ });
17193
17406
  await writeSecureFile(
17194
- getConfigFilePath(),
17407
+ getConfigFilePath({ registryUrlHint: config2.registryUrl }),
17195
17408
  `${JSON.stringify(config2, null, 2)}
17196
17409
  `
17197
17410
  );
17411
+ const currentRouter = readRouterSync();
17412
+ await writeRouter({
17413
+ lastRegistryUrl: selection.registryUrl,
17414
+ lastState: selection.stateKind,
17415
+ migratedLegacyState: currentRouter.migratedLegacyState === true
17416
+ });
17198
17417
  };
17199
17418
  var getConfigValue = async (key) => {
17200
17419
  const config2 = await resolveConfig();
@@ -17208,6 +17427,7 @@ var setConfigValue = async (key, value) => {
17208
17427
  });
17209
17428
  };
17210
17429
  var readCacheFile = async (fileName) => {
17430
+ await ensureStateLayoutMigrated();
17211
17431
  try {
17212
17432
  return await readFile(getCacheFilePath(fileName), "utf-8");
17213
17433
  } catch (error48) {
@@ -17219,6 +17439,7 @@ var readCacheFile = async (fileName) => {
17219
17439
  }
17220
17440
  };
17221
17441
  var writeCacheFile = async (fileName, value) => {
17442
+ await ensureStateLayoutMigrated();
17222
17443
  await writeSecureFile(getCacheFilePath(fileName), value);
17223
17444
  };
17224
17445
 
@@ -17259,14 +17480,14 @@ function createCliError(code, message2) {
17259
17480
  status: 400
17260
17481
  });
17261
17482
  }
17262
- function parseNonEmptyString2(value) {
17483
+ function parseNonEmptyString3(value) {
17263
17484
  if (typeof value !== "string") {
17264
17485
  return "";
17265
17486
  }
17266
17487
  return value.trim();
17267
17488
  }
17268
17489
  function resolveBootstrapRegistryUrl(input) {
17269
- const candidate = parseNonEmptyString2(input.overrideRegistryUrl) || input.configRegistryUrl;
17490
+ const candidate = parseNonEmptyString3(input.overrideRegistryUrl) || input.configRegistryUrl;
17270
17491
  try {
17271
17492
  return new URL(candidate).toString();
17272
17493
  } catch {
@@ -17292,12 +17513,12 @@ function parseBootstrapResponse(payload) {
17292
17513
  "Bootstrap response is invalid"
17293
17514
  );
17294
17515
  }
17295
- const humanId = parseNonEmptyString2(human.id);
17296
- const humanDid = parseNonEmptyString2(human.did);
17297
- const humanDisplayName = parseNonEmptyString2(human.displayName);
17298
- const apiKeyId = parseNonEmptyString2(apiKey.id);
17299
- const apiKeyName = parseNonEmptyString2(apiKey.name);
17300
- const apiKeyToken = parseNonEmptyString2(apiKey.token);
17516
+ const humanId = parseNonEmptyString3(human.id);
17517
+ const humanDid = parseNonEmptyString3(human.did);
17518
+ const humanDisplayName = parseNonEmptyString3(human.displayName);
17519
+ const apiKeyId = parseNonEmptyString3(apiKey.id);
17520
+ const apiKeyName = parseNonEmptyString3(apiKey.name);
17521
+ const apiKeyToken = parseNonEmptyString3(apiKey.token);
17301
17522
  if (humanId.length === 0 || humanDid.length === 0 || humanDisplayName.length === 0 || apiKeyId.length === 0 || apiKeyName.length === 0 || apiKeyToken.length === 0) {
17302
17523
  throw createCliError(
17303
17524
  "CLI_ADMIN_BOOTSTRAP_INVALID_RESPONSE",
@@ -17335,7 +17556,7 @@ function mapBootstrapFailureMessage(payload) {
17335
17556
  return "Admin bootstrap request failed";
17336
17557
  }
17337
17558
  async function bootstrapAdmin(options, dependencies = {}) {
17338
- const bootstrapSecret = parseNonEmptyString2(options.bootstrapSecret);
17559
+ const bootstrapSecret = parseNonEmptyString3(options.bootstrapSecret);
17339
17560
  if (bootstrapSecret.length === 0) {
17340
17561
  throw createCliError(
17341
17562
  "CLI_ADMIN_BOOTSTRAP_SECRET_REQUIRED",
@@ -17358,8 +17579,8 @@ async function bootstrapAdmin(options, dependencies = {}) {
17358
17579
  "x-bootstrap-secret": bootstrapSecret
17359
17580
  },
17360
17581
  body: JSON.stringify({
17361
- displayName: parseNonEmptyString2(options.displayName) || void 0,
17362
- apiKeyName: parseNonEmptyString2(options.apiKeyName) || void 0
17582
+ displayName: parseNonEmptyString3(options.displayName) || void 0,
17583
+ apiKeyName: parseNonEmptyString3(options.apiKeyName) || void 0
17363
17584
  })
17364
17585
  });
17365
17586
  } catch (error48) {
@@ -17470,7 +17691,7 @@ var FILE_MODE2 = 384;
17470
17691
  var isRecord2 = (value) => {
17471
17692
  return typeof value === "object" && value !== null;
17472
17693
  };
17473
- var parseNonEmptyString3 = (value) => {
17694
+ var parseNonEmptyString4 = (value) => {
17474
17695
  if (typeof value !== "string") {
17475
17696
  return "";
17476
17697
  }
@@ -17540,7 +17761,7 @@ var readAgentIdentity = async (agentName) => {
17540
17761
  `Agent "${agentName}" has invalid ${IDENTITY_FILE_NAME} (missing did)`
17541
17762
  );
17542
17763
  }
17543
- const registryUrl = parseNonEmptyString3(parsed.registryUrl);
17764
+ const registryUrl = parseNonEmptyString4(parsed.registryUrl);
17544
17765
  return {
17545
17766
  did,
17546
17767
  registryUrl: registryUrl.length > 0 ? registryUrl : void 0
@@ -17595,7 +17816,7 @@ var readAgentRegistryAuth = async (agentName) => {
17595
17816
  `Agent "${agentName}" has invalid ${REGISTRY_AUTH_FILE_NAME}`
17596
17817
  );
17597
17818
  }
17598
- const refreshToken = parseNonEmptyString3(parsed.refreshToken);
17819
+ const refreshToken = parseNonEmptyString4(parsed.refreshToken);
17599
17820
  if (refreshToken.length === 0) {
17600
17821
  throw new Error(
17601
17822
  `Agent "${agentName}" has invalid ${REGISTRY_AUTH_FILE_NAME} (missing refreshToken)`
@@ -18120,7 +18341,7 @@ var logger4 = createLogger({ service: "cli", module: "api-key" });
18120
18341
  var isRecord3 = (value) => {
18121
18342
  return typeof value === "object" && value !== null;
18122
18343
  };
18123
- function parseNonEmptyString4(value) {
18344
+ function parseNonEmptyString5(value) {
18124
18345
  if (typeof value !== "string") {
18125
18346
  return "";
18126
18347
  }
@@ -18134,7 +18355,7 @@ function createCliError2(code, message2) {
18134
18355
  });
18135
18356
  }
18136
18357
  function resolveRegistryUrl(input) {
18137
- const candidate = parseNonEmptyString4(input.overrideRegistryUrl) || input.configRegistryUrl;
18358
+ const candidate = parseNonEmptyString5(input.overrideRegistryUrl) || input.configRegistryUrl;
18138
18359
  try {
18139
18360
  return new URL(candidate).toString();
18140
18361
  } catch {
@@ -18202,10 +18423,10 @@ function parseApiKeyMetadata(payload) {
18202
18423
  "API key response is invalid"
18203
18424
  );
18204
18425
  }
18205
- const id = parseNonEmptyString4(payload.id);
18206
- const name = parseNonEmptyString4(payload.name);
18426
+ const id = parseNonEmptyString5(payload.id);
18427
+ const name = parseNonEmptyString5(payload.name);
18207
18428
  const status = payload.status;
18208
- const createdAt = parseNonEmptyString4(payload.createdAt);
18429
+ const createdAt = parseNonEmptyString5(payload.createdAt);
18209
18430
  const lastUsedAt = payload.lastUsedAt;
18210
18431
  if (id.length === 0 || name.length === 0 || status !== "active" && status !== "revoked" || createdAt.length === 0 || lastUsedAt !== null && typeof lastUsedAt !== "string") {
18211
18432
  throw createCliError2(
@@ -18229,7 +18450,7 @@ function parseApiKeyCreateResponse(payload) {
18229
18450
  );
18230
18451
  }
18231
18452
  const metadata = parseApiKeyMetadata(payload.apiKey);
18232
- const token = parseNonEmptyString4(payload.apiKey.token);
18453
+ const token = parseNonEmptyString5(payload.apiKey.token);
18233
18454
  if (token.length === 0) {
18234
18455
  throw createCliError2(
18235
18456
  "CLI_API_KEY_INVALID_RESPONSE",
@@ -18301,7 +18522,7 @@ async function createApiKey(options, dependencies = {}) {
18301
18522
  "content-type": "application/json"
18302
18523
  },
18303
18524
  body: JSON.stringify({
18304
- name: parseNonEmptyString4(options.name) || void 0
18525
+ name: parseNonEmptyString5(options.name) || void 0
18305
18526
  })
18306
18527
  }
18307
18528
  });
@@ -18445,7 +18666,7 @@ import { Command as Command4 } from "commander";
18445
18666
  function isRecord4(value) {
18446
18667
  return typeof value === "object" && value !== null;
18447
18668
  }
18448
- function parseNonEmptyString5(value) {
18669
+ function parseNonEmptyString6(value) {
18449
18670
  if (typeof value !== "string") {
18450
18671
  return "";
18451
18672
  }
@@ -18503,7 +18724,7 @@ function parseMetadataPayload(payload, fallbackRegistryUrl) {
18503
18724
  "Registry metadata response is invalid"
18504
18725
  );
18505
18726
  }
18506
- const proxyUrlRaw = parseNonEmptyString5(payload.proxyUrl);
18727
+ const proxyUrlRaw = parseNonEmptyString6(payload.proxyUrl);
18507
18728
  if (proxyUrlRaw.length === 0) {
18508
18729
  throw createCliError3(
18509
18730
  "CLI_REGISTRY_METADATA_INVALID_RESPONSE",
@@ -18511,10 +18732,10 @@ function parseMetadataPayload(payload, fallbackRegistryUrl) {
18511
18732
  );
18512
18733
  }
18513
18734
  const proxyUrl = parseUrl(proxyUrlRaw, "Proxy URL").toString();
18514
- const registryUrlRaw = parseNonEmptyString5(payload.registryUrl);
18735
+ const registryUrlRaw = parseNonEmptyString6(payload.registryUrl);
18515
18736
  const registryUrl = registryUrlRaw.length > 0 ? parseUrl(registryUrlRaw, "Registry URL").toString() : normalizeRegistryUrl(fallbackRegistryUrl);
18516
- const environment = parseNonEmptyString5(payload.environment);
18517
- const version2 = parseNonEmptyString5(payload.version);
18737
+ const environment = parseNonEmptyString6(payload.environment);
18738
+ const version2 = parseNonEmptyString6(payload.version);
18518
18739
  return {
18519
18740
  proxyUrl,
18520
18741
  registryUrl,
@@ -18622,28 +18843,35 @@ var createConfigCommand = (dependencies = {}) => {
18622
18843
  );
18623
18844
  configCommand.command("init").description("Initialize local config file").option("--registry-url <url>", "Initialize config with registry URL").action(
18624
18845
  withErrorHandling("config init", async (options) => {
18625
- const configFilePath = getConfigFilePath();
18846
+ const config2 = await readConfig();
18847
+ const requestedRegistryUrl = options.registryUrl ?? getEnvRegistryUrlOverride() ?? config2.registryUrl;
18848
+ const normalizedRegistryUrl = normalizeRegistryUrl(requestedRegistryUrl);
18849
+ const requestedConfigFilePath = getConfigFilePath({
18850
+ registryUrlHint: normalizedRegistryUrl
18851
+ });
18626
18852
  try {
18627
- await access2(configFilePath);
18628
- writeStdoutLine(`Config already exists at ${configFilePath}`);
18853
+ await access2(requestedConfigFilePath);
18854
+ writeStdoutLine(
18855
+ `Config already exists at ${requestedConfigFilePath}`
18856
+ );
18629
18857
  return;
18630
18858
  } catch (error48) {
18631
18859
  if (!isNotFoundError(error48)) {
18632
18860
  throw error48;
18633
18861
  }
18634
18862
  }
18635
- const config2 = await readConfig();
18636
- const requestedRegistryUrl = options.registryUrl ?? getEnvRegistryUrlOverride() ?? config2.registryUrl;
18637
- const normalizedRegistryUrl = normalizeRegistryUrl(requestedRegistryUrl);
18638
18863
  const metadata = await fetchRegistryMetadata(normalizedRegistryUrl, {
18639
18864
  fetchImpl: dependencies.fetchImpl
18640
18865
  });
18866
+ const targetConfigFilePath = getConfigFilePath({
18867
+ registryUrlHint: metadata.registryUrl
18868
+ });
18641
18869
  await writeConfig({
18642
18870
  ...config2,
18643
18871
  registryUrl: metadata.registryUrl,
18644
18872
  proxyUrl: metadata.proxyUrl
18645
18873
  });
18646
- writeStdoutLine(`Initialized config at ${configFilePath}`);
18874
+ writeStdoutLine(`Initialized config at ${targetConfigFilePath}`);
18647
18875
  writeStdoutLine(
18648
18876
  JSON.stringify(
18649
18877
  maskApiKey({
@@ -20571,7 +20799,7 @@ function createCliError4(code, message2, details) {
20571
20799
  details
20572
20800
  });
20573
20801
  }
20574
- function parseNonEmptyString6(value, label) {
20802
+ function parseNonEmptyString7(value, label) {
20575
20803
  if (typeof value !== "string") {
20576
20804
  throw createCliError4(
20577
20805
  "CLI_CONNECTOR_INVALID_INPUT",
@@ -20594,7 +20822,7 @@ function parseNonEmptyString6(value, label) {
20594
20822
  return trimmed;
20595
20823
  }
20596
20824
  function parseAgentDid(value) {
20597
- const did = parseNonEmptyString6(value, "agent did");
20825
+ const did = parseNonEmptyString7(value, "agent did");
20598
20826
  if (!did.startsWith("did:claw:agent:")) {
20599
20827
  throw createCliError4(
20600
20828
  "CLI_CONNECTOR_INVALID_AGENT_IDENTITY",
@@ -20806,7 +21034,7 @@ function parseRegistryAuth(rawRegistryAuth) {
20806
21034
  "CLI_CONNECTOR_INVALID_REGISTRY_AUTH",
20807
21035
  "Agent registry auth is invalid for connector startup"
20808
21036
  );
20809
- const refreshToken = parseNonEmptyString6(parsed.refreshToken, "refreshToken");
21037
+ const refreshToken = parseNonEmptyString7(parsed.refreshToken, "refreshToken");
20810
21038
  const accessToken = typeof parsed.accessToken === "string" && parsed.accessToken.trim().length > 0 ? parsed.accessToken.trim() : void 0;
20811
21039
  const accessExpiresAt = typeof parsed.accessExpiresAt === "string" && parsed.accessExpiresAt.trim().length > 0 ? parsed.accessExpiresAt.trim() : void 0;
20812
21040
  const refreshExpiresAt = typeof parsed.refreshExpiresAt === "string" && parsed.refreshExpiresAt.trim().length > 0 ? parsed.refreshExpiresAt.trim() : void 0;
@@ -21381,7 +21609,7 @@ var logger7 = createLogger({ service: "cli", module: "invite" });
21381
21609
  var isRecord8 = (value) => {
21382
21610
  return typeof value === "object" && value !== null;
21383
21611
  };
21384
- function parseNonEmptyString7(value) {
21612
+ function parseNonEmptyString8(value) {
21385
21613
  if (typeof value !== "string") {
21386
21614
  return "";
21387
21615
  }
@@ -21409,7 +21637,7 @@ function normalizeProxyUrl(value) {
21409
21637
  }
21410
21638
  }
21411
21639
  function resolveRegistryUrl2(input) {
21412
- const candidate = parseNonEmptyString7(input.overrideRegistryUrl) || input.configRegistryUrl;
21640
+ const candidate = parseNonEmptyString8(input.overrideRegistryUrl) || input.configRegistryUrl;
21413
21641
  return normalizeRegistryUrl(candidate);
21414
21642
  }
21415
21643
  function requireApiKey2(config2) {
@@ -21514,7 +21742,7 @@ function parseInviteRecord(payload) {
21514
21742
  );
21515
21743
  }
21516
21744
  const source = isRecord8(payload.invite) ? payload.invite : payload;
21517
- const code = parseNonEmptyString7(source.code);
21745
+ const code = parseNonEmptyString8(source.code);
21518
21746
  if (code.length === 0) {
21519
21747
  throw createCliError5(
21520
21748
  "CLI_INVITE_CREATE_INVALID_RESPONSE",
@@ -21524,11 +21752,11 @@ function parseInviteRecord(payload) {
21524
21752
  const invite = {
21525
21753
  code
21526
21754
  };
21527
- const id = parseNonEmptyString7(source.id);
21755
+ const id = parseNonEmptyString8(source.id);
21528
21756
  if (id.length > 0) {
21529
21757
  invite.id = id;
21530
21758
  }
21531
- const createdAt = parseNonEmptyString7(source.createdAt);
21759
+ const createdAt = parseNonEmptyString8(source.createdAt);
21532
21760
  if (createdAt.length > 0) {
21533
21761
  invite.createdAt = createdAt;
21534
21762
  }
@@ -21545,7 +21773,7 @@ function parseInviteRedeemResponse(payload) {
21545
21773
  );
21546
21774
  }
21547
21775
  const apiKeySource = isRecord8(payload.apiKey) ? payload.apiKey : payload;
21548
- const apiKeyToken = parseNonEmptyString7(
21776
+ const apiKeyToken = parseNonEmptyString8(
21549
21777
  isRecord8(payload.apiKey) ? payload.apiKey.token : payload.token
21550
21778
  );
21551
21779
  if (apiKeyToken.length === 0) {
@@ -21554,11 +21782,11 @@ function parseInviteRedeemResponse(payload) {
21554
21782
  "Invite redeem response is invalid"
21555
21783
  );
21556
21784
  }
21557
- const apiKeyId = parseNonEmptyString7(apiKeySource.id);
21558
- const apiKeyName = parseNonEmptyString7(apiKeySource.name);
21785
+ const apiKeyId = parseNonEmptyString8(apiKeySource.id);
21786
+ const apiKeyName = parseNonEmptyString8(apiKeySource.name);
21559
21787
  const humanSource = isRecord8(payload.human) ? payload.human : void 0;
21560
- const humanName = parseNonEmptyString7(humanSource?.displayName);
21561
- const proxyUrl = parseNonEmptyString7(payload.proxyUrl);
21788
+ const humanName = parseNonEmptyString8(humanSource?.displayName);
21789
+ const proxyUrl = parseNonEmptyString8(payload.proxyUrl);
21562
21790
  if (humanName.length === 0) {
21563
21791
  throw createCliError5(
21564
21792
  "CLI_INVITE_REDEEM_INVALID_RESPONSE",
@@ -21600,7 +21828,7 @@ async function createInvite(options, dependencies = {}) {
21600
21828
  "content-type": "application/json"
21601
21829
  },
21602
21830
  body: JSON.stringify({
21603
- expiresAt: parseNonEmptyString7(options.expiresAt) || void 0
21831
+ expiresAt: parseNonEmptyString8(options.expiresAt) || void 0
21604
21832
  })
21605
21833
  }
21606
21834
  });
@@ -21617,21 +21845,21 @@ async function createInvite(options, dependencies = {}) {
21617
21845
  };
21618
21846
  }
21619
21847
  async function redeemInvite(code, options, dependencies = {}) {
21620
- const inviteCode = parseNonEmptyString7(code);
21848
+ const inviteCode = parseNonEmptyString8(code);
21621
21849
  if (inviteCode.length === 0) {
21622
21850
  throw createCliError5(
21623
21851
  "CLI_INVITE_REDEEM_CODE_REQUIRED",
21624
21852
  "Invite code is required"
21625
21853
  );
21626
21854
  }
21627
- const displayName = parseNonEmptyString7(options.displayName);
21855
+ const displayName = parseNonEmptyString8(options.displayName);
21628
21856
  if (displayName.length === 0) {
21629
21857
  throw createCliError5(
21630
21858
  "CLI_INVITE_REDEEM_DISPLAY_NAME_REQUIRED",
21631
21859
  "Display name is required. Pass --display-name <name>."
21632
21860
  );
21633
21861
  }
21634
- const apiKeyName = parseNonEmptyString7(options.apiKeyName);
21862
+ const apiKeyName = parseNonEmptyString8(options.apiKeyName);
21635
21863
  const runtime = await resolveInviteRuntime(options.registryUrl, dependencies);
21636
21864
  const response = await executeInviteRequest({
21637
21865
  fetchImpl: runtime.fetchImpl,
@@ -21743,14 +21971,13 @@ var createInviteCommand = (dependencies = {}) => {
21743
21971
  // src/commands/openclaw.ts
21744
21972
  import { spawn } from "child_process";
21745
21973
  import { randomBytes as randomBytes3 } from "crypto";
21746
- import { existsSync } from "fs";
21974
+ import { closeSync, existsSync, openSync } from "fs";
21747
21975
  import { chmod as chmod3, copyFile, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
21748
21976
  import { homedir as homedir3 } from "os";
21749
21977
  import { dirname as dirname5, join as join7, resolve as resolvePath } from "path";
21750
21978
  import { fileURLToPath as fileURLToPath2 } from "url";
21751
21979
  import { Command as Command7 } from "commander";
21752
21980
  var logger8 = createLogger({ service: "cli", module: "openclaw" });
21753
- var CLAWDENTITY_DIR_NAME = ".clawdentity";
21754
21981
  var AGENTS_DIR_NAME4 = "agents";
21755
21982
  var AIT_FILE_NAME3 = "ait.jwt";
21756
21983
  var SECRET_KEY_FILE_NAME2 = "secret.key";
@@ -21788,6 +22015,8 @@ var CONNECTOR_HOST_DOCKER = "host.docker.internal";
21788
22015
  var CONNECTOR_HOST_DOCKER_GATEWAY = "gateway.docker.internal";
21789
22016
  var CONNECTOR_HOST_LINUX_BRIDGE = "172.17.0.1";
21790
22017
  var CONNECTOR_RUN_DIR_NAME = "run";
22018
+ var CONNECTOR_DETACHED_STDOUT_FILE_SUFFIX = "stdout.log";
22019
+ var CONNECTOR_DETACHED_STDERR_FILE_SUFFIX = "stderr.log";
21791
22020
  var PEER_ALIAS_PATTERN = /^[a-zA-Z0-9._-]+$/;
21792
22021
  var FILE_MODE3 = 384;
21793
22022
  var OPENCLAW_HOOK_TOKEN_BYTES = 32;
@@ -21799,6 +22028,8 @@ var OPENCLAW_DEVICE_APPROVAL_RECOVERY_HINT = "Run: clawdentity openclaw setup <a
21799
22028
  var OPENCLAW_GATEWAY_AUTH_RECOVERY_HINT = "Run: clawdentity openclaw setup <agentName> (ensures gateway auth mode/token are configured)";
21800
22029
  var OPENCLAW_GATEWAY_APPROVAL_COMMAND = "openclaw";
21801
22030
  var OPENCLAW_GATEWAY_APPROVAL_TIMEOUT_MS = 1e4;
22031
+ var OPENCLAW_SETUP_STABILITY_WINDOW_SECONDS = 20;
22032
+ var OPENCLAW_SETUP_STABILITY_POLL_INTERVAL_MS = 1e3;
21802
22033
  var textEncoder2 = new TextEncoder();
21803
22034
  var textDecoder = new TextDecoder();
21804
22035
  function isRecord9(value) {
@@ -21818,7 +22049,7 @@ function getErrorCode2(error48) {
21818
22049
  }
21819
22050
  return typeof error48.code === "string" ? error48.code : void 0;
21820
22051
  }
21821
- function parseNonEmptyString8(value, label) {
22052
+ function parseNonEmptyString9(value, label) {
21822
22053
  if (typeof value !== "string") {
21823
22054
  throw createCliError6(
21824
22055
  "CLI_OPENCLAW_INVALID_INPUT",
@@ -21842,10 +22073,10 @@ function parseOptionalProfileName(value, label) {
21842
22073
  if (value === void 0) {
21843
22074
  return void 0;
21844
22075
  }
21845
- return parseNonEmptyString8(value, label);
22076
+ return parseNonEmptyString9(value, label);
21846
22077
  }
21847
22078
  function parsePeerAlias(value) {
21848
- const alias = parseNonEmptyString8(value, "peer alias");
22079
+ const alias = parseNonEmptyString9(value, "peer alias");
21849
22080
  if (alias.length > 128) {
21850
22081
  throw createCliError6(
21851
22082
  "CLI_OPENCLAW_INVALID_PEER_ALIAS",
@@ -21868,7 +22099,7 @@ function parseProxyUrl(value) {
21868
22099
  });
21869
22100
  }
21870
22101
  function parseHttpUrl(value, input) {
21871
- const candidate = parseNonEmptyString8(value, input.label);
22102
+ const candidate = parseNonEmptyString9(value, input.label);
21872
22103
  let parsedUrl;
21873
22104
  try {
21874
22105
  parsedUrl = new URL(candidate);
@@ -21891,7 +22122,7 @@ function parseOpenclawBaseUrl(value) {
21891
22122
  });
21892
22123
  }
21893
22124
  function parseAgentDid2(value, label) {
21894
- const did = parseNonEmptyString8(value, label);
22125
+ const did = parseNonEmptyString9(value, label);
21895
22126
  try {
21896
22127
  const parsed = parseDid(did);
21897
22128
  if (parsed.kind !== "agent") {
@@ -21907,7 +22138,7 @@ function parseAgentDid2(value, label) {
21907
22138
  }
21908
22139
  return did;
21909
22140
  }
21910
- function resolveHomeDir(homeDir) {
22141
+ function resolveHomeDir2(homeDir) {
21911
22142
  if (typeof homeDir === "string" && homeDir.trim().length > 0) {
21912
22143
  return homeDir.trim();
21913
22144
  }
@@ -21968,10 +22199,10 @@ function resolveOpenclawDir(openclawDir, homeDir) {
21968
22199
  return resolveDefaultOpenclawStateDir(openclawHomeDir);
21969
22200
  }
21970
22201
  function resolveAgentDirectory(homeDir, agentName) {
21971
- return join7(homeDir, CLAWDENTITY_DIR_NAME, AGENTS_DIR_NAME4, agentName);
22202
+ return join7(getConfigDir({ homeDir }), AGENTS_DIR_NAME4, agentName);
21972
22203
  }
21973
22204
  function resolvePeersPath(homeDir) {
21974
- return join7(homeDir, CLAWDENTITY_DIR_NAME, PEERS_FILE_NAME);
22205
+ return join7(getConfigDir({ homeDir }), PEERS_FILE_NAME);
21975
22206
  }
21976
22207
  function resolveOpenclawConfigPath(openclawDir, homeDir) {
21977
22208
  const envConfigPath = readNonEmptyEnvPath(
@@ -22001,13 +22232,13 @@ function resolveTransformTargetPath(openclawDir) {
22001
22232
  return join7(openclawDir, "hooks", "transforms", RELAY_MODULE_FILE_NAME);
22002
22233
  }
22003
22234
  function resolveOpenclawAgentNamePath(homeDir) {
22004
- return join7(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_AGENT_FILE_NAME);
22235
+ return join7(getConfigDir({ homeDir }), OPENCLAW_AGENT_FILE_NAME);
22005
22236
  }
22006
22237
  function resolveRelayRuntimeConfigPath(homeDir) {
22007
- return join7(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_RELAY_RUNTIME_FILE_NAME2);
22238
+ return join7(getConfigDir({ homeDir }), OPENCLAW_RELAY_RUNTIME_FILE_NAME2);
22008
22239
  }
22009
22240
  function resolveConnectorAssignmentsPath(homeDir) {
22010
- return join7(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_CONNECTORS_FILE_NAME2);
22241
+ return join7(getConfigDir({ homeDir }), OPENCLAW_CONNECTORS_FILE_NAME2);
22011
22242
  }
22012
22243
  function resolveTransformRuntimePath(openclawDir) {
22013
22244
  return join7(openclawDir, "hooks", "transforms", RELAY_RUNTIME_FILE_NAME);
@@ -22475,12 +22706,51 @@ async function waitForConnectorConnected(input) {
22475
22706
  }
22476
22707
  return latest;
22477
22708
  }
22709
+ function sleepMilliseconds(durationMs) {
22710
+ return new Promise((resolve2) => {
22711
+ setTimeout(resolve2, durationMs);
22712
+ });
22713
+ }
22714
+ async function monitorConnectorStabilityWindow(input) {
22715
+ if (input.durationSeconds <= 0) {
22716
+ return fetchConnectorHealthStatus({
22717
+ connectorBaseUrl: input.connectorBaseUrl,
22718
+ fetchImpl: input.fetchImpl
22719
+ });
22720
+ }
22721
+ const deadline = Date.now() + input.durationSeconds * 1e3;
22722
+ let latest = await fetchConnectorHealthStatus({
22723
+ connectorBaseUrl: input.connectorBaseUrl,
22724
+ fetchImpl: input.fetchImpl
22725
+ });
22726
+ if (!latest.connected) {
22727
+ return latest;
22728
+ }
22729
+ while (Date.now() < deadline) {
22730
+ await sleepMilliseconds(input.pollIntervalMs);
22731
+ latest = await fetchConnectorHealthStatus({
22732
+ connectorBaseUrl: input.connectorBaseUrl,
22733
+ fetchImpl: input.fetchImpl
22734
+ });
22735
+ if (!latest.connected) {
22736
+ return latest;
22737
+ }
22738
+ }
22739
+ return latest;
22740
+ }
22478
22741
  function resolveConnectorRunDir(homeDir) {
22479
- return join7(homeDir, CLAWDENTITY_DIR_NAME, CONNECTOR_RUN_DIR_NAME);
22742
+ return join7(getConfigDir({ homeDir }), CONNECTOR_RUN_DIR_NAME);
22480
22743
  }
22481
22744
  function resolveConnectorPidPath(homeDir, agentName) {
22482
22745
  return join7(resolveConnectorRunDir(homeDir), `connector-${agentName}.pid`);
22483
22746
  }
22747
+ function resolveDetachedConnectorLogPath(homeDir, agentName, stream) {
22748
+ const suffix = stream === "stdout" ? CONNECTOR_DETACHED_STDOUT_FILE_SUFFIX : CONNECTOR_DETACHED_STDERR_FILE_SUFFIX;
22749
+ return join7(
22750
+ resolveConnectorRunDir(homeDir),
22751
+ `connector-${agentName}.${suffix}`
22752
+ );
22753
+ }
22484
22754
  async function readConnectorPidFile(pidPath) {
22485
22755
  try {
22486
22756
  const raw = (await readFile6(pidPath, "utf8")).trim();
@@ -22542,17 +22812,40 @@ async function startDetachedConnectorRuntime(input) {
22542
22812
  "--openclaw-base-url",
22543
22813
  input.openclawBaseUrl
22544
22814
  ];
22545
- const child = spawn(process.execPath, args, {
22546
- detached: true,
22547
- stdio: "ignore",
22548
- env: process.env
22549
- });
22550
- child.unref();
22551
- await writeSecureFile3(
22552
- resolveConnectorPidPath(input.homeDir, input.agentName),
22553
- `${child.pid}
22554
- `
22815
+ const stdoutLogPath = resolveDetachedConnectorLogPath(
22816
+ input.homeDir,
22817
+ input.agentName,
22818
+ "stdout"
22819
+ );
22820
+ const stderrLogPath = resolveDetachedConnectorLogPath(
22821
+ input.homeDir,
22822
+ input.agentName,
22823
+ "stderr"
22555
22824
  );
22825
+ const stdoutFd = openSync(stdoutLogPath, "a");
22826
+ const stderrFd = openSync(stderrLogPath, "a");
22827
+ try {
22828
+ const child = spawn(process.execPath, args, {
22829
+ detached: true,
22830
+ stdio: ["ignore", stdoutFd, stderrFd],
22831
+ env: process.env
22832
+ });
22833
+ child.unref();
22834
+ await writeSecureFile3(
22835
+ resolveConnectorPidPath(input.homeDir, input.agentName),
22836
+ `${child.pid}
22837
+ `
22838
+ );
22839
+ logger8.info("cli.openclaw.setup.detached_runtime_started", {
22840
+ agentName: input.agentName,
22841
+ pid: child.pid,
22842
+ stdoutLogPath,
22843
+ stderrLogPath
22844
+ });
22845
+ } finally {
22846
+ closeSync(stdoutFd);
22847
+ closeSync(stderrFd);
22848
+ }
22556
22849
  }
22557
22850
  async function startSetupConnectorRuntime(input) {
22558
22851
  if (input.mode !== "service") {
@@ -22833,14 +23126,18 @@ async function patchOpenclawConfig(openclawConfigPath, hookToken) {
22833
23126
  hooks,
22834
23127
  gateway
22835
23128
  };
22836
- await writeFile6(
22837
- openclawConfigPath,
22838
- `${JSON.stringify(nextConfig, null, 2)}
23129
+ const configChanged = JSON.stringify(config2) !== JSON.stringify(nextConfig);
23130
+ if (configChanged) {
23131
+ await writeFile6(
23132
+ openclawConfigPath,
23133
+ `${JSON.stringify(nextConfig, null, 2)}
22839
23134
  `,
22840
- "utf8"
22841
- );
23135
+ "utf8"
23136
+ );
23137
+ }
22842
23138
  return {
22843
- hookToken: resolvedHookToken
23139
+ hookToken: resolvedHookToken,
23140
+ configChanged
22844
23141
  };
22845
23142
  }
22846
23143
  function toDoctorCheck(input) {
@@ -22938,7 +23235,7 @@ function toSendToPeerEndpoint(openclawBaseUrl) {
22938
23235
  return new URL(OPENCLAW_SEND_TO_PEER_HOOK_PATH, normalizedBase).toString();
22939
23236
  }
22940
23237
  async function runOpenclawDoctor(options = {}) {
22941
- const homeDir = resolveHomeDir(options.homeDir);
23238
+ const homeDir = resolveHomeDir2(options.homeDir);
22942
23239
  const openclawDir = resolveOpenclawDir(options.openclawDir, homeDir);
22943
23240
  const peerAlias = parseDoctorPeerAlias(options.peerAlias);
22944
23241
  const checks = [];
@@ -23037,7 +23334,7 @@ async function runOpenclawDoctor(options = {}) {
23037
23334
  label: "CLI config",
23038
23335
  status: "fail",
23039
23336
  message: "unable to resolve CLI config",
23040
- remediationHint: "Fix ~/.clawdentity/config.json or rerun: clawdentity config init"
23337
+ remediationHint: "Run: clawdentity config init (or fix your CLI state config file)"
23041
23338
  })
23042
23339
  );
23043
23340
  }
@@ -23159,7 +23456,7 @@ async function runOpenclawDoctor(options = {}) {
23159
23456
  label: "Peers map",
23160
23457
  status: "fail",
23161
23458
  message: `invalid peers config at ${peersPath}`,
23162
- remediationHint: "Fix JSON in ~/.clawdentity/peers.json or rerun openclaw setup",
23459
+ remediationHint: `Fix JSON in ${peersPath} or rerun openclaw setup`,
23163
23460
  details: { peersPath }
23164
23461
  })
23165
23462
  );
@@ -23818,7 +24115,7 @@ async function resolveRelayProbePeerAlias(input) {
23818
24115
  );
23819
24116
  }
23820
24117
  async function runOpenclawRelayTest(options) {
23821
- const homeDir = resolveHomeDir(options.homeDir);
24118
+ const homeDir = resolveHomeDir2(options.homeDir);
23822
24119
  const openclawDir = resolveOpenclawDir(options.openclawDir, homeDir);
23823
24120
  const checkedAt = nowIso();
23824
24121
  let peerAlias;
@@ -23950,7 +24247,7 @@ async function runOpenclawRelayTest(options) {
23950
24247
  }
23951
24248
  async function setupOpenclawRelay(agentName, options) {
23952
24249
  const normalizedAgentName = assertValidAgentName(agentName);
23953
- const homeDir = resolveHomeDir(options.homeDir);
24250
+ const homeDir = resolveHomeDir2(options.homeDir);
23954
24251
  const openclawDir = resolveOpenclawDir(options.openclawDir, homeDir);
23955
24252
  const openclawConfigPath = resolveOpenclawConfigPath(openclawDir, homeDir);
23956
24253
  const transformSource = typeof options.transformSource === "string" && options.transformSource.trim().length > 0 ? options.transformSource.trim() : resolveDefaultTransformSource(openclawDir);
@@ -24053,7 +24350,8 @@ async function setupOpenclawRelay(agentName, options) {
24053
24350
  relayTransformPeersPath,
24054
24351
  openclawBaseUrl,
24055
24352
  connectorBaseUrl,
24056
- relayRuntimeConfigPath
24353
+ relayRuntimeConfigPath,
24354
+ openclawConfigChanged: patchedOpenclawConfig.configChanged
24057
24355
  };
24058
24356
  }
24059
24357
  async function assertSetupChecklistHealthy(input) {
@@ -24121,12 +24419,13 @@ async function assertSetupChecklistHealthy(input) {
24121
24419
  );
24122
24420
  }
24123
24421
  async function setupOpenclawSelfReady(agentName, options) {
24124
- const resolvedHomeDir = resolveHomeDir(options.homeDir);
24422
+ const normalizedAgentName = assertValidAgentName(agentName);
24423
+ const resolvedHomeDir = resolveHomeDir2(options.homeDir);
24125
24424
  const resolvedOpenclawDir = resolveOpenclawDir(
24126
24425
  options.openclawDir,
24127
24426
  resolvedHomeDir
24128
24427
  );
24129
- const setup = await setupOpenclawRelay(agentName, {
24428
+ const setup = await setupOpenclawRelay(normalizedAgentName, {
24130
24429
  ...options,
24131
24430
  homeDir: resolvedHomeDir,
24132
24431
  openclawDir: resolvedOpenclawDir
@@ -24156,8 +24455,8 @@ async function setupOpenclawSelfReady(agentName, options) {
24156
24455
  const waitTimeoutSeconds = parseWaitTimeoutSeconds(
24157
24456
  options.waitTimeoutSeconds
24158
24457
  );
24159
- const runtime = await startSetupConnectorRuntime({
24160
- agentName: assertValidAgentName(agentName),
24458
+ let runtime = await startSetupConnectorRuntime({
24459
+ agentName: normalizedAgentName,
24161
24460
  homeDir: resolvedHomeDir,
24162
24461
  openclawBaseUrl: setup.openclawBaseUrl,
24163
24462
  connectorBaseUrl: setup.connectorBaseUrl,
@@ -24171,6 +24470,44 @@ async function setupOpenclawSelfReady(agentName, options) {
24171
24470
  includeConnectorRuntimeCheck: true,
24172
24471
  gatewayDeviceApprovalRunner: options.gatewayDeviceApprovalRunner
24173
24472
  });
24473
+ const requiresStabilityGuard = setup.openclawConfigChanged && (runtime.runtimeMode === "existing" || runtime.runtimeMode === "detached");
24474
+ if (requiresStabilityGuard) {
24475
+ const stabilityWindowSeconds = Math.min(
24476
+ waitTimeoutSeconds,
24477
+ OPENCLAW_SETUP_STABILITY_WINDOW_SECONDS
24478
+ );
24479
+ const stableStatus = await monitorConnectorStabilityWindow({
24480
+ connectorBaseUrl: setup.connectorBaseUrl,
24481
+ fetchImpl,
24482
+ durationSeconds: stabilityWindowSeconds,
24483
+ pollIntervalMs: OPENCLAW_SETUP_STABILITY_POLL_INTERVAL_MS
24484
+ });
24485
+ if (!stableStatus.connected) {
24486
+ logger8.warn("cli.openclaw.setup.connector_dropped_post_config_change", {
24487
+ agentName: normalizedAgentName,
24488
+ connectorBaseUrl: setup.connectorBaseUrl,
24489
+ connectorStatusUrl: stableStatus.statusUrl,
24490
+ reason: stableStatus.reason,
24491
+ previousRuntimeMode: runtime.runtimeMode,
24492
+ stabilityWindowSeconds
24493
+ });
24494
+ runtime = await startSetupConnectorRuntime({
24495
+ agentName: normalizedAgentName,
24496
+ homeDir: resolvedHomeDir,
24497
+ openclawBaseUrl: setup.openclawBaseUrl,
24498
+ connectorBaseUrl: setup.connectorBaseUrl,
24499
+ mode: resolvedMode,
24500
+ waitTimeoutSeconds,
24501
+ fetchImpl
24502
+ });
24503
+ await assertSetupChecklistHealthy({
24504
+ homeDir: resolvedHomeDir,
24505
+ openclawDir: resolvedOpenclawDir,
24506
+ includeConnectorRuntimeCheck: true,
24507
+ gatewayDeviceApprovalRunner: options.gatewayDeviceApprovalRunner
24508
+ });
24509
+ }
24510
+ }
24174
24511
  return {
24175
24512
  ...setup,
24176
24513
  ...runtime
@@ -24256,7 +24593,7 @@ var createOpenclawCommand = () => {
24256
24593
  const relayCommand = openclawCommand.command("relay").description("Run OpenClaw relay diagnostics");
24257
24594
  relayCommand.command("test").description(
24258
24595
  "Send a relay probe to a configured peer (auto-selects when one peer exists)"
24259
- ).option("--peer <alias>", "Peer alias in ~/.clawdentity/peers.json").option(
24596
+ ).option("--peer <alias>", "Peer alias in local peers map").option(
24260
24597
  "--openclaw-base-url <url>",
24261
24598
  "Base URL for local OpenClaw hook API (default OPENCLAW_BASE_URL or relay runtime config)"
24262
24599
  ).option(
@@ -24300,7 +24637,7 @@ import { randomBytes as randomBytes4 } from "crypto";
24300
24637
  import {
24301
24638
  chmod as chmod4,
24302
24639
  mkdir as mkdir7,
24303
- readdir,
24640
+ readdir as readdir2,
24304
24641
  readFile as readFile7,
24305
24642
  unlink as unlink2,
24306
24643
  writeFile as writeFile7
@@ -24339,7 +24676,7 @@ function createCliError7(code, message2) {
24339
24676
  status: 400
24340
24677
  });
24341
24678
  }
24342
- function parseNonEmptyString9(value) {
24679
+ function parseNonEmptyString10(value) {
24343
24680
  if (typeof value !== "string") {
24344
24681
  return "";
24345
24682
  }
@@ -24355,7 +24692,7 @@ function hasControlChars2(value) {
24355
24692
  return false;
24356
24693
  }
24357
24694
  function parseProfileName(value, label) {
24358
- const candidate = parseNonEmptyString9(value);
24695
+ const candidate = parseNonEmptyString10(value);
24359
24696
  if (candidate.length === 0) {
24360
24697
  throw createCliError7(
24361
24698
  "CLI_PAIR_PROFILE_INVALID",
@@ -24389,7 +24726,7 @@ function parsePeerProfile(payload) {
24389
24726
  };
24390
24727
  }
24391
24728
  function parsePairingTicket(value) {
24392
- let ticket = parseNonEmptyString9(value);
24729
+ let ticket = parseNonEmptyString10(value);
24393
24730
  while (ticket.startsWith("`")) {
24394
24731
  ticket = ticket.slice(1);
24395
24732
  }
@@ -24579,16 +24916,16 @@ function parsePeerEntry(value) {
24579
24916
  "Peer entry must be an object"
24580
24917
  );
24581
24918
  }
24582
- const did = parseNonEmptyString9(value.did);
24583
- const proxyUrl = parseNonEmptyString9(value.proxyUrl);
24919
+ const did = parseNonEmptyString10(value.did);
24920
+ const proxyUrl = parseNonEmptyString10(value.proxyUrl);
24584
24921
  if (did.length === 0 || proxyUrl.length === 0) {
24585
24922
  throw createCliError7(
24586
24923
  "CLI_PAIR_PEERS_CONFIG_INVALID",
24587
24924
  "Peer entry is invalid"
24588
24925
  );
24589
24926
  }
24590
- const agentNameRaw = parseNonEmptyString9(value.agentName);
24591
- const humanNameRaw = parseNonEmptyString9(value.humanName);
24927
+ const agentNameRaw = parseNonEmptyString10(value.agentName);
24928
+ const humanNameRaw = parseNonEmptyString10(value.humanName);
24592
24929
  const entry = {
24593
24930
  did,
24594
24931
  proxyUrl
@@ -24687,7 +25024,7 @@ async function loadRelayTransformPeersPath(input) {
24687
25024
  if (!isRecord10(parsed)) {
24688
25025
  return void 0;
24689
25026
  }
24690
- const relayTransformPeersPath = parseNonEmptyString9(
25027
+ const relayTransformPeersPath = parseNonEmptyString10(
24691
25028
  parsed.relayTransformPeersPath
24692
25029
  );
24693
25030
  if (relayTransformPeersPath.length === 0) {
@@ -24735,7 +25072,7 @@ async function syncOpenclawRelayPeersSnapshot(input) {
24735
25072
  }
24736
25073
  }
24737
25074
  function parseTtlSeconds(value) {
24738
- const raw = parseNonEmptyString9(value);
25075
+ const raw = parseNonEmptyString10(value);
24739
25076
  if (raw.length === 0) {
24740
25077
  return void 0;
24741
25078
  }
@@ -24749,7 +25086,7 @@ function parseTtlSeconds(value) {
24749
25086
  return parsed;
24750
25087
  }
24751
25088
  function parsePositiveIntegerOption(input) {
24752
- const raw = parseNonEmptyString9(input.value);
25089
+ const raw = parseNonEmptyString10(input.value);
24753
25090
  if (raw.length === 0) {
24754
25091
  return input.defaultValue;
24755
25092
  }
@@ -24763,7 +25100,7 @@ function parsePositiveIntegerOption(input) {
24763
25100
  return parsed;
24764
25101
  }
24765
25102
  function resolveLocalPairProfile(input) {
24766
- const humanName = parseNonEmptyString9(input.config.humanName);
25103
+ const humanName = parseNonEmptyString10(input.config.humanName);
24767
25104
  if (humanName.length === 0) {
24768
25105
  throw createCliError7(
24769
25106
  "CLI_PAIR_HUMAN_NAME_MISSING",
@@ -24787,7 +25124,7 @@ function parseProxyUrl2(candidate) {
24787
25124
  }
24788
25125
  }
24789
25126
  async function resolveProxyUrl(input) {
24790
- const fromEnv = parseNonEmptyString9(process.env.CLAWDENTITY_PROXY_URL);
25127
+ const fromEnv = parseNonEmptyString10(process.env.CLAWDENTITY_PROXY_URL);
24791
25128
  if (fromEnv.length > 0) {
24792
25129
  return parseProxyUrl2(fromEnv);
24793
25130
  }
@@ -24795,7 +25132,7 @@ async function resolveProxyUrl(input) {
24795
25132
  fetchImpl: input.fetchImpl
24796
25133
  });
24797
25134
  const metadataProxyUrl = parseProxyUrl2(metadata.proxyUrl);
24798
- const configuredProxyUrl = parseNonEmptyString9(input.config.proxyUrl);
25135
+ const configuredProxyUrl = parseNonEmptyString10(input.config.proxyUrl);
24799
25136
  if (configuredProxyUrl.length === 0) {
24800
25137
  return metadataProxyUrl;
24801
25138
  }
@@ -24935,8 +25272,8 @@ function parsePairStartResponse(payload) {
24935
25272
  );
24936
25273
  }
24937
25274
  const ticket = parsePairingTicket(payload.ticket);
24938
- const initiatorAgentDid = parseNonEmptyString9(payload.initiatorAgentDid);
24939
- const expiresAt = parseNonEmptyString9(payload.expiresAt);
25275
+ const initiatorAgentDid = parseNonEmptyString10(payload.initiatorAgentDid);
25276
+ const expiresAt = parseNonEmptyString10(payload.expiresAt);
24940
25277
  let initiatorProfile;
24941
25278
  if (initiatorAgentDid.length === 0 || expiresAt.length === 0) {
24942
25279
  throw createCliError7(
@@ -24967,8 +25304,8 @@ function parsePairConfirmResponse(payload) {
24967
25304
  );
24968
25305
  }
24969
25306
  const paired = payload.paired === true;
24970
- const initiatorAgentDid = parseNonEmptyString9(payload.initiatorAgentDid);
24971
- const responderAgentDid = parseNonEmptyString9(payload.responderAgentDid);
25307
+ const initiatorAgentDid = parseNonEmptyString10(payload.initiatorAgentDid);
25308
+ const responderAgentDid = parseNonEmptyString10(payload.responderAgentDid);
24972
25309
  let initiatorProfile;
24973
25310
  let responderProfile;
24974
25311
  if (!paired || initiatorAgentDid.length === 0 || responderAgentDid.length === 0) {
@@ -25001,17 +25338,17 @@ function parsePairStatusResponse(payload) {
25001
25338
  "Pair status response is invalid"
25002
25339
  );
25003
25340
  }
25004
- const statusRaw = parseNonEmptyString9(payload.status);
25341
+ const statusRaw = parseNonEmptyString10(payload.status);
25005
25342
  if (statusRaw !== "pending" && statusRaw !== "confirmed") {
25006
25343
  throw createCliError7(
25007
25344
  "CLI_PAIR_STATUS_INVALID_RESPONSE",
25008
25345
  "Pair status response is invalid"
25009
25346
  );
25010
25347
  }
25011
- const initiatorAgentDid = parseNonEmptyString9(payload.initiatorAgentDid);
25012
- const responderAgentDid = parseNonEmptyString9(payload.responderAgentDid);
25013
- const expiresAt = parseNonEmptyString9(payload.expiresAt);
25014
- const confirmedAt = parseNonEmptyString9(payload.confirmedAt);
25348
+ const initiatorAgentDid = parseNonEmptyString10(payload.initiatorAgentDid);
25349
+ const responderAgentDid = parseNonEmptyString10(payload.responderAgentDid);
25350
+ const expiresAt = parseNonEmptyString10(payload.expiresAt);
25351
+ const confirmedAt = parseNonEmptyString10(payload.confirmedAt);
25015
25352
  let initiatorProfile;
25016
25353
  if (initiatorAgentDid.length === 0 || expiresAt.length === 0) {
25017
25354
  throw createCliError7(
@@ -25159,7 +25496,7 @@ function decodeTicketFromPng(imageBytes) {
25159
25496
  decodedPng.data.byteLength
25160
25497
  );
25161
25498
  const decoded = jsQR(imageData, decodedPng.width, decodedPng.height);
25162
- if (!decoded || parseNonEmptyString9(decoded.data).length === 0) {
25499
+ if (!decoded || parseNonEmptyString10(decoded.data).length === 0) {
25163
25500
  throw createCliError7(
25164
25501
  "CLI_PAIR_CONFIRM_QR_NOT_FOUND",
25165
25502
  "No pairing QR code was found in the image"
@@ -25169,13 +25506,13 @@ function decodeTicketFromPng(imageBytes) {
25169
25506
  }
25170
25507
  async function persistPairingQr(input) {
25171
25508
  const mkdirImpl = input.dependencies.mkdirImpl ?? mkdir7;
25172
- const readdirImpl = input.dependencies.readdirImpl ?? readdir;
25509
+ const readdirImpl = input.dependencies.readdirImpl ?? readdir2;
25173
25510
  const unlinkImpl = input.dependencies.unlinkImpl ?? unlink2;
25174
25511
  const writeFileImpl = input.dependencies.writeFileImpl ?? writeFile7;
25175
25512
  const getConfigDirImpl = input.dependencies.getConfigDirImpl ?? getConfigDir;
25176
25513
  const qrEncodeImpl = input.dependencies.qrEncodeImpl ?? encodeTicketQrPng;
25177
25514
  const baseDir = join8(getConfigDirImpl(), PAIRING_QR_DIR_NAME);
25178
- const outputPath = parseNonEmptyString9(input.qrOutput) ? resolve(input.qrOutput ?? "") : join8(
25515
+ const outputPath = parseNonEmptyString10(input.qrOutput) ? resolve(input.qrOutput ?? "") : join8(
25179
25516
  baseDir,
25180
25517
  `${assertValidAgentName(input.agentName)}-pair-${input.nowSeconds}.png`
25181
25518
  );
@@ -25216,8 +25553,8 @@ async function persistPairingQr(input) {
25216
25553
  return outputPath;
25217
25554
  }
25218
25555
  function resolveConfirmTicketSource(options) {
25219
- const inlineTicket = parseNonEmptyString9(options.ticket);
25220
- const qrFile = parseNonEmptyString9(options.qrFile);
25556
+ const inlineTicket = parseNonEmptyString10(options.ticket);
25557
+ const qrFile = parseNonEmptyString10(options.qrFile);
25221
25558
  if (inlineTicket.length > 0 && qrFile.length > 0) {
25222
25559
  throw createCliError7(
25223
25560
  "CLI_PAIR_CONFIRM_INPUT_CONFLICT",
@@ -25587,7 +25924,7 @@ async function waitForPairingStatus(input) {
25587
25924
  }
25588
25925
  }
25589
25926
  async function getPairingStatus(agentName, options, dependencies = {}) {
25590
- const ticketRaw = parseNonEmptyString9(options.ticket);
25927
+ const ticketRaw = parseNonEmptyString10(options.ticket);
25591
25928
  if (ticketRaw.length === 0) {
25592
25929
  throw createCliError7(
25593
25930
  "CLI_PAIR_STATUS_TICKET_REQUIRED",
@@ -25798,7 +26135,7 @@ import { Command as Command9 } from "commander";
25798
26135
 
25799
26136
  // src/install-skill-mode.ts
25800
26137
  import { constants, existsSync as existsSync2 } from "fs";
25801
- import { access as access3, copyFile as copyFile2, mkdir as mkdir8, readdir as readdir2, readFile as readFile8 } from "fs/promises";
26138
+ import { access as access3, copyFile as copyFile2, mkdir as mkdir8, readdir as readdir3, readFile as readFile8 } from "fs/promises";
25802
26139
  import { createRequire } from "module";
25803
26140
  import { homedir as homedir4 } from "os";
25804
26141
  import { dirname as dirname7, join as join9, relative } from "path";
@@ -25826,7 +26163,7 @@ function getErrorCode3(error48) {
25826
26163
  }
25827
26164
  return typeof error48.code === "string" ? error48.code : void 0;
25828
26165
  }
25829
- function resolveHomeDir2(inputHomeDir) {
26166
+ function resolveHomeDir3(inputHomeDir) {
25830
26167
  if (typeof inputHomeDir === "string" && inputHomeDir.trim().length > 0) {
25831
26168
  return inputHomeDir.trim();
25832
26169
  }
@@ -25899,7 +26236,7 @@ async function assertReadableFile(filePath, details) {
25899
26236
  }
25900
26237
  }
25901
26238
  async function listFilesRecursively(directoryPath) {
25902
- const entries = await readdir2(directoryPath, { withFileTypes: true });
26239
+ const entries = await readdir3(directoryPath, { withFileTypes: true });
25903
26240
  const files = [];
25904
26241
  for (const entry of entries.sort(
25905
26242
  (left, right) => left.name.localeCompare(right.name)
@@ -26008,7 +26345,7 @@ async function copyArtifact(input) {
26008
26345
  }
26009
26346
  async function installOpenclawSkillArtifacts(options = {}) {
26010
26347
  const env = options.env ?? process.env;
26011
- const homeDir = resolveHomeDir2(options.homeDir);
26348
+ const homeDir = resolveHomeDir3(options.homeDir);
26012
26349
  const openclawDir = resolveOpenclawDir2(homeDir, options.openclawDir);
26013
26350
  const skillPackageRoot = resolveSkillPackageRoot({
26014
26351
  skillPackageRoot: options.skillPackageRoot,