@perkos/perkos-a2a 0.12.28 → 0.12.30

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
@@ -170,7 +170,7 @@ openclaw gateway restart
170
170
 
171
171
  #### Official idempotent connection
172
172
 
173
- Version 0.12.28+ ships the machine-readable `perkos-a2a connect` CLI. The
173
+ Version 0.12.30+ ships the machine-readable `perkos-a2a connect` CLI. The
174
174
  official PerkOS invitation supplies the artifact URL, SHA-256, agent name, and
175
175
  agent id; it invokes the exact npm version with `npx`. The same command performs
176
176
  a first install or repairs the matching existing identity, even when an invalid
@@ -180,14 +180,30 @@ The command selects OpenClaw's recorded `npm-pack:` installation path (including
180
180
  the `npm/node_modules` layout used by OpenClaw 2026.5.x), quarantines a competing
181
181
  `extensions/perkos-a2a` source, normalizes config against the packaged manifest
182
182
  schema, and protects identity, relay/chat credentials, endpoints, runtime kind,
183
- and session key. It then validates config and plugin health, performs a safe
184
- gateway restart, proves the PID/start identity changed, and accepts only fresh
185
- post-restart relay/chat/heartbeat logs. Success or failure is emitted as one JSON
186
- object; secret values are never printed.
183
+ and session key. Bind-mounted `openclaw.json` files are updated in place when an
184
+ atomic rename is unavailable, while a persistent backup and the quarantined
185
+ legacy extension remain recoverable. A process lock prevents two simultaneous
186
+ repairs from registering the same agent.
187
+
188
+ Every successful install is anchored by a private, content-addressed receipt;
189
+ the resumable repair journal rolls forward after an install has committed
190
+ instead of restoring an incompatible pre-upgrade source. On restart, the
191
+ plugin writes HMAC-bound runtime evidence proving that the expected version and
192
+ normalized config were actually loaded. The receipt, journal, attempt, and
193
+ runtime status files are created with owner-only permissions.
194
+
195
+ It then validates config and plugin health and performs a verifiable restart. A
196
+ normal service must return a new PID/start identity; a containerized PID 1
197
+ gateway restarts in-process through OpenClaw's authorized `SIGUSR1` path and must
198
+ emit fresh restart logs. Relay, registration, heartbeat, and chat authentication
199
+ must remain healthy—with no duplicate plugin or registration messages—for three
200
+ continuous minutes and at least three fresh successful heartbeats. Success or
201
+ failure is emitted as one JSON object; secret
202
+ values are never printed.
187
203
 
188
204
  ```bash
189
205
  A2A_RELAY_API_KEY='<credential-from-invitation>' \
190
- npx --yes @perkos/perkos-a2a@0.12.28 connect \
206
+ npx --yes @perkos/perkos-a2a@0.12.30 connect \
191
207
  --artifact ./perkos-a2a.tgz \
192
208
  --sha256 '<sha256-from-the-official-invitation>' \
193
209
  --agent-name '<agent-name>' \
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,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;AAiC3B,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,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,GAAG,QA80BxC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH,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,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,GAAG,QAq1BxC"}
package/dist/index.js CHANGED
@@ -4944,10 +4944,10 @@ var require_raw_body = __commonJS({
4944
4944
  if (done) {
4945
4945
  return readStream(stream, encoding, length, limit, wrap(done));
4946
4946
  }
4947
- return new Promise(function executor(resolve, reject) {
4947
+ return new Promise(function executor(resolve3, reject) {
4948
4948
  readStream(stream, encoding, length, limit, function onRead(err, buf) {
4949
4949
  if (err) return reject(err);
4950
- resolve(buf);
4950
+ resolve3(buf);
4951
4951
  });
4952
4952
  });
4953
4953
  }
@@ -18429,11 +18429,11 @@ var require_view = __commonJS({
18429
18429
  var debug = require_src()("express:view");
18430
18430
  var path = __require("path");
18431
18431
  var fs = __require("fs");
18432
- var dirname = path.dirname;
18432
+ var dirname2 = path.dirname;
18433
18433
  var basename = path.basename;
18434
18434
  var extname = path.extname;
18435
- var join2 = path.join;
18436
- var resolve = path.resolve;
18435
+ var join3 = path.join;
18436
+ var resolve3 = path.resolve;
18437
18437
  module.exports = View;
18438
18438
  function View(name, options) {
18439
18439
  var opts = options || {};
@@ -18467,8 +18467,8 @@ var require_view = __commonJS({
18467
18467
  debug('lookup "%s"', name);
18468
18468
  for (var i = 0; i < roots.length && !path2; i++) {
18469
18469
  var root = roots[i];
18470
- var loc = resolve(root, name);
18471
- var dir = dirname(loc);
18470
+ var loc = resolve3(root, name);
18471
+ var dir = dirname2(loc);
18472
18472
  var file = basename(loc);
18473
18473
  path2 = this.resolve(dir, file);
18474
18474
  }
@@ -18478,14 +18478,14 @@ var require_view = __commonJS({
18478
18478
  debug('render "%s"', this.path);
18479
18479
  this.engine(this.path, options, callback);
18480
18480
  };
18481
- View.prototype.resolve = function resolve2(dir, file) {
18481
+ View.prototype.resolve = function resolve4(dir, file) {
18482
18482
  var ext = this.ext;
18483
- var path2 = join2(dir, file);
18483
+ var path2 = join3(dir, file);
18484
18484
  var stat = tryStat(path2);
18485
18485
  if (stat && stat.isFile()) {
18486
18486
  return path2;
18487
18487
  }
18488
- path2 = join2(dir, basename(file, ext), "index" + ext);
18488
+ path2 = join3(dir, basename(file, ext), "index" + ext);
18489
18489
  stat = tryStat(path2);
18490
18490
  if (stat && stat.isFile()) {
18491
18491
  return path2;
@@ -19118,9 +19118,9 @@ var require_send = __commonJS({
19118
19118
  var Stream = __require("stream");
19119
19119
  var util = __require("util");
19120
19120
  var extname = path.extname;
19121
- var join2 = path.join;
19121
+ var join3 = path.join;
19122
19122
  var normalize = path.normalize;
19123
- var resolve = path.resolve;
19123
+ var resolve3 = path.resolve;
19124
19124
  var sep = path.sep;
19125
19125
  var BYTES_RANGE_REGEXP = /^ *bytes=/;
19126
19126
  var MAX_MAXAGE = 60 * 60 * 24 * 365 * 1e3;
@@ -19157,7 +19157,7 @@ var require_send = __commonJS({
19157
19157
  this._maxage = opts.maxAge || opts.maxage;
19158
19158
  this._maxage = typeof this._maxage === "string" ? ms(this._maxage) : Number(this._maxage);
19159
19159
  this._maxage = !isNaN(this._maxage) ? Math.min(Math.max(0, this._maxage), MAX_MAXAGE) : 0;
19160
- this._root = opts.root ? resolve(opts.root) : null;
19160
+ this._root = opts.root ? resolve3(opts.root) : null;
19161
19161
  if (!this._root && opts.from) {
19162
19162
  this.from(opts.from);
19163
19163
  }
@@ -19181,7 +19181,7 @@ var require_send = __commonJS({
19181
19181
  return this;
19182
19182
  }, "send.index: pass index as option");
19183
19183
  SendStream.prototype.root = function root(path2) {
19184
- this._root = resolve(String(path2));
19184
+ this._root = resolve3(String(path2));
19185
19185
  debug("root %s", this._root);
19186
19186
  return this;
19187
19187
  };
@@ -19337,7 +19337,7 @@ var require_send = __commonJS({
19337
19337
  return res;
19338
19338
  }
19339
19339
  parts = path2.split(sep);
19340
- path2 = normalize(join2(root, path2));
19340
+ path2 = normalize(join3(root, path2));
19341
19341
  } else {
19342
19342
  if (UP_PATH_REGEXP.test(path2)) {
19343
19343
  debug('malicious path "%s"', path2);
@@ -19345,7 +19345,7 @@ var require_send = __commonJS({
19345
19345
  return res;
19346
19346
  }
19347
19347
  parts = normalize(path2).split(sep);
19348
- path2 = resolve(path2);
19348
+ path2 = resolve3(path2);
19349
19349
  }
19350
19350
  if (containsDotFile(parts)) {
19351
19351
  var access = this._dotfiles;
@@ -19472,7 +19472,7 @@ var require_send = __commonJS({
19472
19472
  if (err) return self.onStatError(err);
19473
19473
  return self.error(404);
19474
19474
  }
19475
- var p = join2(path2, self._index[i]);
19475
+ var p = join3(path2, self._index[i]);
19476
19476
  debug('stat "%s"', p);
19477
19477
  fs.stat(p, function(err2, stat) {
19478
19478
  if (err2) return next(err2);
@@ -20625,7 +20625,7 @@ var require_application = __commonJS({
20625
20625
  var deprecate = require_depd()("express");
20626
20626
  var flatten = require_array_flatten();
20627
20627
  var merge = require_utils_merge();
20628
- var resolve = __require("path").resolve;
20628
+ var resolve3 = __require("path").resolve;
20629
20629
  var setPrototypeOf = require_setprototypeof();
20630
20630
  var hasOwnProperty = Object.prototype.hasOwnProperty;
20631
20631
  var slice = Array.prototype.slice;
@@ -20664,7 +20664,7 @@ var require_application = __commonJS({
20664
20664
  this.mountpath = "/";
20665
20665
  this.locals.settings = this.settings;
20666
20666
  this.set("view", View);
20667
- this.set("views", resolve("views"));
20667
+ this.set("views", resolve3("views"));
20668
20668
  this.set("jsonp callback name", "callback");
20669
20669
  if (env === "production") {
20670
20670
  this.enable("view cache");
@@ -21909,7 +21909,7 @@ var require_response = __commonJS({
21909
21909
  var send = require_send();
21910
21910
  var extname = path.extname;
21911
21911
  var mime = send.mime;
21912
- var resolve = path.resolve;
21912
+ var resolve3 = path.resolve;
21913
21913
  var vary = require_vary();
21914
21914
  var res = Object.create(http.ServerResponse.prototype);
21915
21915
  module.exports = res;
@@ -22168,7 +22168,7 @@ var require_response = __commonJS({
22168
22168
  }
22169
22169
  opts = Object.create(opts);
22170
22170
  opts.headers = headers;
22171
- var fullPath = !opts.root ? resolve(path2) : path2;
22171
+ var fullPath = !opts.root ? resolve3(path2) : path2;
22172
22172
  return this.sendFile(fullPath, opts, done);
22173
22173
  };
22174
22174
  res.contentType = res.type = function contentType(type) {
@@ -22434,7 +22434,7 @@ var require_serve_static = __commonJS({
22434
22434
  var encodeUrl = require_encodeurl();
22435
22435
  var escapeHtml = require_escape_html();
22436
22436
  var parseUrl = require_parseurl();
22437
- var resolve = __require("path").resolve;
22437
+ var resolve3 = __require("path").resolve;
22438
22438
  var send = require_send();
22439
22439
  var url = __require("url");
22440
22440
  module.exports = serveStatic;
@@ -22454,7 +22454,7 @@ var require_serve_static = __commonJS({
22454
22454
  throw new TypeError("option setHeaders must be function");
22455
22455
  }
22456
22456
  opts.maxage = opts.maxage || opts.maxAge || 0;
22457
- opts.root = resolve(root);
22457
+ opts.root = resolve3(root);
22458
22458
  var onDirectory = redirect ? createRedirectDirectoryListener() : createNotFoundDirectoryListener();
22459
22459
  return function serveStatic2(req, res, next) {
22460
22460
  if (req.method !== "GET" && req.method !== "HEAD") {
@@ -26450,7 +26450,7 @@ var RelayClient = class {
26450
26450
  }
26451
26451
  }
26452
26452
  async sendAndWait(msg, expectedType, timeoutMs) {
26453
- return new Promise((resolve, reject) => {
26453
+ return new Promise((resolve3, reject) => {
26454
26454
  const timer = setTimeout(() => {
26455
26455
  this.pendingCallbacks.delete(msg.id);
26456
26456
  reject(new Error(`Relay request timed out after ${timeoutMs}ms`));
@@ -26462,7 +26462,7 @@ var RelayClient = class {
26462
26462
  if (response.type === "error") {
26463
26463
  reject(new Error(`Relay error: ${response.payload.message}`));
26464
26464
  } else {
26465
- resolve(response);
26465
+ resolve3(response);
26466
26466
  }
26467
26467
  }
26468
26468
  });
@@ -27237,7 +27237,7 @@ var A2AServer = class {
27237
27237
  async waitForRemoteTaskTerminal(targetUrl, headers, taskId, timeoutMs = 45e3, pollMs = 1e3) {
27238
27238
  const deadline = Date.now() + timeoutMs;
27239
27239
  while (Date.now() < deadline) {
27240
- await new Promise((resolve) => setTimeout(resolve, pollMs));
27240
+ await new Promise((resolve3) => setTimeout(resolve3, pollMs));
27241
27241
  const response = await fetch(`${targetUrl}/a2a/jsonrpc`, {
27242
27242
  method: "POST",
27243
27243
  headers,
@@ -27261,7 +27261,7 @@ var A2AServer = class {
27261
27261
  if (task && isTerminalTaskState(task.status?.state)) {
27262
27262
  return this.success(randomUUID4(), structuredClone(task));
27263
27263
  }
27264
- await new Promise((resolve) => setTimeout(resolve, pollMs));
27264
+ await new Promise((resolve3) => setTimeout(resolve3, pollMs));
27265
27265
  }
27266
27266
  return null;
27267
27267
  }
@@ -27473,6 +27473,9 @@ var A2AServer = class {
27473
27473
 
27474
27474
  // src/index.ts
27475
27475
  import { randomUUID as randomUUID6 } from "crypto";
27476
+ import { readFileSync as readFileSync3 } from "node:fs";
27477
+ import { dirname, resolve as resolve2 } from "node:path";
27478
+ import { fileURLToPath } from "node:url";
27476
27479
 
27477
27480
  // src/agentic-actions.ts
27478
27481
  var FALLBACK_AGENT_ALIASES = [
@@ -28017,7 +28020,7 @@ var ChatStore = class {
28017
28020
  let firstMessageAt = null;
28018
28021
  let lastMessageAt = null;
28019
28022
  let leftover = "";
28020
- return new Promise((resolve, reject) => {
28023
+ return new Promise((resolve3, reject) => {
28021
28024
  stream.on("data", (chunk) => {
28022
28025
  const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
28023
28026
  hash.update(text);
@@ -28057,7 +28060,7 @@ var ChatStore = class {
28057
28060
  hash.update(metaRaw);
28058
28061
  } catch {
28059
28062
  }
28060
- resolve({
28063
+ resolve3({
28061
28064
  transcriptHash: hash.digest("hex"),
28062
28065
  hashAlgo: "sha256",
28063
28066
  messageCount,
@@ -28526,12 +28529,97 @@ function startPlatformHeartbeat(opts) {
28526
28529
  };
28527
28530
  }
28528
28531
 
28532
+ // src/runtime-evidence.ts
28533
+ import { createHmac } from "node:crypto";
28534
+ import {
28535
+ chmodSync,
28536
+ mkdirSync,
28537
+ readFileSync as readFileSync2,
28538
+ renameSync,
28539
+ writeFileSync
28540
+ } from "node:fs";
28541
+ import { homedir as homedir3 } from "node:os";
28542
+ import { join as join2, resolve } from "node:path";
28543
+ var RUNTIME_EVIDENCE_SCHEMA_VERSION = 1;
28544
+ function canonicalize(value) {
28545
+ if (Array.isArray(value)) return value.map(canonicalize);
28546
+ if (!value || typeof value !== "object") return value;
28547
+ return Object.fromEntries(
28548
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, canonicalize(child)])
28549
+ );
28550
+ }
28551
+ function canonicalConfigJson(config) {
28552
+ return JSON.stringify({ schemaVersion: RUNTIME_EVIDENCE_SCHEMA_VERSION, config: canonicalize(config) });
28553
+ }
28554
+ function configFingerprint(config, hmacKeyHex) {
28555
+ if (!/^[a-f0-9]{64}$/u.test(hmacKeyHex)) throw new Error("runtime evidence HMAC key is invalid");
28556
+ return createHmac("sha256", Buffer.from(hmacKeyHex, "hex")).update(canonicalConfigJson(config)).digest("hex");
28557
+ }
28558
+ function runtimeEvidenceDir(stateDir) {
28559
+ return resolve(stateDir ?? process.env.OPENCLAW_STATE_DIR ?? join2(homedir3(), ".openclaw"), "perkos-a2a");
28560
+ }
28561
+ function runtimeAttemptPath(stateDir) {
28562
+ return join2(runtimeEvidenceDir(stateDir), "active-attempt.json");
28563
+ }
28564
+ function runtimeStatusPath(stateDir) {
28565
+ return join2(runtimeEvidenceDir(stateDir), "runtime-status.json");
28566
+ }
28567
+ function configAgentId(config) {
28568
+ if (config.platform?.agentId) return config.platform.agentId;
28569
+ const match = config.platform?.heartbeatUrl?.match(/\/agents\/([^/]+)\/heartbeat/u);
28570
+ return match?.[1] ?? "unknown";
28571
+ }
28572
+ function recordRuntimeLoadEvidence(input) {
28573
+ const evidenceDir = runtimeEvidenceDir(input.stateDir);
28574
+ let attempt;
28575
+ try {
28576
+ attempt = JSON.parse(readFileSync2(runtimeAttemptPath(input.stateDir), "utf8"));
28577
+ } catch {
28578
+ input.logger.info(`[perkos-a2a] config loaded version=${input.version} agentId=${configAgentId(input.config)} attemptId=none`);
28579
+ return null;
28580
+ }
28581
+ const agentId = configAgentId(input.config);
28582
+ if (attempt.schemaVersion !== RUNTIME_EVIDENCE_SCHEMA_VERSION || attempt.targetVersion !== input.version || attempt.expectedAgentId !== agentId) {
28583
+ input.logger.warn?.("[perkos-a2a] runtime evidence attempt does not match the loaded plugin identity");
28584
+ return null;
28585
+ }
28586
+ const status = {
28587
+ schemaVersion: RUNTIME_EVIDENCE_SCHEMA_VERSION,
28588
+ loadedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
28589
+ version: input.version,
28590
+ agentId,
28591
+ fingerprint: configFingerprint(input.config, attempt.hmacKeyHex),
28592
+ attemptId: attempt.attemptId,
28593
+ pid: process.pid,
28594
+ pluginRoot: resolve(input.pluginRoot)
28595
+ };
28596
+ mkdirSync(evidenceDir, { recursive: true, mode: 448 });
28597
+ const target = runtimeStatusPath(input.stateDir);
28598
+ const temporary = `${target}.${process.pid}.tmp`;
28599
+ writeFileSync(temporary, `${JSON.stringify(status, null, 2)}
28600
+ `, { mode: 384 });
28601
+ renameSync(temporary, target);
28602
+ chmodSync(target, 384);
28603
+ input.logger.info(
28604
+ `[perkos-a2a] config loaded version=${status.version} agentId=${status.agentId} attemptId=${status.attemptId} fingerprint=${status.fingerprint}`
28605
+ );
28606
+ return status;
28607
+ }
28608
+
28529
28609
  // src/types.ts
28530
28610
  function isWebhookEvent(msg) {
28531
28611
  return msg.type === "webhook_event";
28532
28612
  }
28533
28613
 
28534
28614
  // src/index.ts
28615
+ function loadedPluginVersion() {
28616
+ try {
28617
+ const manifest = JSON.parse(readFileSync3(new URL("../openclaw.plugin.json", import.meta.url), "utf8"));
28618
+ return typeof manifest.version === "string" ? manifest.version : "unknown";
28619
+ } catch {
28620
+ return "unknown";
28621
+ }
28622
+ }
28535
28623
  function wakeGatewayAgent(requestHeartbeatNow, reason, logger, sessionKey = "agent:main") {
28536
28624
  if (typeof requestHeartbeatNow === "function") {
28537
28625
  try {
@@ -28791,6 +28879,13 @@ function register(api) {
28791
28879
  peers: {}
28792
28880
  };
28793
28881
  const logger = api.logger || console;
28882
+ const pluginVersion = loadedPluginVersion();
28883
+ recordRuntimeLoadEvidence({
28884
+ config: pluginConfig,
28885
+ version: pluginVersion,
28886
+ pluginRoot: resolve2(dirname(fileURLToPath(import.meta.url)), ".."),
28887
+ logger
28888
+ });
28794
28889
  const server = new A2AServer(pluginConfig, logger);
28795
28890
  const pendingTasks = [];
28796
28891
  const enqueueSystemEvent = api.runtime?.system?.enqueueSystemEvent;
@@ -29114,7 +29209,7 @@ function register(api) {
29114
29209
  url: pluginConfig.platform?.heartbeatUrl,
29115
29210
  relayKey: pluginConfig.relay?.apiKey,
29116
29211
  runtimeKind: "openclaw",
29117
- version: "0.12.26",
29212
+ version: pluginVersion,
29118
29213
  intervalMs: pluginConfig.platform?.heartbeatIntervalMs
29119
29214
  });
29120
29215
  },
@@ -29443,18 +29538,18 @@ function register(api) {
29443
29538
  let portAvailable = true;
29444
29539
  try {
29445
29540
  const netMod = await import("net");
29446
- await new Promise((resolve, reject) => {
29541
+ await new Promise((resolve3, reject) => {
29447
29542
  const srv = netMod.createServer();
29448
29543
  srv.once("error", (err) => {
29449
29544
  if (err.code === "EADDRINUSE") {
29450
29545
  portAvailable = false;
29451
- resolve();
29546
+ resolve3();
29452
29547
  } else {
29453
29548
  reject(err);
29454
29549
  }
29455
29550
  });
29456
29551
  srv.listen(pluginConfig.port, () => {
29457
- srv.close(() => resolve());
29552
+ srv.close(() => resolve3());
29458
29553
  });
29459
29554
  });
29460
29555
  } catch {