@perkos/perkos-a2a 0.12.36 → 0.12.38

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,24 @@ 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
 
252
+ From 0.12.38, runtime verification has its own post-install budget. The
253
+ continuous stability clock begins when signed supervisor health reports one
254
+ healthy relay client plus authenticated chat; fresh heartbeat count continues
255
+ to accumulate during that same window and is enforced at completion. Package
256
+ download, config migration, and gateway reload time therefore cannot consume
257
+ the three-minute transport acceptance window.
258
+
249
259
  ```bash
250
260
  A2A_RELAY_API_KEY='<credential-from-invitation>' \
251
- npx --yes @perkos/perkos-a2a@0.12.36 connect \
261
+ npx --yes @perkos/perkos-a2a@0.12.38 connect \
252
262
  --artifact ./perkos-a2a.tgz \
253
263
  --sha256 '<sha256-from-the-official-invitation>' \
254
264
  --agent-name '<agent-name>' \
@@ -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;AAMD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAc7E;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,GAAG,QAozBxC"}
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;
@@ -28855,7 +28855,7 @@ var OpenClawSupervisorClient = class {
28855
28855
  };
28856
28856
 
28857
28857
  // src/runtime-evidence.ts
28858
- import { createHmac } from "node:crypto";
28858
+ import { createHash as createHash3, createHmac, timingSafeEqual } from "node:crypto";
28859
28859
  import {
28860
28860
  chmodSync as chmodSync2,
28861
28861
  mkdirSync as mkdirSync3,
@@ -28865,7 +28865,8 @@ import {
28865
28865
  } from "node:fs";
28866
28866
  import { homedir as homedir4 } from "node:os";
28867
28867
  import { join as join3, resolve as resolve2 } from "node:path";
28868
- var RUNTIME_EVIDENCE_SCHEMA_VERSION = 2;
28868
+ var RUNTIME_ATTEMPT_SCHEMA_VERSION = 2;
28869
+ var RUNTIME_STATUS_SCHEMA_VERSION = 3;
28869
28870
  function canonicalize(value) {
28870
28871
  if (Array.isArray(value)) return value.map(canonicalize);
28871
28872
  if (!value || typeof value !== "object") return value;
@@ -28873,8 +28874,52 @@ function canonicalize(value) {
28873
28874
  Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, canonicalize(child)])
28874
28875
  );
28875
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
+ }
28876
28918
  function canonicalConfigJson(config) {
28877
- 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");
28878
28923
  }
28879
28924
  function configFingerprint(config, hmacKeyHex) {
28880
28925
  if (!/^[a-f0-9]{64}$/u.test(hmacKeyHex)) throw new Error("runtime evidence HMAC key is invalid");
@@ -28904,17 +28949,22 @@ function recordRuntimeLoadEvidence(input) {
28904
28949
  return null;
28905
28950
  }
28906
28951
  const agentId = configAgentId(input.config);
28907
- 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) {
28908
28953
  input.logger.warn?.("[perkos-a2a] runtime evidence attempt does not match the loaded plugin identity");
28909
28954
  return null;
28910
28955
  }
28956
+ const digest = configDigest(input.config);
28911
28957
  const status = {
28912
- schemaVersion: RUNTIME_EVIDENCE_SCHEMA_VERSION,
28958
+ schemaVersion: RUNTIME_STATUS_SCHEMA_VERSION,
28913
28959
  loadedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
28914
28960
  version: input.version,
28915
28961
  agentId,
28916
28962
  fingerprint: configFingerprint(input.config, attempt.hmacKeyHex),
28963
+ configDigest: digest,
28964
+ configRevision: `${input.generation}:${digest}`,
28917
28965
  attemptId: attempt.attemptId,
28966
+ writerKind: "owner-plugin",
28967
+ generation: input.generation,
28918
28968
  pid: process.pid,
28919
28969
  processStartTime: processStartIdentity(),
28920
28970
  bootId: currentBootId(),
@@ -29658,12 +29708,6 @@ function register(api) {
29658
29708
  const logger = api.logger || console;
29659
29709
  const pluginVersion = loadedPluginVersion();
29660
29710
  const pluginRoot = resolve3(dirname3(fileURLToPath2(import.meta.url)), "..");
29661
- recordRuntimeLoadEvidence({
29662
- config: pluginConfig,
29663
- version: pluginVersion,
29664
- pluginRoot,
29665
- logger
29666
- });
29667
29711
  const server = new A2AServer(pluginConfig, logger, { relayOwnership: "external-required" });
29668
29712
  const lifecycleKey = a2aLifecycleKey(pluginConfig);
29669
29713
  const lifecycleInstanceId = randomUUID7();
@@ -29960,6 +30004,14 @@ function register(api) {
29960
30004
  start: async () => {
29961
30005
  try {
29962
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
+ });
29963
30015
  await server.start();
29964
30016
  logger.info(`[perkos-a2a] A2A server started for ${pluginConfig.agentName}`);
29965
30017
  } catch (error) {