clawdentity 0.0.19 → 0.0.20

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
@@ -18656,9 +18656,9 @@ var createConfigCommand = (dependencies = {}) => {
18656
18656
 
18657
18657
  // src/commands/connector.ts
18658
18658
  import { execFile as execFileCallback } from "child_process";
18659
- import { mkdir as mkdir4, readFile as readFile4, rm, writeFile as writeFile4 } from "fs/promises";
18659
+ import { mkdir as mkdir5, readFile as readFile5, rm, writeFile as writeFile5 } from "fs/promises";
18660
18660
  import { homedir as homedir2 } from "os";
18661
- import { dirname as dirname3, join as join5 } from "path";
18661
+ import { dirname as dirname4, join as join6 } from "path";
18662
18662
  import { fileURLToPath } from "url";
18663
18663
  import { promisify } from "util";
18664
18664
 
@@ -18681,6 +18681,13 @@ var DEFAULT_CONNECTOR_OUTBOUND_PATH = "/v1/outbound";
18681
18681
  var DEFAULT_CONNECTOR_STATUS_PATH = "/v1/status";
18682
18682
  var DEFAULT_RELAY_DELIVER_TIMEOUT_MS = 15e3;
18683
18683
  var DEFAULT_OPENCLAW_DELIVER_RETRY_BUDGET_MS = DEFAULT_RELAY_DELIVER_TIMEOUT_MS - 1e3;
18684
+ var DEFAULT_CONNECTOR_INBOUND_INBOX_MAX_MESSAGES = 1e5;
18685
+ var DEFAULT_CONNECTOR_INBOUND_INBOX_MAX_BYTES = 2 * 1024 * 1024 * 1024;
18686
+ var DEFAULT_CONNECTOR_INBOUND_REPLAY_INTERVAL_MS = 2e3;
18687
+ var DEFAULT_CONNECTOR_INBOUND_REPLAY_BATCH_SIZE = 25;
18688
+ var DEFAULT_CONNECTOR_INBOUND_RETRY_INITIAL_DELAY_MS = 1e3;
18689
+ var DEFAULT_CONNECTOR_INBOUND_RETRY_MAX_DELAY_MS = 6e4;
18690
+ var DEFAULT_CONNECTOR_INBOUND_RETRY_BACKOFF_FACTOR = 2;
18684
18691
  var AGENT_ACCESS_HEADER = "x-claw-agent-access";
18685
18692
  var WS_READY_STATE_OPEN = 1;
18686
18693
 
@@ -18942,6 +18949,7 @@ var ConnectorClient = class {
18942
18949
  fetchImpl;
18943
18950
  logger;
18944
18951
  hooks;
18952
+ inboundDeliverHandler;
18945
18953
  now;
18946
18954
  random;
18947
18955
  ulidFactory;
@@ -18996,6 +19004,7 @@ var ConnectorClient = class {
18996
19004
  this.fetchImpl = options.fetchImpl ?? fetch;
18997
19005
  this.logger = options.logger ?? createLogger({ service: "connector", module: "client" });
18998
19006
  this.hooks = options.hooks ?? {};
19007
+ this.inboundDeliverHandler = options.inboundDeliverHandler;
18999
19008
  this.now = options.now ?? Date.now;
19000
19009
  this.random = options.random ?? Math.random;
19001
19010
  this.ulidFactory = options.ulidFactory ?? generateUlid;
@@ -19208,6 +19217,44 @@ var ConnectorClient = class {
19208
19217
  this.sendFrame(ackFrame);
19209
19218
  }
19210
19219
  async handleDeliverFrame(frame) {
19220
+ if (this.inboundDeliverHandler !== void 0) {
19221
+ try {
19222
+ const result = await this.inboundDeliverHandler(frame);
19223
+ const ackFrame = {
19224
+ v: CONNECTOR_FRAME_VERSION,
19225
+ type: "deliver_ack",
19226
+ id: this.makeFrameId(),
19227
+ ts: this.makeTimestamp(),
19228
+ ackId: frame.id,
19229
+ accepted: result.accepted,
19230
+ reason: result.reason
19231
+ };
19232
+ this.sendFrame(ackFrame);
19233
+ if (result.accepted) {
19234
+ this.hooks.onDeliverSucceeded?.(frame);
19235
+ } else {
19236
+ this.hooks.onDeliverFailed?.(
19237
+ frame,
19238
+ new Error(
19239
+ result.reason ?? "Inbound delivery was rejected by runtime handler"
19240
+ )
19241
+ );
19242
+ }
19243
+ } catch (error48) {
19244
+ const ackFrame = {
19245
+ v: CONNECTOR_FRAME_VERSION,
19246
+ type: "deliver_ack",
19247
+ id: this.makeFrameId(),
19248
+ ts: this.makeTimestamp(),
19249
+ ackId: frame.id,
19250
+ accepted: false,
19251
+ reason: sanitizeErrorReason(error48)
19252
+ };
19253
+ this.sendFrame(ackFrame);
19254
+ this.hooks.onDeliverFailed?.(frame, error48);
19255
+ }
19256
+ return;
19257
+ }
19211
19258
  try {
19212
19259
  await this.deliverToLocalOpenclawWithRetry(frame);
19213
19260
  const ackFrame = {
@@ -19323,13 +19370,353 @@ var ConnectorClient = class {
19323
19370
  }
19324
19371
  };
19325
19372
 
19373
+ // ../../packages/connector/src/inbound-inbox.ts
19374
+ import {
19375
+ appendFile,
19376
+ mkdir as mkdir3,
19377
+ readFile as readFile3,
19378
+ rename as rename2,
19379
+ writeFile as writeFile3
19380
+ } from "fs/promises";
19381
+ import { dirname as dirname2, join as join4 } from "path";
19382
+ var INBOUND_INBOX_DIR_NAME = "inbound-inbox";
19383
+ var INBOUND_INBOX_INDEX_FILE_NAME = "index.json";
19384
+ var INBOUND_INBOX_EVENTS_FILE_NAME = "events.jsonl";
19385
+ var INBOUND_INBOX_SCHEMA_VERSION = 1;
19386
+ function nowIso2() {
19387
+ return (/* @__PURE__ */ new Date()).toISOString();
19388
+ }
19389
+ function isRecord5(value) {
19390
+ return typeof value === "object" && value !== null;
19391
+ }
19392
+ function parsePendingItem(value) {
19393
+ if (!isRecord5(value)) {
19394
+ return void 0;
19395
+ }
19396
+ const id = typeof value.id === "string" ? value.id.trim() : "";
19397
+ const requestId = typeof value.requestId === "string" ? value.requestId.trim() : "";
19398
+ const fromAgentDid = typeof value.fromAgentDid === "string" ? value.fromAgentDid.trim() : "";
19399
+ const toAgentDid = typeof value.toAgentDid === "string" ? value.toAgentDid.trim() : "";
19400
+ const receivedAt = typeof value.receivedAt === "string" ? value.receivedAt.trim() : "";
19401
+ const nextAttemptAt = typeof value.nextAttemptAt === "string" ? value.nextAttemptAt.trim() : "";
19402
+ const attemptCount = typeof value.attemptCount === "number" && Number.isInteger(value.attemptCount) ? value.attemptCount : NaN;
19403
+ const payloadBytes = typeof value.payloadBytes === "number" && Number.isInteger(value.payloadBytes) ? value.payloadBytes : NaN;
19404
+ if (id.length === 0 || requestId.length === 0 || fromAgentDid.length === 0 || toAgentDid.length === 0 || receivedAt.length === 0 || nextAttemptAt.length === 0 || !Number.isFinite(attemptCount) || attemptCount < 0 || !Number.isFinite(payloadBytes) || payloadBytes < 0) {
19405
+ return void 0;
19406
+ }
19407
+ const lastError = typeof value.lastError === "string" ? value.lastError : void 0;
19408
+ const lastAttemptAt = typeof value.lastAttemptAt === "string" ? value.lastAttemptAt : void 0;
19409
+ return {
19410
+ id,
19411
+ requestId,
19412
+ fromAgentDid,
19413
+ toAgentDid,
19414
+ payload: value.payload,
19415
+ payloadBytes,
19416
+ receivedAt,
19417
+ nextAttemptAt,
19418
+ attemptCount,
19419
+ lastError,
19420
+ lastAttemptAt
19421
+ };
19422
+ }
19423
+ function toDefaultIndexFile() {
19424
+ return {
19425
+ version: INBOUND_INBOX_SCHEMA_VERSION,
19426
+ pendingBytes: 0,
19427
+ pendingByRequestId: {},
19428
+ updatedAt: nowIso2()
19429
+ };
19430
+ }
19431
+ function normalizeIndexFile(raw) {
19432
+ if (!isRecord5(raw)) {
19433
+ throw new Error("Inbound inbox index root must be an object");
19434
+ }
19435
+ const pendingByRequestIdRaw = raw.pendingByRequestId;
19436
+ if (!isRecord5(pendingByRequestIdRaw)) {
19437
+ throw new Error("Inbound inbox index pendingByRequestId must be an object");
19438
+ }
19439
+ const pendingByRequestId = {};
19440
+ let pendingBytes = 0;
19441
+ for (const [requestId, candidate] of Object.entries(pendingByRequestIdRaw)) {
19442
+ const entry = parsePendingItem(candidate);
19443
+ if (entry === void 0 || entry.requestId !== requestId) {
19444
+ continue;
19445
+ }
19446
+ pendingByRequestId[requestId] = entry;
19447
+ pendingBytes += entry.payloadBytes;
19448
+ }
19449
+ return {
19450
+ version: typeof raw.version === "number" && Number.isFinite(raw.version) ? raw.version : INBOUND_INBOX_SCHEMA_VERSION,
19451
+ pendingBytes,
19452
+ pendingByRequestId,
19453
+ updatedAt: typeof raw.updatedAt === "string" && raw.updatedAt.trim().length > 0 ? raw.updatedAt : nowIso2()
19454
+ };
19455
+ }
19456
+ function toComparableTimeMs(value) {
19457
+ const parsed = Date.parse(value);
19458
+ if (Number.isFinite(parsed)) {
19459
+ return parsed;
19460
+ }
19461
+ return Number.MAX_SAFE_INTEGER;
19462
+ }
19463
+ var ConnectorInboundInbox = class {
19464
+ agentName;
19465
+ eventsPath;
19466
+ indexPath;
19467
+ maxPendingBytes;
19468
+ maxPendingMessages;
19469
+ inboxDir;
19470
+ writeChain = Promise.resolve();
19471
+ constructor(options) {
19472
+ this.agentName = options.agentName;
19473
+ this.inboxDir = join4(
19474
+ options.configDir,
19475
+ "agents",
19476
+ this.agentName,
19477
+ INBOUND_INBOX_DIR_NAME
19478
+ );
19479
+ this.indexPath = join4(this.inboxDir, INBOUND_INBOX_INDEX_FILE_NAME);
19480
+ this.eventsPath = join4(this.inboxDir, INBOUND_INBOX_EVENTS_FILE_NAME);
19481
+ this.maxPendingBytes = options.maxPendingBytes;
19482
+ this.maxPendingMessages = options.maxPendingMessages;
19483
+ }
19484
+ async enqueue(frame) {
19485
+ return await this.withWriteLock(async () => {
19486
+ const index = await this.loadIndex();
19487
+ const existing = index.pendingByRequestId[frame.id];
19488
+ if (existing !== void 0) {
19489
+ await this.appendEvent({
19490
+ type: "inbound_duplicate",
19491
+ requestId: frame.id
19492
+ });
19493
+ return {
19494
+ accepted: true,
19495
+ duplicate: true,
19496
+ pendingCount: Object.keys(index.pendingByRequestId).length
19497
+ };
19498
+ }
19499
+ const payloadBytes = Buffer.byteLength(
19500
+ JSON.stringify(frame.payload ?? null),
19501
+ "utf8"
19502
+ );
19503
+ const pendingCount = Object.keys(index.pendingByRequestId).length;
19504
+ if (pendingCount >= this.maxPendingMessages) {
19505
+ return {
19506
+ accepted: false,
19507
+ duplicate: false,
19508
+ pendingCount,
19509
+ reason: "connector inbound inbox is full (message cap reached)"
19510
+ };
19511
+ }
19512
+ if (index.pendingBytes + payloadBytes > this.maxPendingBytes) {
19513
+ return {
19514
+ accepted: false,
19515
+ duplicate: false,
19516
+ pendingCount,
19517
+ reason: "connector inbound inbox is full (byte cap reached)"
19518
+ };
19519
+ }
19520
+ const pendingItem = {
19521
+ id: frame.id,
19522
+ requestId: frame.id,
19523
+ fromAgentDid: frame.fromAgentDid,
19524
+ toAgentDid: frame.toAgentDid,
19525
+ payload: frame.payload,
19526
+ payloadBytes,
19527
+ receivedAt: nowIso2(),
19528
+ nextAttemptAt: nowIso2(),
19529
+ attemptCount: 0
19530
+ };
19531
+ index.pendingByRequestId[pendingItem.requestId] = pendingItem;
19532
+ index.pendingBytes += pendingItem.payloadBytes;
19533
+ index.updatedAt = nowIso2();
19534
+ await this.saveIndex(index);
19535
+ await this.appendEvent({
19536
+ type: "inbound_persisted",
19537
+ requestId: pendingItem.requestId,
19538
+ details: {
19539
+ payloadBytes,
19540
+ fromAgentDid: pendingItem.fromAgentDid,
19541
+ toAgentDid: pendingItem.toAgentDid
19542
+ }
19543
+ });
19544
+ return {
19545
+ accepted: true,
19546
+ duplicate: false,
19547
+ pendingCount: Object.keys(index.pendingByRequestId).length
19548
+ };
19549
+ });
19550
+ }
19551
+ async listDuePending(input) {
19552
+ const index = await this.loadIndex();
19553
+ const due = Object.values(index.pendingByRequestId).filter((item) => toComparableTimeMs(item.nextAttemptAt) <= input.nowMs).sort((left, right) => {
19554
+ const leftNext = toComparableTimeMs(left.nextAttemptAt);
19555
+ const rightNext = toComparableTimeMs(right.nextAttemptAt);
19556
+ if (leftNext !== rightNext) {
19557
+ return leftNext - rightNext;
19558
+ }
19559
+ return toComparableTimeMs(left.receivedAt) - toComparableTimeMs(right.receivedAt);
19560
+ });
19561
+ return due.slice(0, Math.max(1, input.limit));
19562
+ }
19563
+ async markDelivered(requestId) {
19564
+ await this.withWriteLock(async () => {
19565
+ const index = await this.loadIndex();
19566
+ const entry = index.pendingByRequestId[requestId];
19567
+ if (entry === void 0) {
19568
+ return;
19569
+ }
19570
+ delete index.pendingByRequestId[requestId];
19571
+ index.pendingBytes = Math.max(0, index.pendingBytes - entry.payloadBytes);
19572
+ index.updatedAt = nowIso2();
19573
+ await this.saveIndex(index);
19574
+ await this.appendEvent({
19575
+ type: "replay_succeeded",
19576
+ requestId
19577
+ });
19578
+ });
19579
+ }
19580
+ async markReplayFailure(input) {
19581
+ await this.withWriteLock(async () => {
19582
+ const index = await this.loadIndex();
19583
+ const entry = index.pendingByRequestId[input.requestId];
19584
+ if (entry === void 0) {
19585
+ return;
19586
+ }
19587
+ entry.attemptCount += 1;
19588
+ entry.lastError = input.errorMessage;
19589
+ entry.lastAttemptAt = nowIso2();
19590
+ entry.nextAttemptAt = input.nextAttemptAt;
19591
+ index.updatedAt = nowIso2();
19592
+ await this.saveIndex(index);
19593
+ await this.appendEvent({
19594
+ type: "replay_failed",
19595
+ requestId: input.requestId,
19596
+ details: {
19597
+ attemptCount: entry.attemptCount,
19598
+ nextAttemptAt: input.nextAttemptAt,
19599
+ errorMessage: input.errorMessage
19600
+ }
19601
+ });
19602
+ });
19603
+ }
19604
+ async pruneDelivered() {
19605
+ await this.withWriteLock(async () => {
19606
+ const index = await this.loadIndex();
19607
+ const beforeCount = Object.keys(index.pendingByRequestId).length;
19608
+ if (beforeCount === 0) {
19609
+ return;
19610
+ }
19611
+ const after = {};
19612
+ let pendingBytes = 0;
19613
+ for (const [requestId, entry] of Object.entries(
19614
+ index.pendingByRequestId
19615
+ )) {
19616
+ if (entry.attemptCount < 0) {
19617
+ continue;
19618
+ }
19619
+ after[requestId] = entry;
19620
+ pendingBytes += entry.payloadBytes;
19621
+ }
19622
+ index.pendingByRequestId = after;
19623
+ index.pendingBytes = pendingBytes;
19624
+ index.updatedAt = nowIso2();
19625
+ await this.saveIndex(index);
19626
+ await this.appendEvent({
19627
+ type: "inbox_pruned",
19628
+ details: {
19629
+ beforeCount,
19630
+ afterCount: Object.keys(after).length
19631
+ }
19632
+ });
19633
+ });
19634
+ }
19635
+ async getSnapshot() {
19636
+ const index = await this.loadIndex();
19637
+ const entries = Object.values(index.pendingByRequestId);
19638
+ if (entries.length === 0) {
19639
+ return {
19640
+ pendingCount: 0,
19641
+ pendingBytes: index.pendingBytes
19642
+ };
19643
+ }
19644
+ entries.sort((left, right) => {
19645
+ return toComparableTimeMs(left.receivedAt) - toComparableTimeMs(right.receivedAt);
19646
+ });
19647
+ const nextAttemptAt = entries.map((entry) => entry.nextAttemptAt).sort(
19648
+ (left, right) => toComparableTimeMs(left) - toComparableTimeMs(right)
19649
+ )[0];
19650
+ return {
19651
+ pendingCount: entries.length,
19652
+ pendingBytes: index.pendingBytes,
19653
+ oldestPendingAt: entries[0]?.receivedAt,
19654
+ nextAttemptAt
19655
+ };
19656
+ }
19657
+ async withWriteLock(fn) {
19658
+ const previous = this.writeChain;
19659
+ let release;
19660
+ this.writeChain = new Promise((resolve2) => {
19661
+ release = resolve2;
19662
+ });
19663
+ await previous;
19664
+ try {
19665
+ return await fn();
19666
+ } finally {
19667
+ release?.();
19668
+ }
19669
+ }
19670
+ async loadIndex() {
19671
+ await mkdir3(this.inboxDir, { recursive: true });
19672
+ let raw;
19673
+ try {
19674
+ raw = await readFile3(this.indexPath, "utf8");
19675
+ } catch (error48) {
19676
+ if (error48 && typeof error48 === "object" && "code" in error48 && error48.code === "ENOENT") {
19677
+ return toDefaultIndexFile();
19678
+ }
19679
+ throw error48;
19680
+ }
19681
+ if (raw.trim().length === 0) {
19682
+ return toDefaultIndexFile();
19683
+ }
19684
+ const parsed = JSON.parse(raw);
19685
+ return normalizeIndexFile(parsed);
19686
+ }
19687
+ async saveIndex(index) {
19688
+ await mkdir3(dirname2(this.indexPath), { recursive: true });
19689
+ const payload = {
19690
+ ...index,
19691
+ version: INBOUND_INBOX_SCHEMA_VERSION,
19692
+ updatedAt: nowIso2()
19693
+ };
19694
+ const tmpPath = `${this.indexPath}.tmp-${Date.now()}`;
19695
+ await writeFile3(tmpPath, `${JSON.stringify(payload, null, 2)}
19696
+ `, "utf8");
19697
+ await rename2(tmpPath, this.indexPath);
19698
+ }
19699
+ async appendEvent(event) {
19700
+ await mkdir3(dirname2(this.eventsPath), { recursive: true });
19701
+ await appendFile(
19702
+ this.eventsPath,
19703
+ `${JSON.stringify({ ...event, at: nowIso2() })}
19704
+ `,
19705
+ "utf8"
19706
+ );
19707
+ }
19708
+ };
19709
+ function createConnectorInboundInbox(options) {
19710
+ return new ConnectorInboundInbox(options);
19711
+ }
19712
+
19326
19713
  // ../../packages/connector/src/runtime.ts
19327
19714
  import { randomBytes as randomBytes2 } from "crypto";
19328
- import { mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
19715
+ import { mkdir as mkdir4, readFile as readFile4, rename as rename3, writeFile as writeFile4 } from "fs/promises";
19329
19716
  import {
19330
19717
  createServer
19331
19718
  } from "http";
19332
- import { dirname as dirname2, join as join4 } from "path";
19719
+ import { dirname as dirname3, join as join5 } from "path";
19333
19720
  import { WebSocket as NodeWebSocket } from "ws";
19334
19721
  var REGISTRY_AUTH_FILENAME = "registry-auth.json";
19335
19722
  var AGENTS_DIR_NAME2 = "agents";
@@ -19337,7 +19724,7 @@ var REFRESH_SINGLE_FLIGHT_PREFIX = "connector-runtime";
19337
19724
  var NONCE_SIZE = 16;
19338
19725
  var MAX_OUTBOUND_BODY_BYTES = 1024 * 1024;
19339
19726
  var ACCESS_TOKEN_REFRESH_SKEW_MS = 3e4;
19340
- function isRecord5(value) {
19727
+ function isRecord6(value) {
19341
19728
  return typeof value === "object" && value !== null;
19342
19729
  }
19343
19730
  function toPathWithQuery2(url2) {
@@ -19405,6 +19792,121 @@ function resolveOpenclawHookToken(input) {
19405
19792
  }
19406
19793
  return value;
19407
19794
  }
19795
+ function toOpenclawHookUrl2(baseUrl, hookPath) {
19796
+ const normalizedBase = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
19797
+ const normalizedHookPath = hookPath.startsWith("/") ? hookPath.slice(1) : hookPath;
19798
+ return new URL(normalizedHookPath, normalizedBase).toString();
19799
+ }
19800
+ function parsePositiveIntEnv(key, fallback, minimum = 1) {
19801
+ const raw = process.env[key]?.trim();
19802
+ if (!raw) {
19803
+ return fallback;
19804
+ }
19805
+ const parsed = Number.parseInt(raw, 10);
19806
+ if (!Number.isFinite(parsed) || parsed < minimum) {
19807
+ return fallback;
19808
+ }
19809
+ return parsed;
19810
+ }
19811
+ function sanitizeErrorReason2(error48) {
19812
+ if (!(error48 instanceof Error)) {
19813
+ return "Unknown error";
19814
+ }
19815
+ return error48.message.trim().slice(0, 240) || "Unknown error";
19816
+ }
19817
+ var LocalOpenclawDeliveryError2 = class extends Error {
19818
+ retryable;
19819
+ constructor(input) {
19820
+ super(input.message);
19821
+ this.name = "LocalOpenclawDeliveryError";
19822
+ this.retryable = input.retryable;
19823
+ }
19824
+ };
19825
+ function loadInboundReplayPolicy() {
19826
+ const retryBackoffFactor = Number.parseFloat(
19827
+ process.env.CONNECTOR_INBOUND_RETRY_BACKOFF_FACTOR ?? ""
19828
+ );
19829
+ return {
19830
+ inboxMaxMessages: parsePositiveIntEnv(
19831
+ "CONNECTOR_INBOUND_INBOX_MAX_MESSAGES",
19832
+ DEFAULT_CONNECTOR_INBOUND_INBOX_MAX_MESSAGES
19833
+ ),
19834
+ inboxMaxBytes: parsePositiveIntEnv(
19835
+ "CONNECTOR_INBOUND_INBOX_MAX_BYTES",
19836
+ DEFAULT_CONNECTOR_INBOUND_INBOX_MAX_BYTES
19837
+ ),
19838
+ replayIntervalMs: parsePositiveIntEnv(
19839
+ "CONNECTOR_INBOUND_REPLAY_INTERVAL_MS",
19840
+ DEFAULT_CONNECTOR_INBOUND_REPLAY_INTERVAL_MS
19841
+ ),
19842
+ batchSize: parsePositiveIntEnv(
19843
+ "CONNECTOR_INBOUND_REPLAY_BATCH_SIZE",
19844
+ DEFAULT_CONNECTOR_INBOUND_REPLAY_BATCH_SIZE
19845
+ ),
19846
+ retryInitialDelayMs: parsePositiveIntEnv(
19847
+ "CONNECTOR_INBOUND_RETRY_INITIAL_DELAY_MS",
19848
+ DEFAULT_CONNECTOR_INBOUND_RETRY_INITIAL_DELAY_MS
19849
+ ),
19850
+ retryMaxDelayMs: parsePositiveIntEnv(
19851
+ "CONNECTOR_INBOUND_RETRY_MAX_DELAY_MS",
19852
+ DEFAULT_CONNECTOR_INBOUND_RETRY_MAX_DELAY_MS
19853
+ ),
19854
+ retryBackoffFactor: Number.isFinite(retryBackoffFactor) && retryBackoffFactor >= 1 ? retryBackoffFactor : DEFAULT_CONNECTOR_INBOUND_RETRY_BACKOFF_FACTOR
19855
+ };
19856
+ }
19857
+ function computeReplayDelayMs(input) {
19858
+ const exponent = Math.max(0, input.attemptCount - 1);
19859
+ const delay = Math.min(
19860
+ input.policy.retryMaxDelayMs,
19861
+ Math.floor(
19862
+ input.policy.retryInitialDelayMs * input.policy.retryBackoffFactor ** exponent
19863
+ )
19864
+ );
19865
+ return Math.max(1, delay);
19866
+ }
19867
+ async function deliverToOpenclawHook(input) {
19868
+ const controller = new AbortController();
19869
+ const timeoutHandle = setTimeout(() => {
19870
+ controller.abort();
19871
+ }, DEFAULT_OPENCLAW_DELIVER_TIMEOUT_MS);
19872
+ const headers = {
19873
+ "content-type": "application/json",
19874
+ "x-request-id": input.requestId
19875
+ };
19876
+ if (input.openclawHookToken !== void 0) {
19877
+ headers["x-openclaw-token"] = input.openclawHookToken;
19878
+ }
19879
+ try {
19880
+ const response = await input.fetchImpl(input.openclawHookUrl, {
19881
+ method: "POST",
19882
+ headers,
19883
+ body: JSON.stringify(input.payload),
19884
+ signal: controller.signal
19885
+ });
19886
+ if (!response.ok) {
19887
+ throw new LocalOpenclawDeliveryError2({
19888
+ message: `Local OpenClaw hook rejected payload with status ${response.status}`,
19889
+ retryable: response.status >= 500 || response.status === 404 || response.status === 429
19890
+ });
19891
+ }
19892
+ } catch (error48) {
19893
+ if (error48 instanceof Error && error48.name === "AbortError") {
19894
+ throw new LocalOpenclawDeliveryError2({
19895
+ message: "Local OpenClaw hook request timed out",
19896
+ retryable: true
19897
+ });
19898
+ }
19899
+ if (error48 instanceof LocalOpenclawDeliveryError2) {
19900
+ throw error48;
19901
+ }
19902
+ throw new LocalOpenclawDeliveryError2({
19903
+ message: sanitizeErrorReason2(error48),
19904
+ retryable: true
19905
+ });
19906
+ } finally {
19907
+ clearTimeout(timeoutHandle);
19908
+ }
19909
+ }
19408
19910
  function toInitialAuthBundle(credentials) {
19409
19911
  return {
19410
19912
  tokenType: "Bearer",
@@ -19432,7 +19934,7 @@ function shouldRefreshAccessToken(auth, nowMs) {
19432
19934
  return expiresAtMs <= nowMs + ACCESS_TOKEN_REFRESH_SKEW_MS;
19433
19935
  }
19434
19936
  function parseOutboundRelayRequest(payload) {
19435
- if (!isRecord5(payload)) {
19937
+ if (!isRecord6(payload)) {
19436
19938
  throw new AppError({
19437
19939
  code: "CONNECTOR_OUTBOUND_INVALID_REQUEST",
19438
19940
  message: "Outbound relay request must be an object",
@@ -19490,20 +19992,20 @@ function createWebSocketFactory() {
19490
19992
  };
19491
19993
  }
19492
19994
  async function writeRegistryAuthAtomic(input) {
19493
- const targetPath = join4(
19995
+ const targetPath = join5(
19494
19996
  input.configDir,
19495
19997
  AGENTS_DIR_NAME2,
19496
19998
  input.agentName,
19497
19999
  REGISTRY_AUTH_FILENAME
19498
20000
  );
19499
20001
  const tmpPath = `${targetPath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
19500
- await mkdir3(dirname2(targetPath), { recursive: true });
19501
- await writeFile3(tmpPath, `${JSON.stringify(input.auth, null, 2)}
20002
+ await mkdir4(dirname3(targetPath), { recursive: true });
20003
+ await writeFile4(tmpPath, `${JSON.stringify(input.auth, null, 2)}
19502
20004
  `, "utf8");
19503
- await rename2(tmpPath, targetPath);
20005
+ await rename3(tmpPath, targetPath);
19504
20006
  }
19505
20007
  function parseRegistryAuthFromDisk(payload) {
19506
- if (!isRecord5(payload)) {
20008
+ if (!isRecord6(payload)) {
19507
20009
  return void 0;
19508
20010
  }
19509
20011
  const tokenType = payload.tokenType;
@@ -19523,7 +20025,7 @@ function parseRegistryAuthFromDisk(payload) {
19523
20025
  };
19524
20026
  }
19525
20027
  async function readRegistryAuthFromDisk(input) {
19526
- const authPath = join4(
20028
+ const authPath = join5(
19527
20029
  input.configDir,
19528
20030
  AGENTS_DIR_NAME2,
19529
20031
  input.agentName,
@@ -19531,7 +20033,7 @@ async function readRegistryAuthFromDisk(input) {
19531
20033
  );
19532
20034
  let raw;
19533
20035
  try {
19534
- raw = await readFile3(authPath, "utf8");
20036
+ raw = await readFile4(authPath, "utf8");
19535
20037
  } catch (error48) {
19536
20038
  if (error48 && typeof error48 === "object" && "code" in error48 && error48.code === "ENOENT") {
19537
20039
  return void 0;
@@ -19660,6 +20162,23 @@ async function startConnectorRuntime(input) {
19660
20162
  await refreshCurrentAuthIfNeeded();
19661
20163
  const wsUrl = normalizeWebSocketUrl(input.proxyWebsocketUrl);
19662
20164
  const wsParsed = new URL(wsUrl);
20165
+ const openclawBaseUrl = resolveOpenclawBaseUrl(input.openclawBaseUrl);
20166
+ const openclawHookPath = resolveOpenclawHookPath(input.openclawHookPath);
20167
+ const openclawHookToken = resolveOpenclawHookToken(input.openclawHookToken);
20168
+ const openclawHookUrl = toOpenclawHookUrl2(openclawBaseUrl, openclawHookPath);
20169
+ const inboundReplayPolicy = loadInboundReplayPolicy();
20170
+ const inboundInbox = createConnectorInboundInbox({
20171
+ configDir: input.configDir,
20172
+ agentName: input.agentName,
20173
+ maxPendingMessages: inboundReplayPolicy.inboxMaxMessages,
20174
+ maxPendingBytes: inboundReplayPolicy.inboxMaxBytes
20175
+ });
20176
+ const inboundReplayStatus = {
20177
+ replayerActive: false
20178
+ };
20179
+ let runtimeStopping = false;
20180
+ let replayInFlight = false;
20181
+ let replayIntervalHandle;
19663
20182
  const resolveUpgradeHeaders = async () => {
19664
20183
  await refreshCurrentAuthIfNeeded();
19665
20184
  return buildUpgradeHeaders({
@@ -19669,14 +20188,108 @@ async function startConnectorRuntime(input) {
19669
20188
  secretKey
19670
20189
  });
19671
20190
  };
20191
+ const replayPendingInboundMessages = async () => {
20192
+ if (runtimeStopping || replayInFlight) {
20193
+ return;
20194
+ }
20195
+ replayInFlight = true;
20196
+ inboundReplayStatus.replayerActive = true;
20197
+ try {
20198
+ const dueItems = await inboundInbox.listDuePending({
20199
+ nowMs: Date.now(),
20200
+ limit: inboundReplayPolicy.batchSize
20201
+ });
20202
+ for (const pending of dueItems) {
20203
+ inboundReplayStatus.lastAttemptAt = (/* @__PURE__ */ new Date()).toISOString();
20204
+ try {
20205
+ await deliverToOpenclawHook({
20206
+ fetchImpl,
20207
+ openclawHookUrl,
20208
+ openclawHookToken,
20209
+ requestId: pending.requestId,
20210
+ payload: pending.payload
20211
+ });
20212
+ await inboundInbox.markDelivered(pending.requestId);
20213
+ inboundReplayStatus.lastReplayAt = (/* @__PURE__ */ new Date()).toISOString();
20214
+ inboundReplayStatus.lastReplayError = void 0;
20215
+ inboundReplayStatus.lastAttemptStatus = "ok";
20216
+ logger11.info("connector.inbound.replay_succeeded", {
20217
+ requestId: pending.requestId,
20218
+ attemptCount: pending.attemptCount + 1
20219
+ });
20220
+ } catch (error48) {
20221
+ const reason = sanitizeErrorReason2(error48);
20222
+ const retryable = error48 instanceof LocalOpenclawDeliveryError2 ? error48.retryable : true;
20223
+ const nextAttemptAt = new Date(
20224
+ Date.now() + computeReplayDelayMs({
20225
+ attemptCount: pending.attemptCount + 1,
20226
+ policy: inboundReplayPolicy
20227
+ }) * (retryable ? 1 : 10)
20228
+ ).toISOString();
20229
+ await inboundInbox.markReplayFailure({
20230
+ requestId: pending.requestId,
20231
+ errorMessage: reason,
20232
+ nextAttemptAt
20233
+ });
20234
+ inboundReplayStatus.lastReplayError = reason;
20235
+ inboundReplayStatus.lastAttemptStatus = "failed";
20236
+ logger11.warn("connector.inbound.replay_failed", {
20237
+ requestId: pending.requestId,
20238
+ attemptCount: pending.attemptCount + 1,
20239
+ retryable,
20240
+ nextAttemptAt,
20241
+ reason
20242
+ });
20243
+ }
20244
+ }
20245
+ } finally {
20246
+ replayInFlight = false;
20247
+ inboundReplayStatus.replayerActive = false;
20248
+ }
20249
+ };
20250
+ const readInboundReplayView = async () => {
20251
+ const pending = await inboundInbox.getSnapshot();
20252
+ return {
20253
+ pending,
20254
+ replayerActive: inboundReplayStatus.replayerActive || replayInFlight,
20255
+ lastReplayAt: inboundReplayStatus.lastReplayAt,
20256
+ lastReplayError: inboundReplayStatus.lastReplayError,
20257
+ openclawHook: {
20258
+ url: openclawHookUrl,
20259
+ lastAttemptAt: inboundReplayStatus.lastAttemptAt,
20260
+ lastAttemptStatus: inboundReplayStatus.lastAttemptStatus
20261
+ }
20262
+ };
20263
+ };
19672
20264
  const connectorClient = new ConnectorClient({
19673
20265
  connectorUrl: wsParsed.toString(),
19674
20266
  connectionHeadersProvider: resolveUpgradeHeaders,
19675
- openclawBaseUrl: resolveOpenclawBaseUrl(input.openclawBaseUrl),
19676
- openclawHookPath: resolveOpenclawHookPath(input.openclawHookPath),
19677
- openclawHookToken: resolveOpenclawHookToken(input.openclawHookToken),
20267
+ openclawBaseUrl,
20268
+ openclawHookPath,
20269
+ openclawHookToken,
19678
20270
  fetchImpl,
19679
20271
  logger: logger11,
20272
+ inboundDeliverHandler: async (frame) => {
20273
+ const persisted = await inboundInbox.enqueue(frame);
20274
+ if (!persisted.accepted) {
20275
+ logger11.warn("connector.inbound.persist_rejected", {
20276
+ requestId: frame.id,
20277
+ reason: persisted.reason ?? "inbox limit reached",
20278
+ pendingCount: persisted.pendingCount
20279
+ });
20280
+ return {
20281
+ accepted: false,
20282
+ reason: persisted.reason
20283
+ };
20284
+ }
20285
+ logger11.info("connector.inbound.persisted", {
20286
+ requestId: frame.id,
20287
+ duplicate: persisted.duplicate,
20288
+ pendingCount: persisted.pendingCount
20289
+ });
20290
+ void replayPendingInboundMessages();
20291
+ return { accepted: true };
20292
+ },
19680
20293
  webSocketFactory: createWebSocketFactory()
19681
20294
  });
19682
20295
  const outboundBaseUrl = normalizeOutboundBaseUrl(input.outboundBaseUrl);
@@ -19760,11 +20373,40 @@ async function startConnectorRuntime(input) {
19760
20373
  writeJson(res, 405, { error: "Method Not Allowed" });
19761
20374
  return;
19762
20375
  }
20376
+ let inboundReplayView;
20377
+ try {
20378
+ inboundReplayView = await readInboundReplayView();
20379
+ } catch (error48) {
20380
+ logger11.warn("connector.status.inbound_inbox_unavailable", {
20381
+ reason: sanitizeErrorReason2(error48)
20382
+ });
20383
+ writeJson(res, 500, {
20384
+ status: "error",
20385
+ error: {
20386
+ code: "CONNECTOR_INBOUND_INBOX_UNAVAILABLE",
20387
+ message: "Connector inbound inbox status is unavailable"
20388
+ },
20389
+ outboundUrl,
20390
+ websocketUrl: wsUrl,
20391
+ websocketConnected: connectorClient.isConnected()
20392
+ });
20393
+ return;
20394
+ }
19763
20395
  writeJson(res, 200, {
19764
20396
  status: "ok",
19765
20397
  outboundUrl,
19766
20398
  websocketUrl: wsUrl,
19767
- websocketConnected: connectorClient.isConnected()
20399
+ websocketConnected: connectorClient.isConnected(),
20400
+ inboundInbox: {
20401
+ pendingCount: inboundReplayView.pending.pendingCount,
20402
+ pendingBytes: inboundReplayView.pending.pendingBytes,
20403
+ oldestPendingAt: inboundReplayView.pending.oldestPendingAt,
20404
+ nextAttemptAt: inboundReplayView.pending.nextAttemptAt,
20405
+ replayerActive: inboundReplayView.replayerActive,
20406
+ lastReplayAt: inboundReplayView.lastReplayAt,
20407
+ lastReplayError: inboundReplayView.lastReplayError
20408
+ },
20409
+ openclawHook: inboundReplayView.openclawHook
19768
20410
  });
19769
20411
  return;
19770
20412
  }
@@ -19814,6 +20456,11 @@ async function startConnectorRuntime(input) {
19814
20456
  stoppedResolve = resolve2;
19815
20457
  });
19816
20458
  const stop = async () => {
20459
+ runtimeStopping = true;
20460
+ if (replayIntervalHandle !== void 0) {
20461
+ clearInterval(replayIntervalHandle);
20462
+ replayIntervalHandle = void 0;
20463
+ }
19817
20464
  connectorClient.disconnect();
19818
20465
  await new Promise((resolve2, reject) => {
19819
20466
  server.close((error48) => {
@@ -19838,6 +20485,11 @@ async function startConnectorRuntime(input) {
19838
20485
  );
19839
20486
  });
19840
20487
  connectorClient.connect();
20488
+ await inboundInbox.pruneDelivered();
20489
+ void replayPendingInboundMessages();
20490
+ replayIntervalHandle = setInterval(() => {
20491
+ void replayPendingInboundMessages();
20492
+ }, inboundReplayPolicy.replayIntervalMs);
19841
20493
  logger11.info("connector.runtime.started", {
19842
20494
  outboundUrl,
19843
20495
  websocketUrl: wsUrl,
@@ -19865,11 +20517,11 @@ var OPENCLAW_CONNECTORS_FILE_NAME = "openclaw-connectors.json";
19865
20517
  var SERVICE_LOG_DIR_NAME = "logs";
19866
20518
  var DEFAULT_CONNECTOR_BASE_URL2 = "http://127.0.0.1:19400";
19867
20519
  var DEFAULT_CONNECTOR_OUTBOUND_PATH2 = "/v1/outbound";
19868
- function isRecord6(value) {
20520
+ function isRecord7(value) {
19869
20521
  return typeof value === "object" && value !== null;
19870
20522
  }
19871
20523
  function getErrorCode(error48) {
19872
- if (!isRecord6(error48)) {
20524
+ if (!isRecord7(error48)) {
19873
20525
  return void 0;
19874
20526
  }
19875
20527
  return typeof error48.code === "string" ? error48.code : void 0;
@@ -20008,7 +20660,7 @@ function resolveConnectorBaseUrlFromEnv() {
20008
20660
  return parseConnectorBaseUrl(value.trim());
20009
20661
  }
20010
20662
  async function readConnectorAssignedBaseUrl(configDir, agentName, readFileImpl) {
20011
- const assignmentsPath = join5(configDir, OPENCLAW_CONNECTORS_FILE_NAME);
20663
+ const assignmentsPath = join6(configDir, OPENCLAW_CONNECTORS_FILE_NAME);
20012
20664
  let raw;
20013
20665
  try {
20014
20666
  raw = await readFileImpl(assignmentsPath, "utf8");
@@ -20028,11 +20680,11 @@ async function readConnectorAssignedBaseUrl(configDir, agentName, readFileImpl)
20028
20680
  { assignmentsPath }
20029
20681
  );
20030
20682
  }
20031
- if (!isRecord6(parsed) || !isRecord6(parsed.agents)) {
20683
+ if (!isRecord7(parsed) || !isRecord7(parsed.agents)) {
20032
20684
  return void 0;
20033
20685
  }
20034
20686
  const entry = parsed.agents[agentName];
20035
- if (!isRecord6(entry) || typeof entry.connectorBaseUrl !== "string") {
20687
+ if (!isRecord7(entry) || typeof entry.connectorBaseUrl !== "string") {
20036
20688
  return void 0;
20037
20689
  }
20038
20690
  return parseConnectorBaseUrl(entry.connectorBaseUrl);
@@ -20072,7 +20724,7 @@ async function readRequiredTrimmedFile(filePath, label, readFileImpl) {
20072
20724
  return trimmed;
20073
20725
  }
20074
20726
  async function readRelayRuntimeConfig(configDir, readFileImpl) {
20075
- const filePath = join5(configDir, OPENCLAW_RELAY_RUNTIME_FILE_NAME);
20727
+ const filePath = join6(configDir, OPENCLAW_RELAY_RUNTIME_FILE_NAME);
20076
20728
  let raw;
20077
20729
  try {
20078
20730
  raw = await readFileImpl(filePath, "utf8");
@@ -20088,7 +20740,7 @@ async function readRelayRuntimeConfig(configDir, readFileImpl) {
20088
20740
  } catch {
20089
20741
  return void 0;
20090
20742
  }
20091
- if (!isRecord6(parsed)) {
20743
+ if (!isRecord7(parsed)) {
20092
20744
  return void 0;
20093
20745
  }
20094
20746
  const openclawHookToken = typeof parsed.openclawHookToken === "string" && parsed.openclawHookToken.trim().length > 0 ? parsed.openclawHookToken.trim() : void 0;
@@ -20106,7 +20758,7 @@ function parseJsonRecord(value, code, message2) {
20106
20758
  } catch {
20107
20759
  throw createCliError4(code, message2);
20108
20760
  }
20109
- if (!isRecord6(parsed)) {
20761
+ if (!isRecord7(parsed)) {
20110
20762
  throw createCliError4(code, message2);
20111
20763
  }
20112
20764
  return parsed;
@@ -20146,7 +20798,7 @@ async function loadDefaultConnectorModule() {
20146
20798
  };
20147
20799
  }
20148
20800
  function resolveWaitPromise(runtime) {
20149
- if (!runtime || !isRecord6(runtime)) {
20801
+ if (!runtime || !isRecord7(runtime)) {
20150
20802
  return void 0;
20151
20803
  }
20152
20804
  if (typeof runtime.waitUntilStopped === "function") {
@@ -20211,7 +20863,7 @@ function buildConnectorStartArgs(agentName, commandOptions) {
20211
20863
  }
20212
20864
  function resolveCliEntryPath(resolveCurrentModulePathImpl) {
20213
20865
  const modulePath = resolveCurrentModulePathImpl?.() ?? fileURLToPath(import.meta.url);
20214
- return join5(dirname3(modulePath), "..", "bin.js");
20866
+ return join6(dirname4(modulePath), "..", "bin.js");
20215
20867
  }
20216
20868
  function escapeXml(value) {
20217
20869
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
@@ -20281,7 +20933,7 @@ function resolveServiceDependencies(dependencies) {
20281
20933
  execFileImpl,
20282
20934
  getConfigDirImpl: dependencies.getConfigDirImpl ?? getConfigDir,
20283
20935
  getHomeDirImpl: dependencies.getHomeDirImpl ?? homedir2,
20284
- mkdirImpl: dependencies.mkdirImpl ?? mkdir4,
20936
+ mkdirImpl: dependencies.mkdirImpl ?? mkdir5,
20285
20937
  removeFileImpl: dependencies.removeFileImpl ?? rm,
20286
20938
  resolveCurrentModulePathImpl: dependencies.resolveCurrentModulePathImpl,
20287
20939
  resolveCurrentPlatformImpl: dependencies.resolveCurrentPlatformImpl ?? (() => process.platform),
@@ -20295,7 +20947,7 @@ function resolveServiceDependencies(dependencies) {
20295
20947
  return process.getuid();
20296
20948
  }),
20297
20949
  resolveNodeExecPathImpl: dependencies.resolveNodeExecPathImpl ?? (() => process.execPath),
20298
- writeFileImpl: dependencies.writeFileImpl ?? writeFile4
20950
+ writeFileImpl: dependencies.writeFileImpl ?? writeFile5
20299
20951
  };
20300
20952
  }
20301
20953
  async function installConnectorServiceForAgent(agentName, commandOptions = {}, dependencies = {}) {
@@ -20309,7 +20961,7 @@ async function installConnectorServiceForAgent(agentName, commandOptions = {}, d
20309
20961
  );
20310
20962
  const configDir = serviceDependencies.getConfigDirImpl();
20311
20963
  const homeDir = serviceDependencies.getHomeDirImpl();
20312
- const logsDir = join5(configDir, SERVICE_LOG_DIR_NAME);
20964
+ const logsDir = join6(configDir, SERVICE_LOG_DIR_NAME);
20313
20965
  const serviceName = sanitizeServiceSegment(
20314
20966
  `clawdentity-connector-${agentName}`
20315
20967
  );
@@ -20319,12 +20971,12 @@ async function installConnectorServiceForAgent(agentName, commandOptions = {}, d
20319
20971
  resolveCliEntryPath(serviceDependencies.resolveCurrentModulePathImpl),
20320
20972
  ...startArgs
20321
20973
  ];
20322
- const outputLogPath = join5(logsDir, `${serviceName}.out.log`);
20323
- const errorLogPath = join5(logsDir, `${serviceName}.err.log`);
20974
+ const outputLogPath = join6(logsDir, `${serviceName}.out.log`);
20975
+ const errorLogPath = join6(logsDir, `${serviceName}.err.log`);
20324
20976
  await serviceDependencies.mkdirImpl(logsDir, { recursive: true });
20325
20977
  if (platform === "systemd") {
20326
- const serviceDir = join5(homeDir, ".config", "systemd", "user");
20327
- const serviceFilePath2 = join5(serviceDir, `${serviceName}.service`);
20978
+ const serviceDir = join6(homeDir, ".config", "systemd", "user");
20979
+ const serviceFilePath2 = join6(serviceDir, `${serviceName}.service`);
20328
20980
  await serviceDependencies.mkdirImpl(serviceDir, { recursive: true });
20329
20981
  await serviceDependencies.writeFileImpl(
20330
20982
  serviceFilePath2,
@@ -20363,9 +21015,9 @@ async function installConnectorServiceForAgent(agentName, commandOptions = {}, d
20363
21015
  serviceFilePath: serviceFilePath2
20364
21016
  };
20365
21017
  }
20366
- const launchAgentsDir = join5(homeDir, "Library", "LaunchAgents");
21018
+ const launchAgentsDir = join6(homeDir, "Library", "LaunchAgents");
20367
21019
  const serviceNameWithDomain = `com.clawdentity.${serviceName}`;
20368
- const serviceFilePath = join5(
21020
+ const serviceFilePath = join6(
20369
21021
  launchAgentsDir,
20370
21022
  `${serviceNameWithDomain}.plist`
20371
21023
  );
@@ -20424,7 +21076,7 @@ async function uninstallConnectorServiceForAgent(agentName, commandOptions = {},
20424
21076
  `clawdentity-connector-${agentName}`
20425
21077
  );
20426
21078
  if (platform === "systemd") {
20427
- const serviceFilePath2 = join5(
21079
+ const serviceFilePath2 = join6(
20428
21080
  homeDir,
20429
21081
  ".config",
20430
21082
  "systemd",
@@ -20455,7 +21107,7 @@ async function uninstallConnectorServiceForAgent(agentName, commandOptions = {},
20455
21107
  };
20456
21108
  }
20457
21109
  const serviceNameWithDomain = `com.clawdentity.${serviceName}`;
20458
- const serviceFilePath = join5(
21110
+ const serviceFilePath = join6(
20459
21111
  homeDir,
20460
21112
  "Library",
20461
21113
  "LaunchAgents",
@@ -20479,11 +21131,11 @@ async function uninstallConnectorServiceForAgent(agentName, commandOptions = {},
20479
21131
  async function startConnectorForAgent(agentName, commandOptions = {}, dependencies = {}) {
20480
21132
  const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
20481
21133
  const getConfigDirImpl = dependencies.getConfigDirImpl ?? getConfigDir;
20482
- const readFileImpl = dependencies.readFileImpl ?? ((path, encoding) => readFile4(path, encoding));
21134
+ const readFileImpl = dependencies.readFileImpl ?? ((path, encoding) => readFile5(path, encoding));
20483
21135
  const fetchImpl = dependencies.fetchImpl ?? globalThis.fetch;
20484
21136
  const loadConnectorModule = dependencies.loadConnectorModule ?? loadDefaultConnectorModule;
20485
21137
  const configDir = getConfigDirImpl();
20486
- const agentDirectory = join5(configDir, AGENTS_DIR_NAME3, agentName);
21138
+ const agentDirectory = join6(configDir, AGENTS_DIR_NAME3, agentName);
20487
21139
  const [
20488
21140
  rawAit,
20489
21141
  rawSecretKey,
@@ -20495,22 +21147,22 @@ async function startConnectorForAgent(agentName, commandOptions = {}, dependenci
20495
21147
  connectorModule
20496
21148
  ] = await Promise.all([
20497
21149
  readRequiredTrimmedFile(
20498
- join5(agentDirectory, AIT_FILE_NAME2),
21150
+ join6(agentDirectory, AIT_FILE_NAME2),
20499
21151
  AIT_FILE_NAME2,
20500
21152
  readFileImpl
20501
21153
  ),
20502
21154
  readRequiredTrimmedFile(
20503
- join5(agentDirectory, SECRET_KEY_FILE_NAME),
21155
+ join6(agentDirectory, SECRET_KEY_FILE_NAME),
20504
21156
  SECRET_KEY_FILE_NAME,
20505
21157
  readFileImpl
20506
21158
  ),
20507
21159
  readRequiredTrimmedFile(
20508
- join5(agentDirectory, IDENTITY_FILE_NAME2),
21160
+ join6(agentDirectory, IDENTITY_FILE_NAME2),
20509
21161
  IDENTITY_FILE_NAME2,
20510
21162
  readFileImpl
20511
21163
  ),
20512
21164
  readRequiredTrimmedFile(
20513
- join5(agentDirectory, REGISTRY_AUTH_FILE_NAME2),
21165
+ join6(agentDirectory, REGISTRY_AUTH_FILE_NAME2),
20514
21166
  REGISTRY_AUTH_FILE_NAME2,
20515
21167
  readFileImpl
20516
21168
  ),
@@ -20557,8 +21209,8 @@ async function startConnectorForAgent(agentName, commandOptions = {}, dependenci
20557
21209
  tokenType: registryAuth.tokenType
20558
21210
  }
20559
21211
  });
20560
- const outboundUrl = runtime && isRecord6(runtime) && typeof runtime.outboundUrl === "string" ? runtime.outboundUrl : resolveOutboundUrl(outboundBaseUrl, outboundPath);
20561
- const proxyWebsocketUrl = runtime && isRecord6(runtime) ? typeof runtime.websocketUrl === "string" ? runtime.websocketUrl : typeof runtime.proxyWebsocketUrl === "string" ? runtime.proxyWebsocketUrl : resolvedProxyWebsocketUrl : void 0;
21212
+ const outboundUrl = runtime && isRecord7(runtime) && typeof runtime.outboundUrl === "string" ? runtime.outboundUrl : resolveOutboundUrl(outboundBaseUrl, outboundPath);
21213
+ const proxyWebsocketUrl = runtime && isRecord7(runtime) ? typeof runtime.websocketUrl === "string" ? runtime.websocketUrl : typeof runtime.proxyWebsocketUrl === "string" ? runtime.proxyWebsocketUrl : resolvedProxyWebsocketUrl : void 0;
20562
21214
  return {
20563
21215
  outboundUrl,
20564
21216
  proxyWebsocketUrl,
@@ -20689,7 +21341,7 @@ function createConnectorCommand(dependencies = {}) {
20689
21341
  // src/commands/invite.ts
20690
21342
  import { Command as Command6 } from "commander";
20691
21343
  var logger7 = createLogger({ service: "cli", module: "invite" });
20692
- var isRecord7 = (value) => {
21344
+ var isRecord8 = (value) => {
20693
21345
  return typeof value === "object" && value !== null;
20694
21346
  };
20695
21347
  function parseNonEmptyString7(value) {
@@ -20733,7 +21385,7 @@ function requireApiKey2(config2) {
20733
21385
  );
20734
21386
  }
20735
21387
  function extractRegistryErrorCode(payload) {
20736
- if (!isRecord7(payload)) {
21388
+ if (!isRecord8(payload)) {
20737
21389
  return void 0;
20738
21390
  }
20739
21391
  const envelope = payload;
@@ -20744,7 +21396,7 @@ function extractRegistryErrorCode(payload) {
20744
21396
  return trimmed.length > 0 ? trimmed : void 0;
20745
21397
  }
20746
21398
  function extractRegistryErrorMessage4(payload) {
20747
- if (!isRecord7(payload)) {
21399
+ if (!isRecord8(payload)) {
20748
21400
  return void 0;
20749
21401
  }
20750
21402
  const envelope = payload;
@@ -20818,13 +21470,13 @@ function mapRedeemInviteError(status, payload) {
20818
21470
  return `Invite redeem failed (${status})`;
20819
21471
  }
20820
21472
  function parseInviteRecord(payload) {
20821
- if (!isRecord7(payload)) {
21473
+ if (!isRecord8(payload)) {
20822
21474
  throw createCliError5(
20823
21475
  "CLI_INVITE_CREATE_INVALID_RESPONSE",
20824
21476
  "Invite response is invalid"
20825
21477
  );
20826
21478
  }
20827
- const source = isRecord7(payload.invite) ? payload.invite : payload;
21479
+ const source = isRecord8(payload.invite) ? payload.invite : payload;
20828
21480
  const code = parseNonEmptyString7(source.code);
20829
21481
  if (code.length === 0) {
20830
21482
  throw createCliError5(
@@ -20849,15 +21501,15 @@ function parseInviteRecord(payload) {
20849
21501
  return invite;
20850
21502
  }
20851
21503
  function parseInviteRedeemResponse(payload) {
20852
- if (!isRecord7(payload)) {
21504
+ if (!isRecord8(payload)) {
20853
21505
  throw createCliError5(
20854
21506
  "CLI_INVITE_REDEEM_INVALID_RESPONSE",
20855
21507
  "Invite redeem response is invalid"
20856
21508
  );
20857
21509
  }
20858
- const apiKeySource = isRecord7(payload.apiKey) ? payload.apiKey : payload;
21510
+ const apiKeySource = isRecord8(payload.apiKey) ? payload.apiKey : payload;
20859
21511
  const apiKeyToken = parseNonEmptyString7(
20860
- isRecord7(payload.apiKey) ? payload.apiKey.token : payload.token
21512
+ isRecord8(payload.apiKey) ? payload.apiKey.token : payload.token
20861
21513
  );
20862
21514
  if (apiKeyToken.length === 0) {
20863
21515
  throw createCliError5(
@@ -20867,7 +21519,7 @@ function parseInviteRedeemResponse(payload) {
20867
21519
  }
20868
21520
  const apiKeyId = parseNonEmptyString7(apiKeySource.id);
20869
21521
  const apiKeyName = parseNonEmptyString7(apiKeySource.name);
20870
- const humanSource = isRecord7(payload.human) ? payload.human : void 0;
21522
+ const humanSource = isRecord8(payload.human) ? payload.human : void 0;
20871
21523
  const humanName = parseNonEmptyString7(humanSource?.displayName);
20872
21524
  const proxyUrl = parseNonEmptyString7(payload.proxyUrl);
20873
21525
  if (humanName.length === 0) {
@@ -21055,9 +21707,9 @@ var createInviteCommand = (dependencies = {}) => {
21055
21707
  import { spawn } from "child_process";
21056
21708
  import { randomBytes as randomBytes3 } from "crypto";
21057
21709
  import { existsSync } from "fs";
21058
- import { chmod as chmod3, copyFile, mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
21710
+ import { chmod as chmod3, copyFile, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
21059
21711
  import { homedir as homedir3 } from "os";
21060
- import { dirname as dirname4, join as join6, resolve as resolvePath } from "path";
21712
+ import { dirname as dirname5, join as join7, resolve as resolvePath } from "path";
21061
21713
  import { fileURLToPath as fileURLToPath2 } from "url";
21062
21714
  import { Command as Command7 } from "commander";
21063
21715
  var logger8 = createLogger({ service: "cli", module: "openclaw" });
@@ -21106,10 +21758,12 @@ var OPENCLAW_SETUP_COMMAND_HINT = "Run: clawdentity openclaw setup <agentName>";
21106
21758
  var OPENCLAW_SETUP_RESTART_COMMAND_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} and restart OpenClaw`;
21107
21759
  var OPENCLAW_SETUP_WITH_BASE_URL_HINT = `${OPENCLAW_SETUP_COMMAND_HINT} --openclaw-base-url <url>`;
21108
21760
  var OPENCLAW_PAIRING_COMMAND_HINT = "Run QR pairing first: clawdentity pair start <agentName> --qr and clawdentity pair confirm <agentName> --qr-file <path>";
21109
- var OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT = "Run: openclaw devices list and openclaw devices approve <requestId>";
21761
+ var OPENCLAW_DEVICE_APPROVAL_RECOVERY_HINT = "Run: clawdentity openclaw setup <agentName> (auto-recovers pending OpenClaw gateway device approvals)";
21762
+ var OPENCLAW_GATEWAY_APPROVAL_COMMAND = "openclaw";
21763
+ var OPENCLAW_GATEWAY_APPROVAL_TIMEOUT_MS = 1e4;
21110
21764
  var textEncoder2 = new TextEncoder();
21111
21765
  var textDecoder = new TextDecoder();
21112
- function isRecord8(value) {
21766
+ function isRecord9(value) {
21113
21767
  return typeof value === "object" && value !== null;
21114
21768
  }
21115
21769
  function createCliError6(code, message2, details) {
@@ -21121,7 +21775,7 @@ function createCliError6(code, message2, details) {
21121
21775
  });
21122
21776
  }
21123
21777
  function getErrorCode2(error48) {
21124
- if (!isRecord8(error48)) {
21778
+ if (!isRecord9(error48)) {
21125
21779
  return void 0;
21126
21780
  }
21127
21781
  return typeof error48.code === "string" ? error48.code : void 0;
@@ -21242,12 +21896,12 @@ function resolveOpenclawHomeDir(homeDir) {
21242
21896
  return envOpenclawHome ?? homeDir;
21243
21897
  }
21244
21898
  function resolveDefaultOpenclawStateDir(openclawHomeDir) {
21245
- const newStateDir = join6(openclawHomeDir, OPENCLAW_DIR_NAME);
21899
+ const newStateDir = join7(openclawHomeDir, OPENCLAW_DIR_NAME);
21246
21900
  if (existsSync(newStateDir)) {
21247
21901
  return newStateDir;
21248
21902
  }
21249
21903
  for (const legacyDirName of LEGACY_OPENCLAW_STATE_DIR_NAMES) {
21250
- const legacyStateDir = join6(openclawHomeDir, legacyDirName);
21904
+ const legacyStateDir = join7(openclawHomeDir, legacyDirName);
21251
21905
  if (existsSync(legacyStateDir)) {
21252
21906
  return legacyStateDir;
21253
21907
  }
@@ -21270,16 +21924,16 @@ function resolveOpenclawDir(openclawDir, homeDir) {
21270
21924
  homeDir
21271
21925
  );
21272
21926
  if (envConfigPath !== void 0) {
21273
- return dirname4(envConfigPath);
21927
+ return dirname5(envConfigPath);
21274
21928
  }
21275
21929
  const openclawHomeDir = resolveOpenclawHomeDir(homeDir);
21276
21930
  return resolveDefaultOpenclawStateDir(openclawHomeDir);
21277
21931
  }
21278
21932
  function resolveAgentDirectory(homeDir, agentName) {
21279
- return join6(homeDir, CLAWDENTITY_DIR_NAME, AGENTS_DIR_NAME4, agentName);
21933
+ return join7(homeDir, CLAWDENTITY_DIR_NAME, AGENTS_DIR_NAME4, agentName);
21280
21934
  }
21281
21935
  function resolvePeersPath(homeDir) {
21282
- return join6(homeDir, CLAWDENTITY_DIR_NAME, PEERS_FILE_NAME);
21936
+ return join7(homeDir, CLAWDENTITY_DIR_NAME, PEERS_FILE_NAME);
21283
21937
  }
21284
21938
  function resolveOpenclawConfigPath(openclawDir, homeDir) {
21285
21939
  const envConfigPath = readNonEmptyEnvPath(
@@ -21290,9 +21944,9 @@ function resolveOpenclawConfigPath(openclawDir, homeDir) {
21290
21944
  return envConfigPath;
21291
21945
  }
21292
21946
  const configCandidates = [
21293
- join6(openclawDir, OPENCLAW_CONFIG_FILE_NAME),
21947
+ join7(openclawDir, OPENCLAW_CONFIG_FILE_NAME),
21294
21948
  ...LEGACY_OPENCLAW_CONFIG_FILE_NAMES.map(
21295
- (fileName) => join6(openclawDir, fileName)
21949
+ (fileName) => join7(openclawDir, fileName)
21296
21950
  )
21297
21951
  ];
21298
21952
  for (const candidate of configCandidates) {
@@ -21303,28 +21957,166 @@ function resolveOpenclawConfigPath(openclawDir, homeDir) {
21303
21957
  return configCandidates[0];
21304
21958
  }
21305
21959
  function resolveDefaultTransformSource(openclawDir) {
21306
- return join6(openclawDir, "skills", SKILL_DIR_NAME, RELAY_MODULE_FILE_NAME);
21960
+ return join7(openclawDir, "skills", SKILL_DIR_NAME, RELAY_MODULE_FILE_NAME);
21307
21961
  }
21308
21962
  function resolveTransformTargetPath(openclawDir) {
21309
- return join6(openclawDir, "hooks", "transforms", RELAY_MODULE_FILE_NAME);
21963
+ return join7(openclawDir, "hooks", "transforms", RELAY_MODULE_FILE_NAME);
21310
21964
  }
21311
21965
  function resolveOpenclawAgentNamePath(homeDir) {
21312
- return join6(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_AGENT_FILE_NAME);
21966
+ return join7(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_AGENT_FILE_NAME);
21313
21967
  }
21314
21968
  function resolveRelayRuntimeConfigPath(homeDir) {
21315
- return join6(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_RELAY_RUNTIME_FILE_NAME2);
21969
+ return join7(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_RELAY_RUNTIME_FILE_NAME2);
21316
21970
  }
21317
21971
  function resolveConnectorAssignmentsPath(homeDir) {
21318
- return join6(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_CONNECTORS_FILE_NAME2);
21972
+ return join7(homeDir, CLAWDENTITY_DIR_NAME, OPENCLAW_CONNECTORS_FILE_NAME2);
21319
21973
  }
21320
21974
  function resolveTransformRuntimePath(openclawDir) {
21321
- return join6(openclawDir, "hooks", "transforms", RELAY_RUNTIME_FILE_NAME);
21975
+ return join7(openclawDir, "hooks", "transforms", RELAY_RUNTIME_FILE_NAME);
21322
21976
  }
21323
21977
  function resolveTransformPeersPath(openclawDir) {
21324
- return join6(openclawDir, "hooks", "transforms", RELAY_PEERS_FILE_NAME);
21978
+ return join7(openclawDir, "hooks", "transforms", RELAY_PEERS_FILE_NAME);
21979
+ }
21980
+ async function readOpenclawGatewayPendingState(openclawDir) {
21981
+ const gatewayDevicePendingPath = join7(openclawDir, "devices", "pending.json");
21982
+ try {
21983
+ const pendingPayload = await readJsonFile(gatewayDevicePendingPath);
21984
+ if (!isRecord9(pendingPayload)) {
21985
+ return {
21986
+ status: "invalid",
21987
+ gatewayDevicePendingPath
21988
+ };
21989
+ }
21990
+ return {
21991
+ status: "ok",
21992
+ gatewayDevicePendingPath,
21993
+ pendingRequestIds: Object.keys(pendingPayload)
21994
+ };
21995
+ } catch (error48) {
21996
+ if (getErrorCode2(error48) === "ENOENT") {
21997
+ return {
21998
+ status: "missing",
21999
+ gatewayDevicePendingPath
22000
+ };
22001
+ }
22002
+ return {
22003
+ status: "unreadable",
22004
+ gatewayDevicePendingPath
22005
+ };
22006
+ }
22007
+ }
22008
+ function resolveOpenclawGatewayApprovalCommand() {
22009
+ const envOverride = process.env.OPENCLAW_GATEWAY_APPROVAL_COMMAND?.trim();
22010
+ if (typeof envOverride === "string" && envOverride.length > 0) {
22011
+ return envOverride;
22012
+ }
22013
+ return OPENCLAW_GATEWAY_APPROVAL_COMMAND;
22014
+ }
22015
+ async function runOpenclawGatewayApprovalCommand(input) {
22016
+ return await new Promise(
22017
+ (resolve2) => {
22018
+ const child = spawn(input.command, input.args, {
22019
+ env: {
22020
+ ...process.env,
22021
+ OPENCLAW_STATE_DIR: input.openclawDir,
22022
+ OPENCLAW_CONFIG_PATH: input.openclawConfigPath
22023
+ },
22024
+ stdio: ["ignore", "pipe", "pipe"]
22025
+ });
22026
+ let settled = false;
22027
+ let stdout = "";
22028
+ let stderr = "";
22029
+ const finalize2 = (result) => {
22030
+ if (settled) {
22031
+ return;
22032
+ }
22033
+ settled = true;
22034
+ resolve2({
22035
+ ...result,
22036
+ stdout: stdout.trim(),
22037
+ stderr: stderr.trim()
22038
+ });
22039
+ };
22040
+ const timeout = setTimeout(() => {
22041
+ try {
22042
+ child.kill("SIGTERM");
22043
+ } catch {
22044
+ }
22045
+ finalize2({
22046
+ ok: false,
22047
+ errorMessage: `command timed out after ${OPENCLAW_GATEWAY_APPROVAL_TIMEOUT_MS}ms`
22048
+ });
22049
+ }, OPENCLAW_GATEWAY_APPROVAL_TIMEOUT_MS);
22050
+ child.stdout?.on("data", (chunk) => {
22051
+ stdout += String(chunk);
22052
+ });
22053
+ child.stderr?.on("data", (chunk) => {
22054
+ stderr += String(chunk);
22055
+ });
22056
+ child.once("error", (error48) => {
22057
+ clearTimeout(timeout);
22058
+ const errorCode = getErrorCode2(error48);
22059
+ finalize2({
22060
+ ok: false,
22061
+ unavailable: errorCode === "ENOENT",
22062
+ errorMessage: error48 instanceof Error ? error48.message : "failed to run openclaw command"
22063
+ });
22064
+ });
22065
+ child.once("close", (exitCode) => {
22066
+ clearTimeout(timeout);
22067
+ finalize2({
22068
+ ok: exitCode === 0,
22069
+ exitCode: typeof exitCode === "number" ? exitCode : void 0
22070
+ });
22071
+ });
22072
+ }
22073
+ );
22074
+ }
22075
+ async function runOpenclawGatewayDeviceApproval(input) {
22076
+ const command = resolveOpenclawGatewayApprovalCommand();
22077
+ return await runOpenclawGatewayApprovalCommand({
22078
+ command,
22079
+ args: ["devices", "approve", input.requestId, "--json"],
22080
+ openclawDir: input.openclawDir,
22081
+ openclawConfigPath: input.openclawConfigPath
22082
+ });
22083
+ }
22084
+ async function autoApproveOpenclawGatewayDevices(input) {
22085
+ const pendingState = await readOpenclawGatewayPendingState(input.openclawDir);
22086
+ if (pendingState.status !== "ok" || pendingState.pendingRequestIds.length === 0) {
22087
+ return void 0;
22088
+ }
22089
+ const openclawConfigPath = resolveOpenclawConfigPath(
22090
+ input.openclawDir,
22091
+ input.homeDir
22092
+ );
22093
+ const approvalRunner = input.runner ?? runOpenclawGatewayDeviceApproval;
22094
+ const attempts = [];
22095
+ for (const requestId of pendingState.pendingRequestIds) {
22096
+ const execution = await approvalRunner({
22097
+ requestId,
22098
+ openclawDir: input.openclawDir,
22099
+ openclawConfigPath
22100
+ });
22101
+ attempts.push({
22102
+ requestId,
22103
+ ok: execution.ok,
22104
+ unavailable: execution.unavailable === true,
22105
+ reason: execution.errorMessage ?? (execution.stderr && execution.stderr.length > 0 ? execution.stderr : execution.stdout && execution.stdout.length > 0 ? execution.stdout : void 0),
22106
+ exitCode: execution.exitCode
22107
+ });
22108
+ if (execution.unavailable === true) {
22109
+ break;
22110
+ }
22111
+ }
22112
+ return {
22113
+ gatewayDevicePendingPath: pendingState.gatewayDevicePendingPath,
22114
+ pendingRequestIds: pendingState.pendingRequestIds,
22115
+ attempts
22116
+ };
21325
22117
  }
21326
22118
  async function readJsonFile(filePath) {
21327
- const raw = await readFile5(filePath, "utf8");
22119
+ const raw = await readFile6(filePath, "utf8");
21328
22120
  try {
21329
22121
  return JSON.parse(raw);
21330
22122
  } catch {
@@ -21336,13 +22128,13 @@ async function readJsonFile(filePath) {
21336
22128
  async function ensureLocalAgentCredentials(homeDir, agentName) {
21337
22129
  const agentDir = resolveAgentDirectory(homeDir, agentName);
21338
22130
  const requiredFiles = [
21339
- join6(agentDir, SECRET_KEY_FILE_NAME2),
21340
- join6(agentDir, AIT_FILE_NAME3)
22131
+ join7(agentDir, SECRET_KEY_FILE_NAME2),
22132
+ join7(agentDir, AIT_FILE_NAME3)
21341
22133
  ];
21342
22134
  for (const filePath of requiredFiles) {
21343
22135
  let content;
21344
22136
  try {
21345
- content = await readFile5(filePath, "utf8");
22137
+ content = await readFile6(filePath, "utf8");
21346
22138
  } catch (error48) {
21347
22139
  if (getErrorCode2(error48) === "ENOENT") {
21348
22140
  throw createCliError6(
@@ -21363,8 +22155,8 @@ async function ensureLocalAgentCredentials(homeDir, agentName) {
21363
22155
  }
21364
22156
  }
21365
22157
  async function writeSecureFile3(filePath, content) {
21366
- await mkdir5(dirname4(filePath), { recursive: true });
21367
- await writeFile5(filePath, content, "utf8");
22158
+ await mkdir6(dirname5(filePath), { recursive: true });
22159
+ await writeFile6(filePath, content, "utf8");
21368
22160
  await chmod3(filePath, FILE_MODE3);
21369
22161
  }
21370
22162
  async function loadPeersConfig(peersPath) {
@@ -21377,7 +22169,7 @@ async function loadPeersConfig(peersPath) {
21377
22169
  }
21378
22170
  throw error48;
21379
22171
  }
21380
- if (!isRecord8(parsed)) {
22172
+ if (!isRecord9(parsed)) {
21381
22173
  throw createCliError6(
21382
22174
  "CLI_OPENCLAW_INVALID_PEERS_CONFIG",
21383
22175
  "Peer config root must be a JSON object",
@@ -21388,7 +22180,7 @@ async function loadPeersConfig(peersPath) {
21388
22180
  if (peersValue === void 0) {
21389
22181
  return { peers: {} };
21390
22182
  }
21391
- if (!isRecord8(peersValue)) {
22183
+ if (!isRecord9(peersValue)) {
21392
22184
  throw createCliError6(
21393
22185
  "CLI_OPENCLAW_INVALID_PEERS_CONFIG",
21394
22186
  "Peer config peers field must be an object",
@@ -21398,7 +22190,7 @@ async function loadPeersConfig(peersPath) {
21398
22190
  const peers = {};
21399
22191
  for (const [alias, value] of Object.entries(peersValue)) {
21400
22192
  const normalizedAlias = parsePeerAlias(alias);
21401
- if (!isRecord8(value)) {
22193
+ if (!isRecord9(value)) {
21402
22194
  throw createCliError6(
21403
22195
  "CLI_OPENCLAW_INVALID_PEERS_CONFIG",
21404
22196
  "Peer entry must be an object",
@@ -21429,7 +22221,7 @@ function parseConnectorBaseUrlForAssignment(value, label) {
21429
22221
  });
21430
22222
  }
21431
22223
  function parseConnectorAssignments(value, connectorAssignmentsPath) {
21432
- if (!isRecord8(value)) {
22224
+ if (!isRecord9(value)) {
21433
22225
  throw createCliError6(
21434
22226
  "CLI_OPENCLAW_INVALID_CONNECTOR_ASSIGNMENTS",
21435
22227
  "Connector assignments config must be an object",
@@ -21437,12 +22229,12 @@ function parseConnectorAssignments(value, connectorAssignmentsPath) {
21437
22229
  );
21438
22230
  }
21439
22231
  const agentsRaw = value.agents;
21440
- if (!isRecord8(agentsRaw)) {
22232
+ if (!isRecord9(agentsRaw)) {
21441
22233
  return { agents: {} };
21442
22234
  }
21443
22235
  const agents = {};
21444
22236
  for (const [agentName, entryValue] of Object.entries(agentsRaw)) {
21445
- if (!isRecord8(entryValue)) {
22237
+ if (!isRecord9(entryValue)) {
21446
22238
  throw createCliError6(
21447
22239
  "CLI_OPENCLAW_INVALID_CONNECTOR_ASSIGNMENTS",
21448
22240
  "Connector assignment entry must be an object",
@@ -21547,14 +22339,28 @@ function resolveConnectorStatusUrl(connectorBaseUrl) {
21547
22339
  ).toString();
21548
22340
  }
21549
22341
  function parseConnectorStatusPayload(payload) {
21550
- if (!isRecord8(payload) || typeof payload.websocketConnected !== "boolean") {
22342
+ if (!isRecord9(payload) || typeof payload.websocketConnected !== "boolean") {
21551
22343
  throw createCliError6(
21552
22344
  "CLI_OPENCLAW_SETUP_CONNECTOR_STATUS_INVALID",
21553
22345
  "Connector status response is invalid"
21554
22346
  );
21555
22347
  }
21556
22348
  return {
21557
- websocketConnected: payload.websocketConnected
22349
+ websocketConnected: payload.websocketConnected,
22350
+ inboundInbox: isRecord9(payload.inboundInbox) ? {
22351
+ pendingCount: typeof payload.inboundInbox.pendingCount === "number" ? payload.inboundInbox.pendingCount : void 0,
22352
+ pendingBytes: typeof payload.inboundInbox.pendingBytes === "number" ? payload.inboundInbox.pendingBytes : void 0,
22353
+ oldestPendingAt: typeof payload.inboundInbox.oldestPendingAt === "string" ? payload.inboundInbox.oldestPendingAt : void 0,
22354
+ nextAttemptAt: typeof payload.inboundInbox.nextAttemptAt === "string" ? payload.inboundInbox.nextAttemptAt : void 0,
22355
+ lastReplayAt: typeof payload.inboundInbox.lastReplayAt === "string" ? payload.inboundInbox.lastReplayAt : void 0,
22356
+ lastReplayError: typeof payload.inboundInbox.lastReplayError === "string" ? payload.inboundInbox.lastReplayError : void 0,
22357
+ replayerActive: typeof payload.inboundInbox.replayerActive === "boolean" ? payload.inboundInbox.replayerActive : void 0
22358
+ } : void 0,
22359
+ openclawHook: isRecord9(payload.openclawHook) ? {
22360
+ url: typeof payload.openclawHook.url === "string" ? payload.openclawHook.url : void 0,
22361
+ lastAttemptAt: typeof payload.openclawHook.lastAttemptAt === "string" ? payload.openclawHook.lastAttemptAt : void 0,
22362
+ lastAttemptStatus: payload.openclawHook.lastAttemptStatus === "ok" || payload.openclawHook.lastAttemptStatus === "failed" ? payload.openclawHook.lastAttemptStatus : void 0
22363
+ } : void 0
21558
22364
  };
21559
22365
  }
21560
22366
  async function fetchConnectorHealthStatus(input) {
@@ -21588,6 +22394,8 @@ async function fetchConnectorHealthStatus(input) {
21588
22394
  const parsed = parseConnectorStatusPayload(payload);
21589
22395
  return {
21590
22396
  connected: parsed.websocketConnected,
22397
+ inboundInbox: parsed.inboundInbox,
22398
+ openclawHook: parsed.openclawHook,
21591
22399
  reachable: true,
21592
22400
  statusUrl,
21593
22401
  reason: parsed.websocketConnected ? void 0 : "connector websocket is disconnected"
@@ -21630,14 +22438,14 @@ async function waitForConnectorConnected(input) {
21630
22438
  return latest;
21631
22439
  }
21632
22440
  function resolveConnectorRunDir(homeDir) {
21633
- return join6(homeDir, CLAWDENTITY_DIR_NAME, CONNECTOR_RUN_DIR_NAME);
22441
+ return join7(homeDir, CLAWDENTITY_DIR_NAME, CONNECTOR_RUN_DIR_NAME);
21634
22442
  }
21635
22443
  function resolveConnectorPidPath(homeDir, agentName) {
21636
- return join6(resolveConnectorRunDir(homeDir), `connector-${agentName}.pid`);
22444
+ return join7(resolveConnectorRunDir(homeDir), `connector-${agentName}.pid`);
21637
22445
  }
21638
22446
  async function readConnectorPidFile(pidPath) {
21639
22447
  try {
21640
- const raw = (await readFile5(pidPath, "utf8")).trim();
22448
+ const raw = (await readFile6(pidPath, "utf8")).trim();
21641
22449
  if (raw.length === 0) {
21642
22450
  return void 0;
21643
22451
  }
@@ -21678,7 +22486,7 @@ function resolveCliEntryPathForDetachedStart() {
21678
22486
  return argvEntry;
21679
22487
  }
21680
22488
  const modulePath = fileURLToPath2(import.meta.url);
21681
- return join6(dirname4(modulePath), "..", "bin.js");
22489
+ return join7(dirname5(modulePath), "..", "bin.js");
21682
22490
  }
21683
22491
  async function startDetachedConnectorRuntime(input) {
21684
22492
  await stopDetachedConnectorIfRunning({
@@ -21686,7 +22494,7 @@ async function startDetachedConnectorRuntime(input) {
21686
22494
  agentName: input.agentName
21687
22495
  });
21688
22496
  const runDir = resolveConnectorRunDir(input.homeDir);
21689
- await mkdir5(runDir, { recursive: true });
22497
+ await mkdir6(runDir, { recursive: true });
21690
22498
  const cliEntryPath = resolveCliEntryPathForDetachedStart();
21691
22499
  const args = [
21692
22500
  cliEntryPath,
@@ -21764,7 +22572,7 @@ async function startSetupConnectorRuntime(input) {
21764
22572
  };
21765
22573
  }
21766
22574
  function parseRelayRuntimeConfig(value, relayRuntimeConfigPath) {
21767
- if (!isRecord8(value)) {
22575
+ if (!isRecord9(value)) {
21768
22576
  throw createCliError6(
21769
22577
  "CLI_OPENCLAW_INVALID_RELAY_RUNTIME_CONFIG",
21770
22578
  "Relay runtime config must be an object",
@@ -21844,7 +22652,7 @@ function normalizeStringArrayWithValues(value, requiredValues) {
21844
22652
  return Array.from(normalized);
21845
22653
  }
21846
22654
  function resolveHookDefaultSessionKey(config2, hooks) {
21847
- const session = isRecord8(config2.session) ? config2.session : {};
22655
+ const session = isRecord9(config2.session) ? config2.session : {};
21848
22656
  const scope = typeof session.scope === "string" ? session.scope.trim().toLowerCase() : "";
21849
22657
  const configuredMainSessionKey = resolveConfiguredOpenclawMainSessionKey(session);
21850
22658
  if (typeof hooks.defaultSessionKey === "string" && hooks.defaultSessionKey.trim().length > 0) {
@@ -21883,20 +22691,20 @@ function generateOpenclawHookToken() {
21883
22691
  return randomBytes3(OPENCLAW_HOOK_TOKEN_BYTES).toString("hex");
21884
22692
  }
21885
22693
  function upsertRelayHookMapping(mappingsValue) {
21886
- const mappings = Array.isArray(mappingsValue) ? mappingsValue.filter(isRecord8).map((mapping) => ({ ...mapping })) : [];
22694
+ const mappings = Array.isArray(mappingsValue) ? mappingsValue.filter(isRecord9).map((mapping) => ({ ...mapping })) : [];
21887
22695
  const existingIndex = mappings.findIndex((mapping) => {
21888
22696
  if (mapping.id === HOOK_MAPPING_ID) {
21889
22697
  return true;
21890
22698
  }
21891
- if (!isRecord8(mapping.match)) {
22699
+ if (!isRecord9(mapping.match)) {
21892
22700
  return false;
21893
22701
  }
21894
22702
  return mapping.match.path === HOOK_PATH_SEND_TO_PEER;
21895
22703
  });
21896
- const baseMapping = existingIndex >= 0 && isRecord8(mappings[existingIndex]) ? mappings[existingIndex] : {};
21897
- const nextMatch = isRecord8(baseMapping.match) ? { ...baseMapping.match } : {};
22704
+ const baseMapping = existingIndex >= 0 && isRecord9(mappings[existingIndex]) ? mappings[existingIndex] : {};
22705
+ const nextMatch = isRecord9(baseMapping.match) ? { ...baseMapping.match } : {};
21898
22706
  nextMatch.path = HOOK_PATH_SEND_TO_PEER;
21899
- const nextTransform = isRecord8(baseMapping.transform) ? { ...baseMapping.transform } : {};
22707
+ const nextTransform = isRecord9(baseMapping.transform) ? { ...baseMapping.transform } : {};
21900
22708
  nextTransform.module = RELAY_MODULE_FILE_NAME;
21901
22709
  const relayMapping = {
21902
22710
  ...baseMapping,
@@ -21927,14 +22735,14 @@ async function patchOpenclawConfig(openclawConfigPath, hookToken) {
21927
22735
  }
21928
22736
  throw error48;
21929
22737
  }
21930
- if (!isRecord8(config2)) {
22738
+ if (!isRecord9(config2)) {
21931
22739
  throw createCliError6(
21932
22740
  "CLI_OPENCLAW_INVALID_CONFIG",
21933
22741
  "OpenClaw config root must be an object",
21934
22742
  { openclawConfigPath }
21935
22743
  );
21936
22744
  }
21937
- const hooks = isRecord8(config2.hooks) ? { ...config2.hooks } : {};
22745
+ const hooks = isRecord9(config2.hooks) ? { ...config2.hooks } : {};
21938
22746
  const existingHookToken = typeof hooks.token === "string" && hooks.token.trim().length > 0 ? hooks.token.trim() : void 0;
21939
22747
  const preferredHookToken = typeof hookToken === "string" && hookToken.trim().length > 0 ? hookToken.trim() : void 0;
21940
22748
  const resolvedHookToken = existingHookToken ?? preferredHookToken ?? generateOpenclawHookToken();
@@ -21952,7 +22760,7 @@ async function patchOpenclawConfig(openclawConfigPath, hookToken) {
21952
22760
  ...config2,
21953
22761
  hooks
21954
22762
  };
21955
- await writeFile5(
22763
+ await writeFile6(
21956
22764
  openclawConfigPath,
21957
22765
  `${JSON.stringify(nextConfig, null, 2)}
21958
22766
  `,
@@ -21973,10 +22781,10 @@ function toDoctorResult(checks) {
21973
22781
  };
21974
22782
  }
21975
22783
  function isRelayHookMapping(value) {
21976
- if (!isRecord8(value)) {
22784
+ if (!isRecord9(value)) {
21977
22785
  return false;
21978
22786
  }
21979
- if (!isRecord8(value.match) || value.match.path !== HOOK_PATH_SEND_TO_PEER) {
22787
+ if (!isRecord9(value.match) || value.match.path !== HOOK_PATH_SEND_TO_PEER) {
21980
22788
  return false;
21981
22789
  }
21982
22790
  if (typeof value.id === "string" && value.id !== HOOK_MAPPING_ID) {
@@ -21985,7 +22793,7 @@ function isRelayHookMapping(value) {
21985
22793
  return true;
21986
22794
  }
21987
22795
  function hasRelayTransformModule(value) {
21988
- if (!isRecord8(value) || !isRecord8(value.transform)) {
22796
+ if (!isRecord9(value) || !isRecord9(value.transform)) {
21989
22797
  return false;
21990
22798
  }
21991
22799
  return value.transform.module === RELAY_MODULE_FILE_NAME;
@@ -22164,7 +22972,7 @@ async function runOpenclawDoctor(options = {}) {
22164
22972
  const selectedAgentPath = resolveOpenclawAgentNamePath(homeDir);
22165
22973
  let selectedAgentName;
22166
22974
  try {
22167
- const selectedAgentRaw = await readFile5(selectedAgentPath, "utf8");
22975
+ const selectedAgentRaw = await readFile6(selectedAgentPath, "utf8");
22168
22976
  selectedAgentName = assertValidAgentName(selectedAgentRaw.trim());
22169
22977
  checks.push(
22170
22978
  toDoctorCheck({
@@ -22287,9 +23095,9 @@ async function runOpenclawDoctor(options = {}) {
22287
23095
  const relayTransformRuntimePath = resolveTransformRuntimePath(openclawDir);
22288
23096
  const relayTransformPeersPath = resolveTransformPeersPath(openclawDir);
22289
23097
  try {
22290
- const transformContents = await readFile5(transformTargetPath, "utf8");
22291
- const runtimeContents = await readFile5(relayTransformRuntimePath, "utf8");
22292
- const peersSnapshotContents = await readFile5(
23098
+ const transformContents = await readFile6(transformTargetPath, "utf8");
23099
+ const runtimeContents = await readFile6(relayTransformRuntimePath, "utf8");
23100
+ const peersSnapshotContents = await readFile6(
22293
23101
  relayTransformPeersPath,
22294
23102
  "utf8"
22295
23103
  );
@@ -22342,10 +23150,10 @@ async function runOpenclawDoctor(options = {}) {
22342
23150
  const openclawConfigPath = resolveOpenclawConfigPath(openclawDir, homeDir);
22343
23151
  try {
22344
23152
  const openclawConfig = await readJsonFile(openclawConfigPath);
22345
- if (!isRecord8(openclawConfig)) {
23153
+ if (!isRecord9(openclawConfig)) {
22346
23154
  throw new Error("root");
22347
23155
  }
22348
- const hooks = isRecord8(openclawConfig.hooks) ? openclawConfig.hooks : {};
23156
+ const hooks = isRecord9(openclawConfig.hooks) ? openclawConfig.hooks : {};
22349
23157
  const hooksEnabled = hooks.enabled === true;
22350
23158
  const hookToken = typeof hooks.token === "string" && hooks.token.trim().length > 0 ? hooks.token.trim() : void 0;
22351
23159
  const defaultSessionKey = typeof hooks.defaultSessionKey === "string" && hooks.defaultSessionKey.trim().length > 0 ? hooks.defaultSessionKey.trim() : void 0;
@@ -22357,7 +23165,7 @@ async function runOpenclawDoctor(options = {}) {
22357
23165
  const missingRequiredSessionPrefixes = defaultSessionKey === void 0 ? ["hook:"] : ["hook:", defaultSessionKey].filter(
22358
23166
  (prefix) => !allowedSessionKeyPrefixes.includes(prefix)
22359
23167
  );
22360
- const mappings = Array.isArray(hooks.mappings) ? hooks.mappings.filter(isRecord8) : [];
23168
+ const mappings = Array.isArray(hooks.mappings) ? hooks.mappings.filter(isRecord9) : [];
22361
23169
  const relayMapping = mappings.find(
22362
23170
  (mapping) => isRelayHookMapping(mapping)
22363
23171
  );
@@ -22511,81 +23319,99 @@ async function runOpenclawDoctor(options = {}) {
22511
23319
  })
22512
23320
  );
22513
23321
  }
22514
- const gatewayDevicePendingPath = join6(openclawDir, "devices", "pending.json");
22515
- try {
22516
- const pendingPayload = await readJsonFile(gatewayDevicePendingPath);
22517
- if (!isRecord8(pendingPayload)) {
23322
+ const gatewayPendingState = await readOpenclawGatewayPendingState(openclawDir);
23323
+ if (gatewayPendingState.status === "missing") {
23324
+ checks.push(
23325
+ toDoctorCheck({
23326
+ id: "state.gatewayDevicePairing",
23327
+ label: "OpenClaw gateway device pairing",
23328
+ status: "pass",
23329
+ message: "no pending gateway device approvals file was found",
23330
+ details: {
23331
+ gatewayDevicePendingPath: gatewayPendingState.gatewayDevicePendingPath
23332
+ }
23333
+ })
23334
+ );
23335
+ } else if (gatewayPendingState.status === "invalid") {
23336
+ checks.push(
23337
+ toDoctorCheck({
23338
+ id: "state.gatewayDevicePairing",
23339
+ label: "OpenClaw gateway device pairing",
23340
+ status: "fail",
23341
+ message: `invalid pending device approvals file: ${gatewayPendingState.gatewayDevicePendingPath}`,
23342
+ remediationHint: OPENCLAW_DEVICE_APPROVAL_RECOVERY_HINT,
23343
+ details: {
23344
+ gatewayDevicePendingPath: gatewayPendingState.gatewayDevicePendingPath
23345
+ }
23346
+ })
23347
+ );
23348
+ } else if (gatewayPendingState.status === "unreadable") {
23349
+ checks.push(
23350
+ toDoctorCheck({
23351
+ id: "state.gatewayDevicePairing",
23352
+ label: "OpenClaw gateway device pairing",
23353
+ status: "fail",
23354
+ message: `unable to read pending device approvals at ${gatewayPendingState.gatewayDevicePendingPath}`,
23355
+ remediationHint: OPENCLAW_DEVICE_APPROVAL_RECOVERY_HINT,
23356
+ details: {
23357
+ gatewayDevicePendingPath: gatewayPendingState.gatewayDevicePendingPath
23358
+ }
23359
+ })
23360
+ );
23361
+ } else if (gatewayPendingState.pendingRequestIds.length === 0) {
23362
+ checks.push(
23363
+ toDoctorCheck({
23364
+ id: "state.gatewayDevicePairing",
23365
+ label: "OpenClaw gateway device pairing",
23366
+ status: "pass",
23367
+ message: "no pending gateway device approvals",
23368
+ details: {
23369
+ gatewayDevicePendingPath: gatewayPendingState.gatewayDevicePendingPath
23370
+ }
23371
+ })
23372
+ );
23373
+ } else {
23374
+ checks.push(
23375
+ toDoctorCheck({
23376
+ id: "state.gatewayDevicePairing",
23377
+ label: "OpenClaw gateway device pairing",
23378
+ status: "fail",
23379
+ message: `pending gateway device approvals: ${gatewayPendingState.pendingRequestIds.length}`,
23380
+ remediationHint: OPENCLAW_DEVICE_APPROVAL_RECOVERY_HINT,
23381
+ details: {
23382
+ gatewayDevicePendingPath: gatewayPendingState.gatewayDevicePendingPath,
23383
+ pendingRequestIds: gatewayPendingState.pendingRequestIds
23384
+ }
23385
+ })
23386
+ );
23387
+ }
23388
+ if (options.includeConnectorRuntimeCheck !== false) {
23389
+ if (selectedAgentName === void 0) {
22518
23390
  checks.push(
22519
23391
  toDoctorCheck({
22520
- id: "state.gatewayDevicePairing",
22521
- label: "OpenClaw gateway device pairing",
23392
+ id: "state.connectorRuntime",
23393
+ label: "Connector runtime",
22522
23394
  status: "fail",
22523
- message: `invalid pending device approvals file: ${gatewayDevicePendingPath}`,
22524
- remediationHint: OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT,
22525
- details: { gatewayDevicePendingPath }
22526
- })
22527
- );
22528
- } else {
22529
- const pendingRequestIds = Object.keys(pendingPayload);
22530
- if (pendingRequestIds.length === 0) {
22531
- checks.push(
22532
- toDoctorCheck({
22533
- id: "state.gatewayDevicePairing",
22534
- label: "OpenClaw gateway device pairing",
22535
- status: "pass",
22536
- message: "no pending gateway device approvals",
22537
- details: { gatewayDevicePendingPath }
22538
- })
22539
- );
22540
- } else {
22541
- checks.push(
22542
- toDoctorCheck({
22543
- id: "state.gatewayDevicePairing",
22544
- label: "OpenClaw gateway device pairing",
22545
- status: "fail",
22546
- message: `pending gateway device approvals: ${pendingRequestIds.length}`,
22547
- remediationHint: OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT,
22548
- details: {
22549
- gatewayDevicePendingPath,
22550
- pendingRequestIds
22551
- }
22552
- })
22553
- );
22554
- }
22555
- }
22556
- } catch (error48) {
22557
- if (getErrorCode2(error48) === "ENOENT") {
22558
- checks.push(
22559
- toDoctorCheck({
22560
- id: "state.gatewayDevicePairing",
22561
- label: "OpenClaw gateway device pairing",
22562
- status: "pass",
22563
- message: "no pending gateway device approvals file was found",
22564
- details: { gatewayDevicePendingPath }
23395
+ message: "cannot validate connector runtime without selected agent marker",
23396
+ remediationHint: OPENCLAW_SETUP_COMMAND_HINT
22565
23397
  })
22566
23398
  );
22567
- } else {
22568
23399
  checks.push(
22569
23400
  toDoctorCheck({
22570
- id: "state.gatewayDevicePairing",
22571
- label: "OpenClaw gateway device pairing",
23401
+ id: "state.connectorInboundInbox",
23402
+ label: "Connector inbound inbox",
22572
23403
  status: "fail",
22573
- message: `unable to read pending device approvals at ${gatewayDevicePendingPath}`,
22574
- remediationHint: OPENCLAW_DEVICE_APPROVAL_COMMAND_HINT,
22575
- details: { gatewayDevicePendingPath }
23404
+ message: "cannot validate connector inbound inbox without selected agent marker",
23405
+ remediationHint: OPENCLAW_SETUP_COMMAND_HINT
22576
23406
  })
22577
23407
  );
22578
- }
22579
- }
22580
- if (options.includeConnectorRuntimeCheck !== false) {
22581
- if (selectedAgentName === void 0) {
22582
23408
  checks.push(
22583
23409
  toDoctorCheck({
22584
- id: "state.connectorRuntime",
22585
- label: "Connector runtime",
23410
+ id: "state.openclawHookHealth",
23411
+ label: "OpenClaw hook health",
22586
23412
  status: "fail",
22587
- message: "cannot validate connector runtime without selected agent marker",
22588
- remediationHint: OPENCLAW_SETUP_COMMAND_HINT
23413
+ message: "cannot validate OpenClaw hook health without selected agent marker",
23414
+ remediationHint: OPENCLAW_SETUP_RESTART_COMMAND_HINT
22589
23415
  })
22590
23416
  );
22591
23417
  } else {
@@ -22606,6 +23432,26 @@ async function runOpenclawDoctor(options = {}) {
22606
23432
  details: { connectorAssignmentsPath, selectedAgentName }
22607
23433
  })
22608
23434
  );
23435
+ checks.push(
23436
+ toDoctorCheck({
23437
+ id: "state.connectorInboundInbox",
23438
+ label: "Connector inbound inbox",
23439
+ status: "fail",
23440
+ message: `no connector assignment found for ${selectedAgentName}`,
23441
+ remediationHint: OPENCLAW_SETUP_COMMAND_HINT,
23442
+ details: { connectorAssignmentsPath, selectedAgentName }
23443
+ })
23444
+ );
23445
+ checks.push(
23446
+ toDoctorCheck({
23447
+ id: "state.openclawHookHealth",
23448
+ label: "OpenClaw hook health",
23449
+ status: "fail",
23450
+ message: `no connector assignment found for ${selectedAgentName}`,
23451
+ remediationHint: OPENCLAW_SETUP_RESTART_COMMAND_HINT,
23452
+ details: { connectorAssignmentsPath, selectedAgentName }
23453
+ })
23454
+ );
22609
23455
  } else {
22610
23456
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
22611
23457
  if (typeof fetchImpl !== "function") {
@@ -22618,6 +23464,24 @@ async function runOpenclawDoctor(options = {}) {
22618
23464
  remediationHint: "Run doctor in a Node runtime with fetch support, or rerun openclaw setup"
22619
23465
  })
22620
23466
  );
23467
+ checks.push(
23468
+ toDoctorCheck({
23469
+ id: "state.connectorInboundInbox",
23470
+ label: "Connector inbound inbox",
23471
+ status: "fail",
23472
+ message: "fetch implementation is unavailable for connector inbox checks",
23473
+ remediationHint: OPENCLAW_SETUP_COMMAND_HINT
23474
+ })
23475
+ );
23476
+ checks.push(
23477
+ toDoctorCheck({
23478
+ id: "state.openclawHookHealth",
23479
+ label: "OpenClaw hook health",
23480
+ status: "fail",
23481
+ message: "fetch implementation is unavailable for OpenClaw hook health checks",
23482
+ remediationHint: OPENCLAW_SETUP_RESTART_COMMAND_HINT
23483
+ })
23484
+ );
22621
23485
  } else {
22622
23486
  const connectorStatus = await fetchConnectorHealthStatus({
22623
23487
  connectorBaseUrl: assignment.connectorBaseUrl,
@@ -22636,6 +23500,36 @@ async function runOpenclawDoctor(options = {}) {
22636
23500
  }
22637
23501
  })
22638
23502
  );
23503
+ const inboxPendingCount = connectorStatus.inboundInbox?.pendingCount ?? 0;
23504
+ const replayError = connectorStatus.inboundInbox?.lastReplayError;
23505
+ checks.push(
23506
+ toDoctorCheck({
23507
+ id: "state.connectorInboundInbox",
23508
+ label: "Connector inbound inbox",
23509
+ status: "pass",
23510
+ message: inboxPendingCount === 0 ? "connector inbound inbox is empty" : `connector inbound inbox has ${inboxPendingCount} pending message(s)`,
23511
+ details: {
23512
+ connectorStatusUrl: connectorStatus.statusUrl,
23513
+ connectorBaseUrl: assignment.connectorBaseUrl,
23514
+ ...connectorStatus.inboundInbox
23515
+ }
23516
+ })
23517
+ );
23518
+ checks.push(
23519
+ toDoctorCheck({
23520
+ id: "state.openclawHookHealth",
23521
+ label: "OpenClaw hook health",
23522
+ status: connectorStatus.openclawHook?.lastAttemptStatus === "failed" && inboxPendingCount > 0 ? "fail" : "pass",
23523
+ message: connectorStatus.openclawHook?.lastAttemptStatus === "failed" && inboxPendingCount > 0 ? `connector replay to local OpenClaw hook is failing: ${replayError ?? "unknown error"}` : "connector replay to local OpenClaw hook is healthy",
23524
+ remediationHint: connectorStatus.openclawHook?.lastAttemptStatus === "failed" && inboxPendingCount > 0 ? OPENCLAW_SETUP_RESTART_COMMAND_HINT : void 0,
23525
+ details: {
23526
+ connectorStatusUrl: connectorStatus.statusUrl,
23527
+ connectorBaseUrl: assignment.connectorBaseUrl,
23528
+ ...connectorStatus.openclawHook,
23529
+ inboxPendingCount
23530
+ }
23531
+ })
23532
+ );
22639
23533
  } else {
22640
23534
  const reason = connectorStatus.reason ?? "connector runtime is unavailable";
22641
23535
  checks.push(
@@ -22651,6 +23545,32 @@ async function runOpenclawDoctor(options = {}) {
22651
23545
  }
22652
23546
  })
22653
23547
  );
23548
+ checks.push(
23549
+ toDoctorCheck({
23550
+ id: "state.connectorInboundInbox",
23551
+ label: "Connector inbound inbox",
23552
+ status: "fail",
23553
+ message: `unable to read connector inbound inbox status: ${reason}`,
23554
+ remediationHint: OPENCLAW_SETUP_COMMAND_HINT,
23555
+ details: {
23556
+ connectorStatusUrl: connectorStatus.statusUrl,
23557
+ connectorBaseUrl: assignment.connectorBaseUrl
23558
+ }
23559
+ })
23560
+ );
23561
+ checks.push(
23562
+ toDoctorCheck({
23563
+ id: "state.openclawHookHealth",
23564
+ label: "OpenClaw hook health",
23565
+ status: "fail",
23566
+ message: `unable to verify OpenClaw hook health: ${reason}`,
23567
+ remediationHint: OPENCLAW_SETUP_RESTART_COMMAND_HINT,
23568
+ details: {
23569
+ connectorStatusUrl: connectorStatus.statusUrl,
23570
+ connectorBaseUrl: assignment.connectorBaseUrl
23571
+ }
23572
+ })
23573
+ );
22654
23574
  }
22655
23575
  }
22656
23576
  }
@@ -22665,6 +23585,24 @@ async function runOpenclawDoctor(options = {}) {
22665
23585
  details: { connectorAssignmentsPath }
22666
23586
  })
22667
23587
  );
23588
+ checks.push(
23589
+ toDoctorCheck({
23590
+ id: "state.connectorInboundInbox",
23591
+ label: "Connector inbound inbox",
23592
+ status: "fail",
23593
+ message: "cannot validate connector inbound inbox without connector assignment",
23594
+ remediationHint: OPENCLAW_SETUP_COMMAND_HINT
23595
+ })
23596
+ );
23597
+ checks.push(
23598
+ toDoctorCheck({
23599
+ id: "state.openclawHookHealth",
23600
+ label: "OpenClaw hook health",
23601
+ status: "fail",
23602
+ message: "cannot validate OpenClaw hook health without connector assignment",
23603
+ remediationHint: OPENCLAW_SETUP_RESTART_COMMAND_HINT
23604
+ })
23605
+ );
22668
23606
  }
22669
23607
  }
22670
23608
  }
@@ -22692,7 +23630,7 @@ function parseRelayProbeFailure(input) {
22692
23630
  if (input.status === 500) {
22693
23631
  return {
22694
23632
  message: "Relay probe failed inside local relay pipeline",
22695
- remediationHint: "Check connector runtime and peer pairing; rerun clawdentity openclaw doctor"
23633
+ remediationHint: "Check peer pairing and rerun: clawdentity openclaw setup <agentName>"
22696
23634
  };
22697
23635
  }
22698
23636
  return {
@@ -22870,7 +23808,7 @@ async function setupOpenclawRelay(agentName, options) {
22870
23808
  relayRuntimeConfigPath
22871
23809
  });
22872
23810
  await ensureLocalAgentCredentials(homeDir, normalizedAgentName);
22873
- await mkdir5(dirname4(transformTargetPath), { recursive: true });
23811
+ await mkdir6(dirname5(transformTargetPath), { recursive: true });
22874
23812
  try {
22875
23813
  await copyFile(transformSource, transformTargetPath);
22876
23814
  } catch (error48) {
@@ -22963,7 +23901,7 @@ async function setupOpenclawRelay(agentName, options) {
22963
23901
  };
22964
23902
  }
22965
23903
  async function assertSetupChecklistHealthy(input) {
22966
- const checklist = await runOpenclawDoctor({
23904
+ let checklist = await runOpenclawDoctor({
22967
23905
  homeDir: input.homeDir,
22968
23906
  openclawDir: input.openclawDir,
22969
23907
  includeConfigCheck: false,
@@ -22972,14 +23910,56 @@ async function assertSetupChecklistHealthy(input) {
22972
23910
  if (checklist.status === "healthy") {
22973
23911
  return;
22974
23912
  }
22975
- const firstFailure = checklist.checks.find((check2) => check2.status === "fail");
23913
+ let gatewayApprovalSummary;
23914
+ const gatewayPairingFailure = checklist.checks.find(
23915
+ (check2) => check2.id === "state.gatewayDevicePairing" && check2.status === "fail"
23916
+ );
23917
+ if (gatewayPairingFailure !== void 0) {
23918
+ gatewayApprovalSummary = await autoApproveOpenclawGatewayDevices({
23919
+ homeDir: input.homeDir,
23920
+ openclawDir: input.openclawDir,
23921
+ runner: input.gatewayDeviceApprovalRunner
23922
+ });
23923
+ if (gatewayApprovalSummary !== void 0) {
23924
+ const successfulAttempts = gatewayApprovalSummary.attempts.filter(
23925
+ (attempt) => attempt.ok
23926
+ ).length;
23927
+ const failedAttempts = gatewayApprovalSummary.attempts.filter(
23928
+ (attempt) => !attempt.ok
23929
+ );
23930
+ logger8.info("cli.openclaw_setup_gateway_device_recovery_attempted", {
23931
+ openclawDir: input.openclawDir,
23932
+ pendingCount: gatewayApprovalSummary.pendingRequestIds.length,
23933
+ successfulAttempts,
23934
+ failedAttempts: failedAttempts.length,
23935
+ commandUnavailable: failedAttempts.some(
23936
+ (attempt) => attempt.unavailable
23937
+ )
23938
+ });
23939
+ checklist = await runOpenclawDoctor({
23940
+ homeDir: input.homeDir,
23941
+ openclawDir: input.openclawDir,
23942
+ includeConfigCheck: false,
23943
+ includeConnectorRuntimeCheck: input.includeConnectorRuntimeCheck
23944
+ });
23945
+ if (checklist.status === "healthy") {
23946
+ return;
23947
+ }
23948
+ }
23949
+ }
23950
+ const firstFailure = checklist.checks.find(
23951
+ (check2) => check2.status === "fail"
23952
+ );
23953
+ const unavailableGatewayApprovalAttempt = gatewayApprovalSummary?.attempts.find((attempt) => attempt.unavailable);
23954
+ const remediationHint = unavailableGatewayApprovalAttempt !== void 0 && firstFailure?.id === "state.gatewayDevicePairing" ? `${OPENCLAW_DEVICE_APPROVAL_RECOVERY_HINT}. Ensure the \`${resolveOpenclawGatewayApprovalCommand()}\` command is available.` : firstFailure?.remediationHint;
22976
23955
  throw createCliError6(
22977
23956
  "CLI_OPENCLAW_SETUP_CHECKLIST_FAILED",
22978
23957
  "OpenClaw setup checklist failed",
22979
23958
  {
22980
23959
  firstFailedCheckId: firstFailure?.id,
22981
23960
  firstFailedCheckMessage: firstFailure?.message,
22982
- remediationHint: firstFailure?.remediationHint,
23961
+ remediationHint,
23962
+ gatewayDeviceApproval: gatewayApprovalSummary,
22983
23963
  checks: checklist.checks
22984
23964
  }
22985
23965
  );
@@ -22999,7 +23979,8 @@ async function setupOpenclawSelfReady(agentName, options) {
22999
23979
  await assertSetupChecklistHealthy({
23000
23980
  homeDir: resolvedHomeDir,
23001
23981
  openclawDir: resolvedOpenclawDir,
23002
- includeConnectorRuntimeCheck: false
23982
+ includeConnectorRuntimeCheck: false,
23983
+ gatewayDeviceApprovalRunner: options.gatewayDeviceApprovalRunner
23003
23984
  });
23004
23985
  return {
23005
23986
  ...setup,
@@ -23031,7 +24012,8 @@ async function setupOpenclawSelfReady(agentName, options) {
23031
24012
  await assertSetupChecklistHealthy({
23032
24013
  homeDir: resolvedHomeDir,
23033
24014
  openclawDir: resolvedOpenclawDir,
23034
- includeConnectorRuntimeCheck: true
24015
+ includeConnectorRuntimeCheck: true,
24016
+ gatewayDeviceApprovalRunner: options.gatewayDeviceApprovalRunner
23035
24017
  });
23036
24018
  return {
23037
24019
  ...setup,
@@ -23161,13 +24143,13 @@ var createOpenclawCommand = () => {
23161
24143
  import { randomBytes as randomBytes4 } from "crypto";
23162
24144
  import {
23163
24145
  chmod as chmod4,
23164
- mkdir as mkdir6,
24146
+ mkdir as mkdir7,
23165
24147
  readdir,
23166
- readFile as readFile6,
24148
+ readFile as readFile7,
23167
24149
  unlink as unlink2,
23168
- writeFile as writeFile6
24150
+ writeFile as writeFile7
23169
24151
  } from "fs/promises";
23170
- import { dirname as dirname5, join as join7, resolve } from "path";
24152
+ import { dirname as dirname6, join as join8, resolve } from "path";
23171
24153
  import { Command as Command8 } from "commander";
23172
24154
  import jsQR from "jsqr";
23173
24155
  import { PNG } from "pngjs";
@@ -23191,7 +24173,7 @@ var PEER_ALIAS_PATTERN2 = /^[a-zA-Z0-9._-]+$/;
23191
24173
  var DEFAULT_STATUS_WAIT_SECONDS = 300;
23192
24174
  var DEFAULT_STATUS_POLL_INTERVAL_SECONDS = 3;
23193
24175
  var MAX_PROFILE_NAME_LENGTH = 64;
23194
- var isRecord9 = (value) => {
24176
+ var isRecord10 = (value) => {
23195
24177
  return typeof value === "object" && value !== null;
23196
24178
  };
23197
24179
  function createCliError7(code, message2) {
@@ -23239,7 +24221,7 @@ function parseProfileName(value, label) {
23239
24221
  return candidate;
23240
24222
  }
23241
24223
  function parsePeerProfile(payload) {
23242
- if (!isRecord9(payload)) {
24224
+ if (!isRecord10(payload)) {
23243
24225
  throw createCliError7(
23244
24226
  "CLI_PAIR_PROFILE_INVALID",
23245
24227
  "Pair profile must be an object"
@@ -23286,7 +24268,7 @@ function parsePairingTicketIssuerOrigin(ticket) {
23286
24268
  "Pairing ticket is invalid"
23287
24269
  );
23288
24270
  }
23289
- if (!isRecord9(payload) || typeof payload.iss !== "string") {
24271
+ if (!isRecord10(payload) || typeof payload.iss !== "string") {
23290
24272
  throw createCliError7(
23291
24273
  "CLI_PAIR_CONFIRM_TICKET_INVALID",
23292
24274
  "Pairing ticket is invalid"
@@ -23335,7 +24317,7 @@ function parseAitAgentDid(ait) {
23335
24317
  "Agent AIT is invalid. Recreate the agent before pairing."
23336
24318
  );
23337
24319
  }
23338
- if (!isRecord9(payload) || typeof payload.sub !== "string") {
24320
+ if (!isRecord10(payload) || typeof payload.sub !== "string") {
23339
24321
  throw createCliError7(
23340
24322
  "CLI_PAIR_AGENT_NOT_FOUND",
23341
24323
  "Agent AIT is invalid. Recreate the agent before pairing."
@@ -23397,10 +24379,10 @@ function resolvePeerAlias(input) {
23397
24379
  return `${baseAlias}-${index}`;
23398
24380
  }
23399
24381
  function resolvePeersConfigPath(getConfigDirImpl) {
23400
- return join7(getConfigDirImpl(), PEERS_FILE_NAME2);
24382
+ return join8(getConfigDirImpl(), PEERS_FILE_NAME2);
23401
24383
  }
23402
24384
  function parsePeerEntry(value) {
23403
- if (!isRecord9(value)) {
24385
+ if (!isRecord10(value)) {
23404
24386
  throw createCliError7(
23405
24387
  "CLI_PAIR_PEERS_CONFIG_INVALID",
23406
24388
  "Peer entry must be an object"
@@ -23449,7 +24431,7 @@ async function loadPeersConfig2(input) {
23449
24431
  "Peer config is not valid JSON"
23450
24432
  );
23451
24433
  }
23452
- if (!isRecord9(parsed)) {
24434
+ if (!isRecord10(parsed)) {
23453
24435
  throw createCliError7(
23454
24436
  "CLI_PAIR_PEERS_CONFIG_INVALID",
23455
24437
  "Peer config must be a JSON object"
@@ -23458,7 +24440,7 @@ async function loadPeersConfig2(input) {
23458
24440
  if (parsed.peers === void 0) {
23459
24441
  return { peers: {} };
23460
24442
  }
23461
- if (!isRecord9(parsed.peers)) {
24443
+ if (!isRecord10(parsed.peers)) {
23462
24444
  throw createCliError7(
23463
24445
  "CLI_PAIR_PEERS_CONFIG_INVALID",
23464
24446
  "Peer config peers field must be an object"
@@ -23472,7 +24454,7 @@ async function loadPeersConfig2(input) {
23472
24454
  }
23473
24455
  async function savePeersConfig2(input) {
23474
24456
  const peersPath = resolvePeersConfigPath(input.getConfigDirImpl);
23475
- await input.mkdirImpl(dirname5(peersPath), { recursive: true });
24457
+ await input.mkdirImpl(dirname6(peersPath), { recursive: true });
23476
24458
  await input.writeFileImpl(
23477
24459
  peersPath,
23478
24460
  `${JSON.stringify(input.config, null, 2)}
@@ -23482,7 +24464,7 @@ async function savePeersConfig2(input) {
23482
24464
  await input.chmodImpl(peersPath, FILE_MODE4);
23483
24465
  }
23484
24466
  function resolveRelayRuntimeConfigPath2(getConfigDirImpl) {
23485
- return join7(getConfigDirImpl(), OPENCLAW_RELAY_RUNTIME_FILE_NAME3);
24467
+ return join8(getConfigDirImpl(), OPENCLAW_RELAY_RUNTIME_FILE_NAME3);
23486
24468
  }
23487
24469
  async function loadRelayTransformPeersPath(input) {
23488
24470
  const relayRuntimeConfigPath = resolveRelayRuntimeConfigPath2(
@@ -23511,7 +24493,7 @@ async function loadRelayTransformPeersPath(input) {
23511
24493
  });
23512
24494
  return void 0;
23513
24495
  }
23514
- if (!isRecord9(parsed)) {
24496
+ if (!isRecord10(parsed)) {
23515
24497
  return void 0;
23516
24498
  }
23517
24499
  const relayTransformPeersPath = parseNonEmptyString9(
@@ -23544,7 +24526,7 @@ async function syncOpenclawRelayPeersSnapshot(input) {
23544
24526
  return;
23545
24527
  }
23546
24528
  try {
23547
- await input.mkdirImpl(dirname5(relayTransformPeersPath), {
24529
+ await input.mkdirImpl(dirname6(relayTransformPeersPath), {
23548
24530
  recursive: true
23549
24531
  });
23550
24532
  await input.writeFileImpl(
@@ -23644,7 +24626,7 @@ function toPathWithQuery3(url2) {
23644
24626
  return `${parsed.pathname}${parsed.search}`;
23645
24627
  }
23646
24628
  function extractErrorCode(payload) {
23647
- if (!isRecord9(payload)) {
24629
+ if (!isRecord10(payload)) {
23648
24630
  return void 0;
23649
24631
  }
23650
24632
  const envelope = payload;
@@ -23655,7 +24637,7 @@ function extractErrorCode(payload) {
23655
24637
  return code.length > 0 ? code : void 0;
23656
24638
  }
23657
24639
  function extractErrorMessage(payload) {
23658
- if (!isRecord9(payload)) {
24640
+ if (!isRecord10(payload)) {
23659
24641
  return void 0;
23660
24642
  }
23661
24643
  const envelope = payload;
@@ -23743,7 +24725,7 @@ function mapStatusPairError(status, payload) {
23743
24725
  return `Pair status failed (${status})`;
23744
24726
  }
23745
24727
  function parsePairStartResponse(payload) {
23746
- if (!isRecord9(payload)) {
24728
+ if (!isRecord10(payload)) {
23747
24729
  throw createCliError7(
23748
24730
  "CLI_PAIR_START_INVALID_RESPONSE",
23749
24731
  "Pair start response is invalid"
@@ -23775,7 +24757,7 @@ function parsePairStartResponse(payload) {
23775
24757
  };
23776
24758
  }
23777
24759
  function parsePairConfirmResponse(payload) {
23778
- if (!isRecord9(payload)) {
24760
+ if (!isRecord10(payload)) {
23779
24761
  throw createCliError7(
23780
24762
  "CLI_PAIR_CONFIRM_INVALID_RESPONSE",
23781
24763
  "Pair confirm response is invalid"
@@ -23810,7 +24792,7 @@ function parsePairConfirmResponse(payload) {
23810
24792
  };
23811
24793
  }
23812
24794
  function parsePairStatusResponse(payload) {
23813
- if (!isRecord9(payload)) {
24795
+ if (!isRecord10(payload)) {
23814
24796
  throw createCliError7(
23815
24797
  "CLI_PAIR_STATUS_INVALID_RESPONSE",
23816
24798
  "Pair status response is invalid"
@@ -23876,16 +24858,16 @@ function parsePairStatusResponse(payload) {
23876
24858
  };
23877
24859
  }
23878
24860
  async function readAgentProofMaterial(agentName, dependencies) {
23879
- const readFileImpl = dependencies.readFileImpl ?? readFile6;
24861
+ const readFileImpl = dependencies.readFileImpl ?? readFile7;
23880
24862
  const getConfigDirImpl = dependencies.getConfigDirImpl ?? getConfigDir;
23881
24863
  const normalizedAgentName = assertValidAgentName(agentName);
23882
- const agentDir = join7(
24864
+ const agentDir = join8(
23883
24865
  getConfigDirImpl(),
23884
24866
  AGENTS_DIR_NAME5,
23885
24867
  normalizedAgentName
23886
24868
  );
23887
- const aitPath = join7(agentDir, AIT_FILE_NAME4);
23888
- const secretKeyPath = join7(agentDir, SECRET_KEY_FILE_NAME3);
24869
+ const aitPath = join8(agentDir, AIT_FILE_NAME4);
24870
+ const secretKeyPath = join8(agentDir, SECRET_KEY_FILE_NAME3);
23889
24871
  let ait;
23890
24872
  try {
23891
24873
  ait = (await readFileImpl(aitPath, "utf-8")).trim();
@@ -23983,14 +24965,14 @@ function decodeTicketFromPng(imageBytes) {
23983
24965
  return parsePairingTicket(decoded.data);
23984
24966
  }
23985
24967
  async function persistPairingQr(input) {
23986
- const mkdirImpl = input.dependencies.mkdirImpl ?? mkdir6;
24968
+ const mkdirImpl = input.dependencies.mkdirImpl ?? mkdir7;
23987
24969
  const readdirImpl = input.dependencies.readdirImpl ?? readdir;
23988
24970
  const unlinkImpl = input.dependencies.unlinkImpl ?? unlink2;
23989
- const writeFileImpl = input.dependencies.writeFileImpl ?? writeFile6;
24971
+ const writeFileImpl = input.dependencies.writeFileImpl ?? writeFile7;
23990
24972
  const getConfigDirImpl = input.dependencies.getConfigDirImpl ?? getConfigDir;
23991
24973
  const qrEncodeImpl = input.dependencies.qrEncodeImpl ?? encodeTicketQrPng;
23992
- const baseDir = join7(getConfigDirImpl(), PAIRING_QR_DIR_NAME);
23993
- const outputPath = parseNonEmptyString9(input.qrOutput) ? resolve(input.qrOutput ?? "") : join7(
24974
+ const baseDir = join8(getConfigDirImpl(), PAIRING_QR_DIR_NAME);
24975
+ const outputPath = parseNonEmptyString9(input.qrOutput) ? resolve(input.qrOutput ?? "") : join8(
23994
24976
  baseDir,
23995
24977
  `${assertValidAgentName(input.agentName)}-pair-${input.nowSeconds}.png`
23996
24978
  );
@@ -24016,7 +24998,7 @@ async function persistPairingQr(input) {
24016
24998
  if (issuedAtSeconds + PAIRING_QR_MAX_AGE_SECONDS > input.nowSeconds) {
24017
24999
  continue;
24018
25000
  }
24019
- const stalePath = join7(baseDir, fileName);
25001
+ const stalePath = join8(baseDir, fileName);
24020
25002
  await unlinkImpl(stalePath).catch((error48) => {
24021
25003
  const nodeError = error48;
24022
25004
  if (nodeError.code === "ENOENT") {
@@ -24025,7 +25007,7 @@ async function persistPairingQr(input) {
24025
25007
  throw error48;
24026
25008
  });
24027
25009
  }
24028
- await mkdirImpl(dirname5(outputPath), { recursive: true });
25010
+ await mkdirImpl(dirname6(outputPath), { recursive: true });
24029
25011
  const imageBytes = await qrEncodeImpl(input.ticket);
24030
25012
  await writeFileImpl(outputPath, imageBytes);
24031
25013
  return outputPath;
@@ -24059,9 +25041,9 @@ function resolveConfirmTicketSource(options) {
24059
25041
  }
24060
25042
  async function persistPairedPeer(input) {
24061
25043
  const getConfigDirImpl = input.dependencies.getConfigDirImpl ?? getConfigDir;
24062
- const readFileImpl = input.dependencies.readFileImpl ?? readFile6;
24063
- const mkdirImpl = input.dependencies.mkdirImpl ?? mkdir6;
24064
- const writeFileImpl = input.dependencies.writeFileImpl ?? writeFile6;
25044
+ const readFileImpl = input.dependencies.readFileImpl ?? readFile7;
25045
+ const mkdirImpl = input.dependencies.mkdirImpl ?? mkdir7;
25046
+ const writeFileImpl = input.dependencies.writeFileImpl ?? writeFile7;
24065
25047
  const chmodImpl = input.dependencies.chmodImpl ?? chmod4;
24066
25048
  const issuerOrigin = parsePairingTicketIssuerOrigin(input.ticket);
24067
25049
  const peerProxyUrl = new URL("/hooks/agent", `${issuerOrigin}/`).toString();
@@ -24173,7 +25155,7 @@ async function confirmPairing(agentName, options, dependencies = {}) {
24173
25155
  const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
24174
25156
  const nowSecondsImpl = dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
24175
25157
  const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
24176
- const readFileImpl = dependencies.readFileImpl ?? readFile6;
25158
+ const readFileImpl = dependencies.readFileImpl ?? readFile7;
24177
25159
  const qrDecodeImpl = dependencies.qrDecodeImpl ?? decodeTicketFromPng;
24178
25160
  const config2 = await resolveConfigImpl();
24179
25161
  const normalizedAgentName = assertValidAgentName(agentName);
@@ -24602,16 +25584,16 @@ import { Command as Command9 } from "commander";
24602
25584
 
24603
25585
  // src/install-skill-mode.ts
24604
25586
  import { constants, existsSync as existsSync2 } from "fs";
24605
- import { access as access3, copyFile as copyFile2, mkdir as mkdir7, readdir as readdir2, readFile as readFile7 } from "fs/promises";
25587
+ import { access as access3, copyFile as copyFile2, mkdir as mkdir8, readdir as readdir2, readFile as readFile8 } from "fs/promises";
24606
25588
  import { createRequire } from "module";
24607
25589
  import { homedir as homedir4 } from "os";
24608
- import { dirname as dirname6, join as join8, relative } from "path";
25590
+ import { dirname as dirname7, join as join9, relative } from "path";
24609
25591
  import { fileURLToPath as fileURLToPath3 } from "url";
24610
25592
  var OPENCLAW_DIR_NAME2 = ".openclaw";
24611
25593
  var SKILL_PACKAGE_NAME = "@clawdentity/openclaw-skill";
24612
25594
  var SKILL_DIR_NAME2 = "clawdentity-openclaw-relay";
24613
25595
  var RELAY_MODULE_FILE_NAME2 = "relay-to-peer.mjs";
24614
- function isRecord10(value) {
25596
+ function isRecord11(value) {
24615
25597
  return typeof value === "object" && value !== null;
24616
25598
  }
24617
25599
  var SkillInstallError = class extends Error {
@@ -24625,7 +25607,7 @@ var SkillInstallError = class extends Error {
24625
25607
  }
24626
25608
  };
24627
25609
  function getErrorCode3(error48) {
24628
- if (!isRecord10(error48)) {
25610
+ if (!isRecord11(error48)) {
24629
25611
  return void 0;
24630
25612
  }
24631
25613
  return typeof error48.code === "string" ? error48.code : void 0;
@@ -24640,7 +25622,7 @@ function resolveOpenclawDir2(homeDir, inputOpenclawDir) {
24640
25622
  if (typeof inputOpenclawDir === "string" && inputOpenclawDir.trim().length > 0) {
24641
25623
  return inputOpenclawDir.trim();
24642
25624
  }
24643
- return join8(homeDir, OPENCLAW_DIR_NAME2);
25625
+ return join9(homeDir, OPENCLAW_DIR_NAME2);
24644
25626
  }
24645
25627
  function resolveSkillPackageRoot(input) {
24646
25628
  if (typeof input.skillPackageRoot === "string" && input.skillPackageRoot.trim().length > 0) {
@@ -24650,8 +25632,8 @@ function resolveSkillPackageRoot(input) {
24650
25632
  if (typeof overriddenRoot === "string" && overriddenRoot.trim().length > 0) {
24651
25633
  return overriddenRoot.trim();
24652
25634
  }
24653
- const bundledSkillRoot = join8(
24654
- dirname6(fileURLToPath3(import.meta.url)),
25635
+ const bundledSkillRoot = join9(
25636
+ dirname7(fileURLToPath3(import.meta.url)),
24655
25637
  "..",
24656
25638
  "skill-bundle",
24657
25639
  "openclaw-skill"
@@ -24663,10 +25645,10 @@ function resolveSkillPackageRoot(input) {
24663
25645
  let packageJsonPath;
24664
25646
  try {
24665
25647
  packageJsonPath = require3.resolve(`${SKILL_PACKAGE_NAME}/package.json`);
24666
- return dirname6(packageJsonPath);
25648
+ return dirname7(packageJsonPath);
24667
25649
  } catch {
24668
- const workspaceFallbackRoot = join8(
24669
- dirname6(fileURLToPath3(import.meta.url)),
25650
+ const workspaceFallbackRoot = join9(
25651
+ dirname7(fileURLToPath3(import.meta.url)),
24670
25652
  "..",
24671
25653
  "..",
24672
25654
  "openclaw-skill"
@@ -24708,7 +25690,7 @@ async function listFilesRecursively(directoryPath) {
24708
25690
  for (const entry of entries.sort(
24709
25691
  (left, right) => left.name.localeCompare(right.name)
24710
25692
  )) {
24711
- const entryPath = join8(directoryPath, entry.name);
25693
+ const entryPath = join9(directoryPath, entry.name);
24712
25694
  if (entry.isDirectory()) {
24713
25695
  files.push(...await listFilesRecursively(entryPath));
24714
25696
  continue;
@@ -24720,10 +25702,10 @@ async function listFilesRecursively(directoryPath) {
24720
25702
  return files;
24721
25703
  }
24722
25704
  async function resolveArtifacts(input) {
24723
- const skillRoot = join8(input.skillPackageRoot, "skill");
24724
- const skillDocSource = join8(skillRoot, "SKILL.md");
24725
- const referencesRoot = join8(skillRoot, "references");
24726
- const relaySource = join8(
25705
+ const skillRoot = join9(input.skillPackageRoot, "skill");
25706
+ const skillDocSource = join9(skillRoot, "SKILL.md");
25707
+ const referencesRoot = join9(skillRoot, "references");
25708
+ const relaySource = join9(
24727
25709
  input.skillPackageRoot,
24728
25710
  "dist",
24729
25711
  RELAY_MODULE_FILE_NAME2
@@ -24759,19 +25741,19 @@ async function resolveArtifacts(input) {
24759
25741
  }
24760
25742
  });
24761
25743
  }
24762
- const targetSkillRoot = join8(input.openclawDir, "skills", SKILL_DIR_NAME2);
25744
+ const targetSkillRoot = join9(input.openclawDir, "skills", SKILL_DIR_NAME2);
24763
25745
  const artifacts = [
24764
25746
  {
24765
25747
  sourcePath: skillDocSource,
24766
- targetPath: join8(targetSkillRoot, "SKILL.md")
25748
+ targetPath: join9(targetSkillRoot, "SKILL.md")
24767
25749
  },
24768
25750
  {
24769
25751
  sourcePath: relaySource,
24770
- targetPath: join8(targetSkillRoot, RELAY_MODULE_FILE_NAME2)
25752
+ targetPath: join9(targetSkillRoot, RELAY_MODULE_FILE_NAME2)
24771
25753
  },
24772
25754
  {
24773
25755
  sourcePath: relaySource,
24774
- targetPath: join8(
25756
+ targetPath: join9(
24775
25757
  input.openclawDir,
24776
25758
  "hooks",
24777
25759
  "transforms",
@@ -24783,7 +25765,7 @@ async function resolveArtifacts(input) {
24783
25765
  const relativePath = relative(referencesRoot, referenceFile);
24784
25766
  artifacts.push({
24785
25767
  sourcePath: referenceFile,
24786
- targetPath: join8(targetSkillRoot, "references", relativePath)
25768
+ targetPath: join9(targetSkillRoot, "references", relativePath)
24787
25769
  });
24788
25770
  }
24789
25771
  return artifacts.sort(
@@ -24791,10 +25773,10 @@ async function resolveArtifacts(input) {
24791
25773
  );
24792
25774
  }
24793
25775
  async function copyArtifact(input) {
24794
- const sourceContent = await readFile7(input.sourcePath);
25776
+ const sourceContent = await readFile8(input.sourcePath);
24795
25777
  let existingContent;
24796
25778
  try {
24797
- existingContent = await readFile7(input.targetPath);
25779
+ existingContent = await readFile8(input.targetPath);
24798
25780
  } catch (error48) {
24799
25781
  if (getErrorCode3(error48) !== "ENOENT") {
24800
25782
  throw error48;
@@ -24803,7 +25785,7 @@ async function copyArtifact(input) {
24803
25785
  if (existingContent !== void 0 && sourceContent.equals(existingContent)) {
24804
25786
  return "unchanged";
24805
25787
  }
24806
- await mkdir7(dirname6(input.targetPath), { recursive: true });
25788
+ await mkdir8(dirname7(input.targetPath), { recursive: true });
24807
25789
  await copyFile2(input.sourcePath, input.targetPath);
24808
25790
  if (existingContent !== void 0) {
24809
25791
  return "updated";
@@ -24835,7 +25817,7 @@ async function installOpenclawSkillArtifacts(options = {}) {
24835
25817
  homeDir,
24836
25818
  openclawDir,
24837
25819
  skillPackageRoot,
24838
- targetSkillDirectory: join8(openclawDir, "skills", SKILL_DIR_NAME2),
25820
+ targetSkillDirectory: join9(openclawDir, "skills", SKILL_DIR_NAME2),
24839
25821
  records
24840
25822
  };
24841
25823
  }
@@ -24926,7 +25908,7 @@ var createSkillCommand = () => {
24926
25908
  };
24927
25909
 
24928
25910
  // src/commands/verify.ts
24929
- import { readFile as readFile8 } from "fs/promises";
25911
+ import { readFile as readFile9 } from "fs/promises";
24930
25912
  import { Command as Command10 } from "commander";
24931
25913
  var logger10 = createLogger({ service: "cli", module: "verify" });
24932
25914
  var REGISTRY_KEYS_CACHE_FILE = "registry-keys.json";
@@ -24939,7 +25921,7 @@ var VerifyCommandError = class extends Error {
24939
25921
  this.name = "VerifyCommandError";
24940
25922
  }
24941
25923
  };
24942
- var isRecord11 = (value) => {
25924
+ var isRecord12 = (value) => {
24943
25925
  return typeof value === "object" && value !== null;
24944
25926
  };
24945
25927
  var normalizeRegistryUrl2 = (registryUrl) => {
@@ -24975,7 +25957,7 @@ var resolveToken = async (tokenOrFile) => {
24975
25957
  throw new VerifyCommandError("invalid token (value is empty)");
24976
25958
  }
24977
25959
  try {
24978
- const fileContents = await readFile8(input, "utf-8");
25960
+ const fileContents = await readFile9(input, "utf-8");
24979
25961
  const token = fileContents.trim();
24980
25962
  if (token.length === 0) {
24981
25963
  throw new VerifyCommandError(`invalid token (${input} is empty)`);
@@ -25007,7 +25989,7 @@ var parseResponseJson = async (response) => {
25007
25989
  }
25008
25990
  };
25009
25991
  var parseSigningKeys = (payload) => {
25010
- if (!isRecord11(payload) || !Array.isArray(payload.keys)) {
25992
+ if (!isRecord12(payload) || !Array.isArray(payload.keys)) {
25011
25993
  throw new VerifyCommandError(
25012
25994
  "verification keys unavailable (response payload is invalid)"
25013
25995
  );
@@ -25026,7 +26008,7 @@ var parseSigningKeys = (payload) => {
25026
26008
  };
25027
26009
  var parseRegistryKeysCache = (rawCache) => {
25028
26010
  const parsed = parseJson(rawCache);
25029
- if (!isRecord11(parsed)) {
26011
+ if (!isRecord12(parsed)) {
25030
26012
  return void 0;
25031
26013
  }
25032
26014
  const { registryUrl, fetchedAtMs, keys } = parsed;
@@ -25052,7 +26034,7 @@ var parseRegistryKeysCache = (rawCache) => {
25052
26034
  };
25053
26035
  var parseCrlCache = (rawCache) => {
25054
26036
  const parsed = parseJson(rawCache);
25055
- if (!isRecord11(parsed)) {
26037
+ if (!isRecord12(parsed)) {
25056
26038
  return void 0;
25057
26039
  }
25058
26040
  const { registryUrl, fetchedAtMs, claims } = parsed;
@@ -25150,7 +26132,7 @@ var fetchCrlClaims = async (input) => {
25150
26132
  );
25151
26133
  }
25152
26134
  const payload = await parseResponseJson(response);
25153
- if (!isRecord11(payload) || typeof payload.crl !== "string") {
26135
+ if (!isRecord12(payload) || typeof payload.crl !== "string") {
25154
26136
  throw new VerifyCommandError(
25155
26137
  "revocation check unavailable (response payload is invalid)"
25156
26138
  );
@@ -25195,7 +26177,7 @@ var loadCrlClaims = async (input) => {
25195
26177
  return claims;
25196
26178
  };
25197
26179
  var toInvalidTokenReason = (error48) => {
25198
- if (isRecord11(error48) && typeof error48.message === "string") {
26180
+ if (isRecord12(error48) && typeof error48.message === "string") {
25199
26181
  return `invalid token (${error48.message})`;
25200
26182
  }
25201
26183
  if (error48 instanceof Error && error48.message.length > 0) {