@perkos/perkos-a2a 0.12.35 → 0.12.37

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/README.md CHANGED
@@ -241,14 +241,17 @@ generation. Before accepting a newer generation, the supervisor waits for
241
241
  confirmed transport shutdown; a timeout fails closed with
242
242
  `OWNER_STOP_TIMEOUT`. A private TTL rollover lock serializes concurrent upgrades,
243
243
  and health certifies the supervisor package version and real install root. Lease
244
- and health evidence are owner-only files and never
245
- contain relay or chat credentials. This ownership boundary applies to OpenClaw;
244
+ and health evidence are owner-only files and never contain relay or chat
245
+ credentials. From 0.12.37, supervisor health is HMAC-bound to the active repair
246
+ attempt and is authoritative after a real restart; runtime load status is
247
+ written only by the claimed owner service. Incidental validation/doctor plugin
248
+ loads are diagnostic and cannot invalidate signed owner health. This ownership boundary applies to OpenClaw;
246
249
  Hermes/custom runtimes keep their existing runtime-owned model and transport
247
250
  routing.
248
251
 
249
252
  ```bash
250
253
  A2A_RELAY_API_KEY='<credential-from-invitation>' \
251
- npx --yes @perkos/perkos-a2a@0.12.35 connect \
254
+ npx --yes @perkos/perkos-a2a@0.12.37 connect \
252
255
  --artifact ./perkos-a2a.tgz \
253
256
  --sha256 '<sha256-from-the-official-invitation>' \
254
257
  --agent-name '<agent-name>' \
package/dist/index.d.ts CHANGED
@@ -53,5 +53,12 @@ export declare function runOpenClawChatTurn(api: any, config: A2APluginConfig, s
53
53
  export declare function formatRecentChatContext(messages: ChatMessage[], currentMessageId: string): string;
54
54
  /** The identity the relay treats as exclusive; names are display metadata. */
55
55
  export declare function a2aLifecycleKey(config: A2APluginConfig): string;
56
+ /**
57
+ * OpenClaw may briefly call register while a plugin entry is being rewritten.
58
+ * Treat partial/empty config as an inert, valid shape instead of crashing the
59
+ * whole service on string operations. The repair CLI restores the complete
60
+ * identity atomically before the service is enabled.
61
+ */
62
+ export declare function normalizeOpenClawPluginConfig(value: unknown): A2APluginConfig;
56
63
  export default function register(api: any): void;
57
64
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAGV,WAAW,EAEZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,cAAc,YAAY,CAAC;AA0C3B,kEAAkE;AAClE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAItF;AAED,KAAK,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AASpD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,oBAAoB,EAAE,MAAM,GAC3B,OAAO,CA2BT;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,oBAAoB,EAC3B,eAAe,CAAC,EAAE,MAAM,GACvB,oBAAoB,CA+BtB;AAiED,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC,CAAC;IACH,IAAI,CAAC,EAAE;QACL,yBAAyB,CAAC,EAAE,MAAM,CAAC;QACnC,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;CACH,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,CAYrF;AAQD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;CAAE,GAClF,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuFxB;AAYD,oFAAoF;AACpF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,WAAW,EAAE,EACvB,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAWR;AA4CD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAe/D;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,GAAG,QAuzBxC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAGV,WAAW,EAEZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,cAAc,YAAY,CAAC;AA0C3B,kEAAkE;AAClE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAItF;AAED,KAAK,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AASpD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,oBAAoB,EAAE,MAAM,GAC3B,OAAO,CA2BT;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,oBAAoB,EAC3B,eAAe,CAAC,EAAE,MAAM,GACvB,oBAAoB,CA+BtB;AAiED,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC,CAAC;IACH,IAAI,CAAC,EAAE;QACL,yBAAyB,CAAC,EAAE,MAAM,CAAC;QACnC,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;CACH,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,CAYrF;AAQD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;CAAE,GAClF,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuFxB;AAYD,oFAAoF;AACpF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,WAAW,EAAE,EACvB,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAWR;AA4CD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAe/D;AAMD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAc7E;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,GAAG,QAszBxC"}
package/dist/index.js CHANGED
@@ -24817,7 +24817,7 @@ var require_websocket = __commonJS({
24817
24817
  var http = __require("http");
24818
24818
  var net2 = __require("net");
24819
24819
  var tls = __require("tls");
24820
- var { randomBytes, createHash: createHash3 } = __require("crypto");
24820
+ var { randomBytes, createHash: createHash4 } = __require("crypto");
24821
24821
  var { Duplex, Readable } = __require("stream");
24822
24822
  var { URL: URL2 } = __require("url");
24823
24823
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -25477,7 +25477,7 @@ var require_websocket = __commonJS({
25477
25477
  abortHandshake(websocket, socket, "Invalid Upgrade header");
25478
25478
  return;
25479
25479
  }
25480
- const digest = createHash3("sha1").update(key + GUID).digest("base64");
25480
+ const digest = createHash4("sha1").update(key + GUID).digest("base64");
25481
25481
  if (res.headers["sec-websocket-accept"] !== digest) {
25482
25482
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
25483
25483
  return;
@@ -25844,7 +25844,7 @@ var require_websocket_server = __commonJS({
25844
25844
  var EventEmitter = __require("events");
25845
25845
  var http = __require("http");
25846
25846
  var { Duplex } = __require("stream");
25847
- var { createHash: createHash3 } = __require("crypto");
25847
+ var { createHash: createHash4 } = __require("crypto");
25848
25848
  var extension2 = require_extension();
25849
25849
  var PerMessageDeflate2 = require_permessage_deflate();
25850
25850
  var subprotocol2 = require_subprotocol();
@@ -26145,7 +26145,7 @@ var require_websocket_server = __commonJS({
26145
26145
  );
26146
26146
  }
26147
26147
  if (this._state > RUNNING) return abortHandshake(socket, 503);
26148
- const digest = createHash3("sha1").update(key + GUID).digest("base64");
26148
+ const digest = createHash4("sha1").update(key + GUID).digest("base64");
26149
26149
  const headers = [
26150
26150
  "HTTP/1.1 101 Switching Protocols",
26151
26151
  "Upgrade: websocket",
@@ -28469,7 +28469,7 @@ var OpenClawSupervisorClient = class {
28469
28469
  claim = await this.rolloverSupervisor("legacy-health-missing-target");
28470
28470
  }
28471
28471
  const health = claim?.health;
28472
- if (claim?.state !== "READY" || health?.schemaVersion !== 2 || health.authority !== "process-external-supervisor" || health.state !== "running" || health.ownerKey !== this.ownerHash || health.instanceId !== this.lease.instanceId || health.generation !== this.lease.generation || !this.healthMatchesTarget(health) || health.relayClientCount !== (this.config.relay?.enabled && this.config.relay.url ? 1 : 0) || !health.claimedAt) {
28472
+ if (claim?.state !== "READY" || health?.schemaVersion !== 2 && health?.schemaVersion !== 3 || health.authority !== "process-external-supervisor" || health.state !== "running" || health.ownerKey !== this.ownerHash || health.instanceId !== this.lease.instanceId || health.generation !== this.lease.generation || !this.healthMatchesTarget(health) || health.relayClientCount !== (this.config.relay?.enabled && this.config.relay.url ? 1 : 0) || !health.claimedAt) {
28473
28473
  throw new Error("OWNERSHIP_PRIMITIVE_UNAVAILABLE:supervisor claim did not return certified READY/CLAIMED health");
28474
28474
  }
28475
28475
  this.health = health;
@@ -28530,17 +28530,42 @@ var OpenClawSupervisorClient = class {
28530
28530
  const oldHealth = this.health;
28531
28531
  this.closeSocket(new Error("SUPERVISOR_ROLLOVER"));
28532
28532
  await this.waitForSupervisorStop(oldHealth, 1e4);
28533
- await this.connectOrSpawn();
28534
- const target = await this.claimCurrentSupervisor();
28535
- if (!this.healthMatchesTarget(target.health)) {
28536
- throw new Error(`SUPERVISOR_ROLLOVER_TARGET_MISMATCH:${reason}`);
28537
- }
28533
+ const target = await this.claimTargetAfterRollover(reason);
28538
28534
  this.logger.info(`[perkos-a2a] Supervisor rollover complete targetVersion=${this.config.version}`);
28539
28535
  return target;
28540
28536
  } finally {
28541
28537
  releaseLock();
28542
28538
  }
28543
28539
  }
28540
+ /**
28541
+ * OpenClaw can register two adjacent hot-reload generations while its config
28542
+ * file is settling. The first generation may successfully bootstrap the
28543
+ * target supervisor just as its client socket is replaced. Reconnect to that
28544
+ * single fenced supervisor instead of abandoning the agent offline.
28545
+ */
28546
+ async claimTargetAfterRollover(reason) {
28547
+ let lastError;
28548
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
28549
+ try {
28550
+ await this.connectOrSpawn();
28551
+ const target = await this.claimCurrentSupervisor();
28552
+ if (!this.healthMatchesTarget(target.health)) {
28553
+ throw new Error(`SUPERVISOR_ROLLOVER_TARGET_MISMATCH:${reason}`);
28554
+ }
28555
+ return target;
28556
+ } catch (error) {
28557
+ lastError = error;
28558
+ this.closeSocket(new Error("SUPERVISOR_ROLLOVER_TARGET_RETRY"));
28559
+ this.logger.error(
28560
+ `[perkos-a2a] Supervisor target claim retry ${attempt}/3: ${error instanceof Error ? error.message : String(error)}`
28561
+ );
28562
+ if (attempt < 3) await new Promise((resolveWait) => setTimeout(resolveWait, 250));
28563
+ }
28564
+ }
28565
+ throw new Error(
28566
+ `SUPERVISOR_ROLLOVER_TARGET_START_FAILED:${lastError instanceof Error ? lastError.message : String(lastError)}`
28567
+ );
28568
+ }
28544
28569
  async acquireRolloverLock(timeoutMs = 15e3) {
28545
28570
  const path = supervisorRolloverLockPath(this.stateDir, this.ownerHash);
28546
28571
  mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
@@ -28830,7 +28855,7 @@ var OpenClawSupervisorClient = class {
28830
28855
  };
28831
28856
 
28832
28857
  // src/runtime-evidence.ts
28833
- import { createHmac } from "node:crypto";
28858
+ import { createHash as createHash3, createHmac, timingSafeEqual } from "node:crypto";
28834
28859
  import {
28835
28860
  chmodSync as chmodSync2,
28836
28861
  mkdirSync as mkdirSync3,
@@ -28840,7 +28865,8 @@ import {
28840
28865
  } from "node:fs";
28841
28866
  import { homedir as homedir4 } from "node:os";
28842
28867
  import { join as join3, resolve as resolve2 } from "node:path";
28843
- var RUNTIME_EVIDENCE_SCHEMA_VERSION = 2;
28868
+ var RUNTIME_ATTEMPT_SCHEMA_VERSION = 2;
28869
+ var RUNTIME_STATUS_SCHEMA_VERSION = 3;
28844
28870
  function canonicalize(value) {
28845
28871
  if (Array.isArray(value)) return value.map(canonicalize);
28846
28872
  if (!value || typeof value !== "object") return value;
@@ -28848,8 +28874,52 @@ function canonicalize(value) {
28848
28874
  Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, canonicalize(child)])
28849
28875
  );
28850
28876
  }
28877
+ function objectValue(value) {
28878
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
28879
+ }
28880
+ function normalizedServiceUrl(value) {
28881
+ if (typeof value !== "string" || !value.trim()) return void 0;
28882
+ try {
28883
+ const url = new URL(value);
28884
+ url.username = "";
28885
+ url.password = "";
28886
+ url.search = "";
28887
+ url.hash = "";
28888
+ return url.toString().replace(/\/+$/u, "");
28889
+ } catch {
28890
+ return value.trim().replace(/\/+$/u, "");
28891
+ }
28892
+ }
28893
+ function configEvidenceProjection(config) {
28894
+ const root = objectValue(config);
28895
+ const relay = objectValue(root.relay);
28896
+ const chat = objectValue(root.chat);
28897
+ const platform = objectValue(root.platform);
28898
+ const heartbeatUrl = typeof platform.heartbeatUrl === "string" ? platform.heartbeatUrl : "";
28899
+ const heartbeatAgentId = heartbeatUrl.match(/\/agents\/([^/]+)\/heartbeat(?:\?|$)/u)?.[1];
28900
+ const agentId = typeof root.agentId === "string" ? root.agentId : typeof platform.agentId === "string" ? platform.agentId : heartbeatAgentId;
28901
+ return {
28902
+ agentName: typeof root.agentName === "string" ? root.agentName.trim() : "",
28903
+ agentId: agentId ?? "unknown",
28904
+ relay: {
28905
+ enabled: relay.enabled === true,
28906
+ url: normalizedServiceUrl(relay.url) ?? ""
28907
+ },
28908
+ chat: {
28909
+ enabled: chat.enabled === true,
28910
+ url: normalizedServiceUrl(chat.url) ?? (chat.enabled === true ? "wss://chat.perkos.xyz/chat" : "")
28911
+ },
28912
+ platform: {
28913
+ heartbeatAgentId: heartbeatAgentId ?? agentId ?? "unknown",
28914
+ heartbeatIntervalMs: typeof platform.heartbeatIntervalMs === "number" ? platform.heartbeatIntervalMs : null
28915
+ }
28916
+ };
28917
+ }
28851
28918
  function canonicalConfigJson(config) {
28852
- return JSON.stringify({ schemaVersion: RUNTIME_EVIDENCE_SCHEMA_VERSION, config: canonicalize(config) });
28919
+ return JSON.stringify({ schemaVersion: RUNTIME_STATUS_SCHEMA_VERSION, config: canonicalize(configEvidenceProjection(config)) });
28920
+ }
28921
+ function configDigest(config) {
28922
+ return createHash3("sha256").update(canonicalConfigJson(config)).digest("hex");
28853
28923
  }
28854
28924
  function configFingerprint(config, hmacKeyHex) {
28855
28925
  if (!/^[a-f0-9]{64}$/u.test(hmacKeyHex)) throw new Error("runtime evidence HMAC key is invalid");
@@ -28879,17 +28949,22 @@ function recordRuntimeLoadEvidence(input) {
28879
28949
  return null;
28880
28950
  }
28881
28951
  const agentId = configAgentId(input.config);
28882
- if (attempt.schemaVersion !== RUNTIME_EVIDENCE_SCHEMA_VERSION || attempt.targetVersion !== input.version || attempt.expectedAgentId !== agentId) {
28952
+ if (attempt.schemaVersion !== RUNTIME_ATTEMPT_SCHEMA_VERSION || attempt.targetVersion !== input.version || attempt.expectedAgentId !== agentId) {
28883
28953
  input.logger.warn?.("[perkos-a2a] runtime evidence attempt does not match the loaded plugin identity");
28884
28954
  return null;
28885
28955
  }
28956
+ const digest = configDigest(input.config);
28886
28957
  const status = {
28887
- schemaVersion: RUNTIME_EVIDENCE_SCHEMA_VERSION,
28958
+ schemaVersion: RUNTIME_STATUS_SCHEMA_VERSION,
28888
28959
  loadedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
28889
28960
  version: input.version,
28890
28961
  agentId,
28891
28962
  fingerprint: configFingerprint(input.config, attempt.hmacKeyHex),
28963
+ configDigest: digest,
28964
+ configRevision: `${input.generation}:${digest}`,
28892
28965
  attemptId: attempt.attemptId,
28966
+ writerKind: "owner-plugin",
28967
+ generation: input.generation,
28893
28968
  pid: process.pid,
28894
28969
  processStartTime: processStartIdentity(),
28895
28970
  bootId: currentBootId(),
@@ -29607,25 +29682,32 @@ function a2aLifecycleKey(config) {
29607
29682
  }
29608
29683
  const heartbeatUrl = config.platform?.heartbeatUrl || "";
29609
29684
  const idFromHeartbeat = heartbeatUrl.match(/\/agents\/([^/]+)\/heartbeat(?:\?|$)/u)?.[1];
29610
- const agentId = config.platform?.agentId || idFromHeartbeat || config.agentName.trim().toLowerCase();
29685
+ const agentId = config.platform?.agentId || idFromHeartbeat || safeAgentName(config.agentName).toLowerCase();
29611
29686
  return `${relayUrl}|${agentId}`;
29612
29687
  }
29613
- function register(api) {
29614
- const pluginConfig = api.config?.plugins?.entries?.["perkos-a2a"]?.config || {
29615
- agentName: "agent",
29616
- port: 5050,
29617
- skills: [],
29618
- peers: {}
29688
+ function safeAgentName(value) {
29689
+ return typeof value === "string" && value.trim() ? value.trim() : "agent";
29690
+ }
29691
+ function normalizeOpenClawPluginConfig(value) {
29692
+ const raw = value && typeof value === "object" && !Array.isArray(value) ? value : {};
29693
+ const platform = raw.platform && typeof raw.platform === "object" ? raw.platform : void 0;
29694
+ const heartbeatAgentId = platform?.heartbeatUrl?.match(/\/agents\/([^/]+)\/heartbeat/u)?.[1];
29695
+ const agentName = safeAgentName(raw.agentName || platform?.agentId || heartbeatAgentId);
29696
+ return {
29697
+ ...raw,
29698
+ agentName,
29699
+ port: typeof raw.port === "number" ? raw.port : 5050,
29700
+ skills: Array.isArray(raw.skills) ? raw.skills : [],
29701
+ peers: raw.peers && typeof raw.peers === "object" ? raw.peers : {}
29619
29702
  };
29703
+ }
29704
+ function register(api) {
29705
+ const pluginConfig = normalizeOpenClawPluginConfig(
29706
+ api.config?.plugins?.entries?.["perkos-a2a"]?.config
29707
+ );
29620
29708
  const logger = api.logger || console;
29621
29709
  const pluginVersion = loadedPluginVersion();
29622
29710
  const pluginRoot = resolve3(dirname3(fileURLToPath2(import.meta.url)), "..");
29623
- recordRuntimeLoadEvidence({
29624
- config: pluginConfig,
29625
- version: pluginVersion,
29626
- pluginRoot,
29627
- logger
29628
- });
29629
29711
  const server = new A2AServer(pluginConfig, logger, { relayOwnership: "external-required" });
29630
29712
  const lifecycleKey = a2aLifecycleKey(pluginConfig);
29631
29713
  const lifecycleInstanceId = randomUUID7();
@@ -29880,7 +29962,7 @@ function register(api) {
29880
29962
  if (pluginConfig.chat?.enabled && !chatApiKey) {
29881
29963
  logger.error("[perkos-chat] chat.enabled=true but no apiKey (set chat.apiKey or fall back from relay.apiKey)");
29882
29964
  }
29883
- const platformAgentId = pluginConfig.platform?.agentId || pluginConfig.platform?.heartbeatUrl?.match(/\/agents\/([^/]+)\/heartbeat/u)?.[1] || pluginConfig.agentName.trim().toLowerCase();
29965
+ const platformAgentId = pluginConfig.platform?.agentId || pluginConfig.platform?.heartbeatUrl?.match(/\/agents\/([^/]+)\/heartbeat/u)?.[1] || safeAgentName(pluginConfig.agentName).toLowerCase();
29884
29966
  supervisorClient = new OpenClawSupervisorClient({
29885
29967
  ownerHash: supervisorOwnerHash(lifecycleKey),
29886
29968
  generation: Date.now(),
@@ -29922,6 +30004,14 @@ function register(api) {
29922
30004
  start: async () => {
29923
30005
  try {
29924
30006
  await supervisorClient.start();
30007
+ const ownerHealth = supervisorClient.currentHealth();
30008
+ recordRuntimeLoadEvidence({
30009
+ config: pluginConfig,
30010
+ version: pluginVersion,
30011
+ pluginRoot,
30012
+ generation: ownerHealth?.generation ?? 0,
30013
+ logger
30014
+ });
29925
30015
  await server.start();
29926
30016
  logger.info(`[perkos-a2a] A2A server started for ${pluginConfig.agentName}`);
29927
30017
  } catch (error) {
@@ -30325,6 +30415,7 @@ export {
30325
30415
  extractOpenClawChatReply,
30326
30416
  formatRecentChatContext,
30327
30417
  isWebhookEvent,
30418
+ normalizeOpenClawPluginConfig,
30328
30419
  omitUnavailableAgentHarness,
30329
30420
  openclawChatSessionKey,
30330
30421
  runOpenClawChatTurn