@perkos/perkos-a2a 0.12.29 → 0.12.31
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 +12 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +405 -69
- package/dist/index.js.map +4 -4
- package/dist/relay-client.d.ts +7 -0
- package/dist/relay-client.d.ts.map +1 -1
- package/dist/relay-client.js +89 -18
- package/dist/relay-client.js.map +1 -1
- package/dist/relay.d.ts +1 -1
- package/dist/relay.d.ts.map +1 -1
- package/dist/relay.js +21 -3
- package/dist/relay.js.map +1 -1
- package/dist/repair-cli.js +31 -2
- package/dist/repair-cli.js.map +1 -1
- package/dist/repair.d.ts +37 -4
- package/dist/repair.d.ts.map +1 -1
- package/dist/repair.js +377 -51
- package/dist/repair.js.map +1 -1
- package/dist/runtime-evidence.d.ts +42 -0
- package/dist/runtime-evidence.d.ts.map +1 -0
- package/dist/runtime-evidence.js +81 -0
- package/dist/runtime-evidence.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +160 -13
- package/dist/server.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
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(
|
|
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
|
-
|
|
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
|
|
18432
|
+
var dirname2 = path.dirname;
|
|
18433
18433
|
var basename = path.basename;
|
|
18434
18434
|
var extname = path.extname;
|
|
18435
|
-
var
|
|
18436
|
-
var
|
|
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 =
|
|
18471
|
-
var dir =
|
|
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
|
|
18481
|
+
View.prototype.resolve = function resolve4(dir, file) {
|
|
18482
18482
|
var ext = this.ext;
|
|
18483
|
-
var path2 =
|
|
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 =
|
|
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
|
|
19121
|
+
var join3 = path.join;
|
|
19122
19122
|
var normalize = path.normalize;
|
|
19123
|
-
var
|
|
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 ?
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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",
|
|
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
|
|
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 ?
|
|
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
|
|
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 =
|
|
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") {
|
|
@@ -26263,6 +26263,7 @@ var RelayClient = class {
|
|
|
26263
26263
|
heartbeatTimer = null;
|
|
26264
26264
|
connected = false;
|
|
26265
26265
|
stopped = false;
|
|
26266
|
+
generation = 0;
|
|
26266
26267
|
pendingCallbacks = /* @__PURE__ */ new Map();
|
|
26267
26268
|
/** Responses that arrived with nobody waiting, claimable by a retry. */
|
|
26268
26269
|
orphanResponses = /* @__PURE__ */ new Map();
|
|
@@ -26274,17 +26275,46 @@ var RelayClient = class {
|
|
|
26274
26275
|
return this.connected;
|
|
26275
26276
|
}
|
|
26276
26277
|
start() {
|
|
26278
|
+
if (!this.stopped && this.ws) return;
|
|
26277
26279
|
this.stopped = false;
|
|
26278
|
-
this.
|
|
26280
|
+
const generation = ++this.generation;
|
|
26281
|
+
this.connect(generation);
|
|
26279
26282
|
}
|
|
26280
26283
|
stop() {
|
|
26281
|
-
|
|
26282
|
-
|
|
26283
|
-
|
|
26284
|
-
|
|
26285
|
-
this.ws = null;
|
|
26284
|
+
const socket = this.beginStop();
|
|
26285
|
+
try {
|
|
26286
|
+
socket?.close(1e3, "Client shutting down");
|
|
26287
|
+
} catch {
|
|
26286
26288
|
}
|
|
26287
|
-
|
|
26289
|
+
}
|
|
26290
|
+
/** Stop reconnect activity and wait a bounded time for the socket to close. */
|
|
26291
|
+
async stopAndWait(timeoutMs = 5e3) {
|
|
26292
|
+
const socket = this.beginStop();
|
|
26293
|
+
if (!socket || socket.readyState === wrapper_default.CLOSED) return { closeTimedOut: false };
|
|
26294
|
+
return new Promise((resolve3) => {
|
|
26295
|
+
let settled = false;
|
|
26296
|
+
const finish = (closeTimedOut) => {
|
|
26297
|
+
if (settled) return;
|
|
26298
|
+
settled = true;
|
|
26299
|
+
clearTimeout(timer);
|
|
26300
|
+
resolve3({ closeTimedOut });
|
|
26301
|
+
};
|
|
26302
|
+
const timer = setTimeout(() => {
|
|
26303
|
+
finish(true);
|
|
26304
|
+
try {
|
|
26305
|
+
socket.terminate();
|
|
26306
|
+
} catch {
|
|
26307
|
+
}
|
|
26308
|
+
}, timeoutMs);
|
|
26309
|
+
timer.unref?.();
|
|
26310
|
+
socket.once("close", () => finish(false));
|
|
26311
|
+
socket.once("error", () => finish(false));
|
|
26312
|
+
try {
|
|
26313
|
+
socket.close(1e3, "Client shutting down");
|
|
26314
|
+
} catch {
|
|
26315
|
+
finish(false);
|
|
26316
|
+
}
|
|
26317
|
+
});
|
|
26288
26318
|
}
|
|
26289
26319
|
/**
|
|
26290
26320
|
* Send a task to another agent via the relay.
|
|
@@ -26333,8 +26363,8 @@ var RelayClient = class {
|
|
|
26333
26363
|
};
|
|
26334
26364
|
this.send(response);
|
|
26335
26365
|
}
|
|
26336
|
-
connect() {
|
|
26337
|
-
if (this.
|
|
26366
|
+
connect(generation) {
|
|
26367
|
+
if (!this.isCurrent(generation)) return;
|
|
26338
26368
|
const url = this.options.relay.url;
|
|
26339
26369
|
this.logger.info(`[perkos-a2a] Connecting to relay: ${url}`);
|
|
26340
26370
|
try {
|
|
@@ -26342,17 +26372,23 @@ var RelayClient = class {
|
|
|
26342
26372
|
} catch (err) {
|
|
26343
26373
|
const msg = err instanceof Error ? err.message : String(err);
|
|
26344
26374
|
this.logger.error(`[perkos-a2a] Failed to create WebSocket: ${msg}`);
|
|
26345
|
-
this.scheduleReconnect();
|
|
26375
|
+
this.scheduleReconnect(generation);
|
|
26346
26376
|
return;
|
|
26347
26377
|
}
|
|
26348
|
-
this.ws
|
|
26378
|
+
const socket = this.ws;
|
|
26379
|
+
socket.on("open", () => {
|
|
26380
|
+
if (!this.isCurrent(generation, socket)) {
|
|
26381
|
+
socket.close(1e3, "Stale relay generation");
|
|
26382
|
+
return;
|
|
26383
|
+
}
|
|
26349
26384
|
this.connected = true;
|
|
26350
26385
|
this.reconnectMs = MIN_RECONNECT_MS;
|
|
26351
26386
|
this.logger.info("[perkos-a2a] Connected to relay hub");
|
|
26352
26387
|
this.register();
|
|
26353
26388
|
this.startHeartbeat();
|
|
26354
26389
|
});
|
|
26355
|
-
|
|
26390
|
+
socket.on("message", (data) => {
|
|
26391
|
+
if (!this.isCurrent(generation, socket)) return;
|
|
26356
26392
|
let msg;
|
|
26357
26393
|
try {
|
|
26358
26394
|
msg = JSON.parse(data.toString());
|
|
@@ -26362,15 +26398,17 @@ var RelayClient = class {
|
|
|
26362
26398
|
}
|
|
26363
26399
|
this.handleMessage(msg);
|
|
26364
26400
|
});
|
|
26365
|
-
|
|
26401
|
+
socket.on("close", (code, reason) => {
|
|
26402
|
+
if (!this.isCurrent(generation, socket)) return;
|
|
26366
26403
|
this.connected = false;
|
|
26367
26404
|
this.clearTimers();
|
|
26368
26405
|
if (!this.stopped) {
|
|
26369
26406
|
this.logger.info(`[perkos-a2a] Relay connection closed (${code}: ${reason || "no reason"}). Reconnecting...`);
|
|
26370
|
-
this.scheduleReconnect();
|
|
26407
|
+
this.scheduleReconnect(generation);
|
|
26371
26408
|
}
|
|
26372
26409
|
});
|
|
26373
|
-
|
|
26410
|
+
socket.on("error", (err) => {
|
|
26411
|
+
if (!this.isCurrent(generation, socket)) return;
|
|
26374
26412
|
this.logger.error(`[perkos-a2a] Relay WebSocket error: ${err.message}`);
|
|
26375
26413
|
});
|
|
26376
26414
|
}
|
|
@@ -26450,7 +26488,7 @@ var RelayClient = class {
|
|
|
26450
26488
|
}
|
|
26451
26489
|
}
|
|
26452
26490
|
async sendAndWait(msg, expectedType, timeoutMs) {
|
|
26453
|
-
return new Promise((
|
|
26491
|
+
return new Promise((resolve3, reject) => {
|
|
26454
26492
|
const timer = setTimeout(() => {
|
|
26455
26493
|
this.pendingCallbacks.delete(msg.id);
|
|
26456
26494
|
reject(new Error(`Relay request timed out after ${timeoutMs}ms`));
|
|
@@ -26462,8 +26500,12 @@ var RelayClient = class {
|
|
|
26462
26500
|
if (response.type === "error") {
|
|
26463
26501
|
reject(new Error(`Relay error: ${response.payload.message}`));
|
|
26464
26502
|
} else {
|
|
26465
|
-
|
|
26503
|
+
resolve3(response);
|
|
26466
26504
|
}
|
|
26505
|
+
},
|
|
26506
|
+
cancel: (error) => {
|
|
26507
|
+
clearTimeout(timer);
|
|
26508
|
+
reject(error);
|
|
26467
26509
|
}
|
|
26468
26510
|
});
|
|
26469
26511
|
this.send(msg);
|
|
@@ -26481,10 +26523,10 @@ var RelayClient = class {
|
|
|
26481
26523
|
this.send(msg);
|
|
26482
26524
|
}, HEARTBEAT_INTERVAL_MS);
|
|
26483
26525
|
}
|
|
26484
|
-
scheduleReconnect() {
|
|
26485
|
-
if (this.
|
|
26526
|
+
scheduleReconnect(generation) {
|
|
26527
|
+
if (!this.isCurrent(generation)) return;
|
|
26486
26528
|
this.reconnectTimer = setTimeout(() => {
|
|
26487
|
-
this.connect();
|
|
26529
|
+
if (this.isCurrent(generation)) this.connect(generation);
|
|
26488
26530
|
}, this.reconnectMs);
|
|
26489
26531
|
this.reconnectMs = Math.min(this.reconnectMs * 2, MAX_RECONNECT_MS);
|
|
26490
26532
|
}
|
|
@@ -26498,6 +26540,23 @@ var RelayClient = class {
|
|
|
26498
26540
|
this.reconnectTimer = null;
|
|
26499
26541
|
}
|
|
26500
26542
|
}
|
|
26543
|
+
isCurrent(generation, socket) {
|
|
26544
|
+
return !this.stopped && generation === this.generation && (!socket || socket === this.ws);
|
|
26545
|
+
}
|
|
26546
|
+
beginStop() {
|
|
26547
|
+
if (this.stopped && !this.ws) return null;
|
|
26548
|
+
this.stopped = true;
|
|
26549
|
+
this.generation += 1;
|
|
26550
|
+
this.clearTimers();
|
|
26551
|
+
const error = new Error("CLIENT_STOPPED");
|
|
26552
|
+
for (const pending of this.pendingCallbacks.values()) pending.cancel(error);
|
|
26553
|
+
this.pendingCallbacks.clear();
|
|
26554
|
+
this.orphanResponses.clear();
|
|
26555
|
+
const socket = this.ws;
|
|
26556
|
+
this.ws = null;
|
|
26557
|
+
this.connected = false;
|
|
26558
|
+
return socket;
|
|
26559
|
+
}
|
|
26501
26560
|
};
|
|
26502
26561
|
|
|
26503
26562
|
// src/relay.ts
|
|
@@ -26533,17 +26592,33 @@ var RelayHub = class {
|
|
|
26533
26592
|
});
|
|
26534
26593
|
this.heartbeatTimer = setInterval(() => this.checkHeartbeats(), this.config.heartbeatIntervalMs);
|
|
26535
26594
|
}
|
|
26536
|
-
stop() {
|
|
26595
|
+
async stop(timeoutMs = 5e3) {
|
|
26537
26596
|
if (this.heartbeatTimer) {
|
|
26538
26597
|
clearInterval(this.heartbeatTimer);
|
|
26539
26598
|
this.heartbeatTimer = null;
|
|
26540
26599
|
}
|
|
26541
26600
|
if (this.wss) {
|
|
26601
|
+
const wss = this.wss;
|
|
26602
|
+
this.wss = null;
|
|
26542
26603
|
for (const agent of this.agents.values()) {
|
|
26543
26604
|
agent.ws.close(1001, "Relay shutting down");
|
|
26544
26605
|
}
|
|
26545
|
-
|
|
26546
|
-
|
|
26606
|
+
await new Promise((resolve3) => {
|
|
26607
|
+
let settled = false;
|
|
26608
|
+
const done = () => {
|
|
26609
|
+
if (settled) return;
|
|
26610
|
+
settled = true;
|
|
26611
|
+
clearTimeout(timer);
|
|
26612
|
+
resolve3();
|
|
26613
|
+
};
|
|
26614
|
+
const timer = setTimeout(() => {
|
|
26615
|
+
for (const client of wss.clients) client.terminate();
|
|
26616
|
+
done();
|
|
26617
|
+
}, timeoutMs);
|
|
26618
|
+
timer.unref?.();
|
|
26619
|
+
wss.close(() => done());
|
|
26620
|
+
});
|
|
26621
|
+
this.agents.clear();
|
|
26547
26622
|
this.logger.info("[perkos-a2a] Relay hub stopped");
|
|
26548
26623
|
}
|
|
26549
26624
|
}
|
|
@@ -26938,6 +27013,12 @@ var A2AServer = class {
|
|
|
26938
27013
|
taskResultHandler = null;
|
|
26939
27014
|
taskFailureHandler = null;
|
|
26940
27015
|
lastRuntimeDelivery = null;
|
|
27016
|
+
httpServer = null;
|
|
27017
|
+
lifecycleState = "new";
|
|
27018
|
+
generation = 0;
|
|
27019
|
+
startPromise = null;
|
|
27020
|
+
stopPromise = null;
|
|
27021
|
+
operations = /* @__PURE__ */ new Set();
|
|
26941
27022
|
constructor(config, logger) {
|
|
26942
27023
|
this.config = config;
|
|
26943
27024
|
this.logger = logger || { info: console.log, error: console.error };
|
|
@@ -27068,6 +27149,9 @@ var A2AServer = class {
|
|
|
27068
27149
|
});
|
|
27069
27150
|
}
|
|
27070
27151
|
async handleSendMessage(params, rpcId) {
|
|
27152
|
+
if (this.lifecycleState === "stopping" || this.lifecycleState === "stopped") {
|
|
27153
|
+
return this.error(rpcId, 503, "SERVER_RELOADING");
|
|
27154
|
+
}
|
|
27071
27155
|
const message = params.message;
|
|
27072
27156
|
const taskId = randomUUID4();
|
|
27073
27157
|
const contextId = message?.contextId || randomUUID4();
|
|
@@ -27088,10 +27172,11 @@ var A2AServer = class {
|
|
|
27088
27172
|
this.logger.info(
|
|
27089
27173
|
`[perkos-a2a] Task ${taskId} received from ${task.metadata?.fromAgent}`
|
|
27090
27174
|
);
|
|
27091
|
-
|
|
27175
|
+
const generation = this.generation;
|
|
27176
|
+
this.trackOperation(this.processTask(task, generation));
|
|
27092
27177
|
return this.success(rpcId, task);
|
|
27093
27178
|
}
|
|
27094
|
-
async processTask(task) {
|
|
27179
|
+
async processTask(task, generation) {
|
|
27095
27180
|
task.status = { state: "working", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
27096
27181
|
this.tasks.set(task.id, structuredClone(task));
|
|
27097
27182
|
const textParts = task.messages.flatMap((m) => m.parts || []).filter((p) => p.kind === "text").map((p) => p.text).join("\n");
|
|
@@ -27135,9 +27220,11 @@ var A2AServer = class {
|
|
|
27135
27220
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
27136
27221
|
};
|
|
27137
27222
|
}
|
|
27223
|
+
if (!this.isGenerationActive(generation)) return;
|
|
27138
27224
|
this.tasks.set(task.id, structuredClone(task));
|
|
27139
27225
|
this.logger.info(`[perkos-a2a] Task ${task.id} completed`);
|
|
27140
27226
|
} catch (err) {
|
|
27227
|
+
if (!this.isGenerationActive(generation)) return;
|
|
27141
27228
|
const msg = err instanceof Error ? err.message : String(err);
|
|
27142
27229
|
if (this.taskFailureHandler) {
|
|
27143
27230
|
await this.taskFailureHandler(task, msg);
|
|
@@ -27237,7 +27324,7 @@ var A2AServer = class {
|
|
|
27237
27324
|
async waitForRemoteTaskTerminal(targetUrl, headers, taskId, timeoutMs = 45e3, pollMs = 1e3) {
|
|
27238
27325
|
const deadline = Date.now() + timeoutMs;
|
|
27239
27326
|
while (Date.now() < deadline) {
|
|
27240
|
-
await new Promise((
|
|
27327
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollMs));
|
|
27241
27328
|
const response = await fetch(`${targetUrl}/a2a/jsonrpc`, {
|
|
27242
27329
|
method: "POST",
|
|
27243
27330
|
headers,
|
|
@@ -27261,7 +27348,7 @@ var A2AServer = class {
|
|
|
27261
27348
|
if (task && isTerminalTaskState(task.status?.state)) {
|
|
27262
27349
|
return this.success(randomUUID4(), structuredClone(task));
|
|
27263
27350
|
}
|
|
27264
|
-
await new Promise((
|
|
27351
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollMs));
|
|
27265
27352
|
}
|
|
27266
27353
|
return null;
|
|
27267
27354
|
}
|
|
@@ -27324,10 +27411,17 @@ var A2AServer = class {
|
|
|
27324
27411
|
}
|
|
27325
27412
|
/** Handle an inbound task received via the relay */
|
|
27326
27413
|
handleRelayTask(msg) {
|
|
27414
|
+
if (this.lifecycleState !== "running") {
|
|
27415
|
+
this.relayClient?.sendTaskResponse(
|
|
27416
|
+
msg,
|
|
27417
|
+
this.error(String(msg.payload?.id ?? msg.id), 503, "SERVER_RELOADING")
|
|
27418
|
+
);
|
|
27419
|
+
return;
|
|
27420
|
+
}
|
|
27327
27421
|
const payload = msg.payload;
|
|
27328
27422
|
const params = payload.params || payload;
|
|
27329
27423
|
const rpcId = payload.id || msg.id;
|
|
27330
|
-
this.handleSendMessage(params, rpcId).then(async (response) => {
|
|
27424
|
+
const operation = this.handleSendMessage(params, rpcId).then(async (response) => {
|
|
27331
27425
|
const task = response.result;
|
|
27332
27426
|
const acceptQueued = task?.messages?.[0]?.metadata?.acceptQueued === true;
|
|
27333
27427
|
if (task?.id && !isTerminalTaskState(task.status?.state)) {
|
|
@@ -27358,6 +27452,7 @@ var A2AServer = class {
|
|
|
27358
27452
|
this.logger.error(`[perkos-a2a] Failed to process relay task: ${errMsg2}`);
|
|
27359
27453
|
this.relayClient?.sendTaskResponse(msg, this.error(rpcId, -32603, errMsg2));
|
|
27360
27454
|
});
|
|
27455
|
+
this.trackOperation(operation);
|
|
27361
27456
|
}
|
|
27362
27457
|
startRelayClient() {
|
|
27363
27458
|
const relay = this.config.relay;
|
|
@@ -27389,13 +27484,30 @@ var A2AServer = class {
|
|
|
27389
27484
|
this.relayHub.start();
|
|
27390
27485
|
}
|
|
27391
27486
|
async start() {
|
|
27487
|
+
if (this.lifecycleState === "running") return;
|
|
27488
|
+
if (this.lifecycleState === "starting" && this.startPromise) return this.startPromise;
|
|
27489
|
+
if (this.lifecycleState === "stopping" && this.stopPromise) await this.stopPromise;
|
|
27490
|
+
const generation = ++this.generation;
|
|
27491
|
+
this.lifecycleState = "starting";
|
|
27492
|
+
const startPromise = this.startGeneration(generation);
|
|
27493
|
+
this.startPromise = startPromise;
|
|
27494
|
+
try {
|
|
27495
|
+
await startPromise;
|
|
27496
|
+
if (this.isGenerationActive(generation, "starting")) this.lifecycleState = "running";
|
|
27497
|
+
} finally {
|
|
27498
|
+
if (this.startPromise === startPromise) this.startPromise = null;
|
|
27499
|
+
}
|
|
27500
|
+
}
|
|
27501
|
+
async startGeneration(generation) {
|
|
27392
27502
|
const mode = this.config.mode || "auto";
|
|
27393
27503
|
if (mode === "relay") {
|
|
27504
|
+
if (!this.isGenerationActive(generation, "starting")) return;
|
|
27394
27505
|
this.clientOnly = true;
|
|
27395
27506
|
this.startRelayHub();
|
|
27396
27507
|
return;
|
|
27397
27508
|
}
|
|
27398
27509
|
if (mode === "client-only") {
|
|
27510
|
+
if (!this.isGenerationActive(generation, "starting")) return;
|
|
27399
27511
|
this.clientOnly = true;
|
|
27400
27512
|
this.startRelayClient();
|
|
27401
27513
|
if (!this.config.relay?.enabled) {
|
|
@@ -27406,12 +27518,14 @@ var A2AServer = class {
|
|
|
27406
27518
|
return;
|
|
27407
27519
|
}
|
|
27408
27520
|
if (mode === "full") {
|
|
27409
|
-
this.tryListen(this.config.port);
|
|
27521
|
+
await this.tryListen(this.config.port, 1, generation);
|
|
27522
|
+
if (!this.isGenerationActive(generation, "starting")) return;
|
|
27410
27523
|
this.startRelayClient();
|
|
27411
27524
|
return;
|
|
27412
27525
|
}
|
|
27413
27526
|
try {
|
|
27414
27527
|
const net = await detectNetworking();
|
|
27528
|
+
if (!this.isGenerationActive(generation, "starting")) return;
|
|
27415
27529
|
if (net.isBehindNat && !net.hasTailscale) {
|
|
27416
27530
|
if (this.config.relay?.enabled) {
|
|
27417
27531
|
this.logger.info(
|
|
@@ -27434,37 +27548,129 @@ var A2AServer = class {
|
|
|
27434
27548
|
}
|
|
27435
27549
|
} catch {
|
|
27436
27550
|
}
|
|
27437
|
-
this.tryListen(this.config.port);
|
|
27551
|
+
await this.tryListen(this.config.port, 1, generation);
|
|
27552
|
+
if (!this.isGenerationActive(generation, "starting")) return;
|
|
27438
27553
|
this.startRelayClient();
|
|
27439
27554
|
}
|
|
27440
|
-
|
|
27555
|
+
stop(options = {}) {
|
|
27556
|
+
if (this.lifecycleState === "stopping" && this.stopPromise) return this.stopPromise;
|
|
27557
|
+
if (this.lifecycleState === "stopped" || this.lifecycleState === "new") {
|
|
27558
|
+
this.lifecycleState = "stopped";
|
|
27559
|
+
this.generation += 1;
|
|
27560
|
+
return Promise.resolve();
|
|
27561
|
+
}
|
|
27562
|
+
this.lifecycleState = "stopping";
|
|
27563
|
+
this.generation += 1;
|
|
27564
|
+
const priorStart = this.startPromise;
|
|
27565
|
+
let stopPromise;
|
|
27566
|
+
stopPromise = (async () => {
|
|
27567
|
+
try {
|
|
27568
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
27569
|
+
const drainMs = Math.min(options.drainMs ?? 3e3, timeoutMs);
|
|
27570
|
+
await this.teardownResources(timeoutMs);
|
|
27571
|
+
await priorStart?.catch(() => void 0);
|
|
27572
|
+
await this.teardownResources(timeoutMs);
|
|
27573
|
+
await this.drainOperations(drainMs);
|
|
27574
|
+
for (const task of this.tasks.values()) {
|
|
27575
|
+
if (!isTerminalTaskState(task.status.state)) {
|
|
27576
|
+
task.status = {
|
|
27577
|
+
state: "failed",
|
|
27578
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27579
|
+
message: { role: "agent", parts: [{ kind: "text", text: "interrupted_by_reload" }] }
|
|
27580
|
+
};
|
|
27581
|
+
}
|
|
27582
|
+
}
|
|
27583
|
+
this.lifecycleState = "stopped";
|
|
27584
|
+
this.logger.info(`[perkos-a2a] A2A server stopped${options.reason ? ` (${options.reason})` : ""}`);
|
|
27585
|
+
} finally {
|
|
27586
|
+
if (this.stopPromise === stopPromise) this.stopPromise = null;
|
|
27587
|
+
}
|
|
27588
|
+
})();
|
|
27589
|
+
this.stopPromise = stopPromise;
|
|
27590
|
+
return stopPromise;
|
|
27591
|
+
}
|
|
27592
|
+
tryListen(port, attempt, generation) {
|
|
27441
27593
|
const maxAttempts = 3;
|
|
27442
|
-
|
|
27594
|
+
return new Promise((resolveStart) => {
|
|
27595
|
+
if (!this.isGenerationActive(generation, "starting")) {
|
|
27596
|
+
resolveStart();
|
|
27597
|
+
return;
|
|
27598
|
+
}
|
|
27443
27599
|
const bindHost = this.config.bindHost || "0.0.0.0";
|
|
27444
27600
|
const srv = this.app.listen(port, bindHost, () => {
|
|
27601
|
+
if (!this.isGenerationActive(generation, "starting")) {
|
|
27602
|
+
srv.close(() => resolveStart());
|
|
27603
|
+
return;
|
|
27604
|
+
}
|
|
27605
|
+
this.httpServer = srv;
|
|
27445
27606
|
this.logger.info(
|
|
27446
27607
|
`[perkos-a2a] ${this.config.agentName} server on ${bindHost}:${port}`
|
|
27447
27608
|
);
|
|
27448
27609
|
this.agentCard.url = `${(this.config.publicUrl || `http://localhost:${port}`).replace(/\/+$/, "")}/a2a/jsonrpc`;
|
|
27610
|
+
resolveStart();
|
|
27449
27611
|
});
|
|
27450
27612
|
srv.on("error", (err) => {
|
|
27451
27613
|
if (err.code === "EADDRINUSE" && attempt < maxAttempts) {
|
|
27452
27614
|
const nextPort = port + 1;
|
|
27453
27615
|
this.logger.info(`[perkos-a2a] Port ${port} in use, trying ${nextPort}`);
|
|
27454
|
-
this.tryListen(nextPort, attempt + 1);
|
|
27616
|
+
void this.tryListen(nextPort, attempt + 1, generation).then(resolveStart);
|
|
27455
27617
|
} else if (err.code === "EADDRINUSE") {
|
|
27456
27618
|
this.logger.info(
|
|
27457
27619
|
`[perkos-a2a] Ports ${this.config.port}-${port} all in use. Falling back to client-only mode.`
|
|
27458
27620
|
);
|
|
27459
27621
|
this.clientOnly = true;
|
|
27622
|
+
resolveStart();
|
|
27460
27623
|
} else {
|
|
27461
27624
|
this.logger.error(`[perkos-a2a] Server error: ${err.message}`);
|
|
27625
|
+
resolveStart();
|
|
27462
27626
|
}
|
|
27463
27627
|
});
|
|
27464
|
-
}
|
|
27465
|
-
|
|
27466
|
-
|
|
27467
|
-
|
|
27628
|
+
});
|
|
27629
|
+
}
|
|
27630
|
+
isGenerationActive(generation, expectedState) {
|
|
27631
|
+
return generation === this.generation && (expectedState ? this.lifecycleState === expectedState : this.lifecycleState !== "stopping" && this.lifecycleState !== "stopped");
|
|
27632
|
+
}
|
|
27633
|
+
trackOperation(operation) {
|
|
27634
|
+
this.operations.add(operation);
|
|
27635
|
+
void operation.finally(() => this.operations.delete(operation)).catch(() => void 0);
|
|
27636
|
+
}
|
|
27637
|
+
async drainOperations(timeoutMs) {
|
|
27638
|
+
if (this.operations.size === 0 || timeoutMs <= 0) return;
|
|
27639
|
+
await Promise.race([
|
|
27640
|
+
Promise.allSettled([...this.operations]),
|
|
27641
|
+
new Promise((resolveDrain) => {
|
|
27642
|
+
const timer = setTimeout(resolveDrain, timeoutMs);
|
|
27643
|
+
timer.unref?.();
|
|
27644
|
+
})
|
|
27645
|
+
]);
|
|
27646
|
+
}
|
|
27647
|
+
async teardownResources(timeoutMs) {
|
|
27648
|
+
const deadline = Date.now() + timeoutMs;
|
|
27649
|
+
const remaining = () => Math.max(1, deadline - Date.now());
|
|
27650
|
+
const relayClient = this.relayClient;
|
|
27651
|
+
this.relayClient = null;
|
|
27652
|
+
await relayClient?.stopAndWait(remaining());
|
|
27653
|
+
const relayHub = this.relayHub;
|
|
27654
|
+
this.relayHub = null;
|
|
27655
|
+
await relayHub?.stop(remaining());
|
|
27656
|
+
const httpServer = this.httpServer;
|
|
27657
|
+
this.httpServer = null;
|
|
27658
|
+
if (!httpServer) return;
|
|
27659
|
+
await new Promise((resolveClose) => {
|
|
27660
|
+
let settled = false;
|
|
27661
|
+
const done = () => {
|
|
27662
|
+
if (settled) return;
|
|
27663
|
+
settled = true;
|
|
27664
|
+
clearTimeout(timer);
|
|
27665
|
+
resolveClose();
|
|
27666
|
+
};
|
|
27667
|
+
const timer = setTimeout(() => {
|
|
27668
|
+
httpServer.closeAllConnections?.();
|
|
27669
|
+
done();
|
|
27670
|
+
}, remaining());
|
|
27671
|
+
timer.unref?.();
|
|
27672
|
+
httpServer.close(() => done());
|
|
27673
|
+
});
|
|
27468
27674
|
}
|
|
27469
27675
|
getExpressApp() {
|
|
27470
27676
|
return this.app;
|
|
@@ -27473,6 +27679,9 @@ var A2AServer = class {
|
|
|
27473
27679
|
|
|
27474
27680
|
// src/index.ts
|
|
27475
27681
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
27682
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
27683
|
+
import { dirname, resolve as resolve2 } from "node:path";
|
|
27684
|
+
import { fileURLToPath } from "node:url";
|
|
27476
27685
|
|
|
27477
27686
|
// src/agentic-actions.ts
|
|
27478
27687
|
var FALLBACK_AGENT_ALIASES = [
|
|
@@ -28017,7 +28226,7 @@ var ChatStore = class {
|
|
|
28017
28226
|
let firstMessageAt = null;
|
|
28018
28227
|
let lastMessageAt = null;
|
|
28019
28228
|
let leftover = "";
|
|
28020
|
-
return new Promise((
|
|
28229
|
+
return new Promise((resolve3, reject) => {
|
|
28021
28230
|
stream.on("data", (chunk) => {
|
|
28022
28231
|
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
28023
28232
|
hash.update(text);
|
|
@@ -28057,7 +28266,7 @@ var ChatStore = class {
|
|
|
28057
28266
|
hash.update(metaRaw);
|
|
28058
28267
|
} catch {
|
|
28059
28268
|
}
|
|
28060
|
-
|
|
28269
|
+
resolve3({
|
|
28061
28270
|
transcriptHash: hash.digest("hex"),
|
|
28062
28271
|
hashAlgo: "sha256",
|
|
28063
28272
|
messageCount,
|
|
@@ -28526,12 +28735,97 @@ function startPlatformHeartbeat(opts) {
|
|
|
28526
28735
|
};
|
|
28527
28736
|
}
|
|
28528
28737
|
|
|
28738
|
+
// src/runtime-evidence.ts
|
|
28739
|
+
import { createHmac } from "node:crypto";
|
|
28740
|
+
import {
|
|
28741
|
+
chmodSync,
|
|
28742
|
+
mkdirSync,
|
|
28743
|
+
readFileSync as readFileSync2,
|
|
28744
|
+
renameSync,
|
|
28745
|
+
writeFileSync
|
|
28746
|
+
} from "node:fs";
|
|
28747
|
+
import { homedir as homedir3 } from "node:os";
|
|
28748
|
+
import { join as join2, resolve } from "node:path";
|
|
28749
|
+
var RUNTIME_EVIDENCE_SCHEMA_VERSION = 1;
|
|
28750
|
+
function canonicalize(value) {
|
|
28751
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
28752
|
+
if (!value || typeof value !== "object") return value;
|
|
28753
|
+
return Object.fromEntries(
|
|
28754
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, canonicalize(child)])
|
|
28755
|
+
);
|
|
28756
|
+
}
|
|
28757
|
+
function canonicalConfigJson(config) {
|
|
28758
|
+
return JSON.stringify({ schemaVersion: RUNTIME_EVIDENCE_SCHEMA_VERSION, config: canonicalize(config) });
|
|
28759
|
+
}
|
|
28760
|
+
function configFingerprint(config, hmacKeyHex) {
|
|
28761
|
+
if (!/^[a-f0-9]{64}$/u.test(hmacKeyHex)) throw new Error("runtime evidence HMAC key is invalid");
|
|
28762
|
+
return createHmac("sha256", Buffer.from(hmacKeyHex, "hex")).update(canonicalConfigJson(config)).digest("hex");
|
|
28763
|
+
}
|
|
28764
|
+
function runtimeEvidenceDir(stateDir) {
|
|
28765
|
+
return resolve(stateDir ?? process.env.OPENCLAW_STATE_DIR ?? join2(homedir3(), ".openclaw"), "perkos-a2a");
|
|
28766
|
+
}
|
|
28767
|
+
function runtimeAttemptPath(stateDir) {
|
|
28768
|
+
return join2(runtimeEvidenceDir(stateDir), "active-attempt.json");
|
|
28769
|
+
}
|
|
28770
|
+
function runtimeStatusPath(stateDir) {
|
|
28771
|
+
return join2(runtimeEvidenceDir(stateDir), "runtime-status.json");
|
|
28772
|
+
}
|
|
28773
|
+
function configAgentId(config) {
|
|
28774
|
+
if (config.platform?.agentId) return config.platform.agentId;
|
|
28775
|
+
const match = config.platform?.heartbeatUrl?.match(/\/agents\/([^/]+)\/heartbeat/u);
|
|
28776
|
+
return match?.[1] ?? "unknown";
|
|
28777
|
+
}
|
|
28778
|
+
function recordRuntimeLoadEvidence(input) {
|
|
28779
|
+
const evidenceDir = runtimeEvidenceDir(input.stateDir);
|
|
28780
|
+
let attempt;
|
|
28781
|
+
try {
|
|
28782
|
+
attempt = JSON.parse(readFileSync2(runtimeAttemptPath(input.stateDir), "utf8"));
|
|
28783
|
+
} catch {
|
|
28784
|
+
input.logger.info(`[perkos-a2a] config loaded version=${input.version} agentId=${configAgentId(input.config)} attemptId=none`);
|
|
28785
|
+
return null;
|
|
28786
|
+
}
|
|
28787
|
+
const agentId = configAgentId(input.config);
|
|
28788
|
+
if (attempt.schemaVersion !== RUNTIME_EVIDENCE_SCHEMA_VERSION || attempt.targetVersion !== input.version || attempt.expectedAgentId !== agentId) {
|
|
28789
|
+
input.logger.warn?.("[perkos-a2a] runtime evidence attempt does not match the loaded plugin identity");
|
|
28790
|
+
return null;
|
|
28791
|
+
}
|
|
28792
|
+
const status = {
|
|
28793
|
+
schemaVersion: RUNTIME_EVIDENCE_SCHEMA_VERSION,
|
|
28794
|
+
loadedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
28795
|
+
version: input.version,
|
|
28796
|
+
agentId,
|
|
28797
|
+
fingerprint: configFingerprint(input.config, attempt.hmacKeyHex),
|
|
28798
|
+
attemptId: attempt.attemptId,
|
|
28799
|
+
pid: process.pid,
|
|
28800
|
+
pluginRoot: resolve(input.pluginRoot)
|
|
28801
|
+
};
|
|
28802
|
+
mkdirSync(evidenceDir, { recursive: true, mode: 448 });
|
|
28803
|
+
const target = runtimeStatusPath(input.stateDir);
|
|
28804
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
28805
|
+
writeFileSync(temporary, `${JSON.stringify(status, null, 2)}
|
|
28806
|
+
`, { mode: 384 });
|
|
28807
|
+
renameSync(temporary, target);
|
|
28808
|
+
chmodSync(target, 384);
|
|
28809
|
+
input.logger.info(
|
|
28810
|
+
`[perkos-a2a] config loaded version=${status.version} agentId=${status.agentId} attemptId=${status.attemptId} fingerprint=${status.fingerprint}`
|
|
28811
|
+
);
|
|
28812
|
+
return status;
|
|
28813
|
+
}
|
|
28814
|
+
|
|
28529
28815
|
// src/types.ts
|
|
28530
28816
|
function isWebhookEvent(msg) {
|
|
28531
28817
|
return msg.type === "webhook_event";
|
|
28532
28818
|
}
|
|
28533
28819
|
|
|
28534
28820
|
// src/index.ts
|
|
28821
|
+
function loadedPluginVersion() {
|
|
28822
|
+
try {
|
|
28823
|
+
const manifest = JSON.parse(readFileSync3(new URL("../openclaw.plugin.json", import.meta.url), "utf8"));
|
|
28824
|
+
return typeof manifest.version === "string" ? manifest.version : "unknown";
|
|
28825
|
+
} catch {
|
|
28826
|
+
return "unknown";
|
|
28827
|
+
}
|
|
28828
|
+
}
|
|
28535
28829
|
function wakeGatewayAgent(requestHeartbeatNow, reason, logger, sessionKey = "agent:main") {
|
|
28536
28830
|
if (typeof requestHeartbeatNow === "function") {
|
|
28537
28831
|
try {
|
|
@@ -28783,6 +29077,28 @@ async function deliverToHermes(config, message, logger) {
|
|
|
28783
29077
|
}
|
|
28784
29078
|
logger.info(`[perkos-a2a] Delivered inbound A2A task to Hermes API session ${sessionKey} via ${url}`);
|
|
28785
29079
|
}
|
|
29080
|
+
var A2A_LIFECYCLE_REGISTRY = /* @__PURE__ */ Symbol.for("@perkos/perkos-a2a:lifecycle-registry");
|
|
29081
|
+
function lifecycleRegistry() {
|
|
29082
|
+
const root = globalThis;
|
|
29083
|
+
return root[A2A_LIFECYCLE_REGISTRY] ??= /* @__PURE__ */ new Map();
|
|
29084
|
+
}
|
|
29085
|
+
function a2aLifecycleKey(config) {
|
|
29086
|
+
const rawRelayUrl = (config.relay?.url || "no-relay").trim();
|
|
29087
|
+
let relayUrl = rawRelayUrl.replace(/[?#].*$/u, "").replace(/\/+$/u, "").toLowerCase();
|
|
29088
|
+
try {
|
|
29089
|
+
const parsed = new URL(rawRelayUrl);
|
|
29090
|
+
parsed.username = "";
|
|
29091
|
+
parsed.password = "";
|
|
29092
|
+
parsed.search = "";
|
|
29093
|
+
parsed.hash = "";
|
|
29094
|
+
relayUrl = parsed.toString().replace(/\/+$/u, "").toLowerCase();
|
|
29095
|
+
} catch {
|
|
29096
|
+
}
|
|
29097
|
+
const heartbeatUrl = config.platform?.heartbeatUrl || "";
|
|
29098
|
+
const idFromHeartbeat = heartbeatUrl.match(/\/agents\/([^/]+)\/heartbeat(?:\?|$)/u)?.[1];
|
|
29099
|
+
const agentId = config.platform?.agentId || idFromHeartbeat || config.agentName.trim().toLowerCase();
|
|
29100
|
+
return `${relayUrl}|${agentId}`;
|
|
29101
|
+
}
|
|
28786
29102
|
function register(api) {
|
|
28787
29103
|
const pluginConfig = api.config?.plugins?.entries?.["perkos-a2a"]?.config || {
|
|
28788
29104
|
agentName: "agent",
|
|
@@ -28791,7 +29107,16 @@ function register(api) {
|
|
|
28791
29107
|
peers: {}
|
|
28792
29108
|
};
|
|
28793
29109
|
const logger = api.logger || console;
|
|
29110
|
+
const pluginVersion = loadedPluginVersion();
|
|
29111
|
+
recordRuntimeLoadEvidence({
|
|
29112
|
+
config: pluginConfig,
|
|
29113
|
+
version: pluginVersion,
|
|
29114
|
+
pluginRoot: resolve2(dirname(fileURLToPath(import.meta.url)), ".."),
|
|
29115
|
+
logger
|
|
29116
|
+
});
|
|
28794
29117
|
const server = new A2AServer(pluginConfig, logger);
|
|
29118
|
+
const lifecycleKey = a2aLifecycleKey(pluginConfig);
|
|
29119
|
+
const lifecycleInstanceId = randomUUID6();
|
|
28795
29120
|
const pendingTasks = [];
|
|
28796
29121
|
const enqueueSystemEvent = api.runtime?.system?.enqueueSystemEvent;
|
|
28797
29122
|
const requestHeartbeatNow = api.runtime?.system?.requestHeartbeatNow;
|
|
@@ -28963,12 +29288,22 @@ function register(api) {
|
|
|
28963
29288
|
});
|
|
28964
29289
|
api.registerService({
|
|
28965
29290
|
id: "perkos-a2a",
|
|
28966
|
-
start: () => {
|
|
28967
|
-
|
|
29291
|
+
start: async () => {
|
|
29292
|
+
const registry = lifecycleRegistry();
|
|
29293
|
+
const previous = registry.get(lifecycleKey);
|
|
29294
|
+
if (previous && previous.instanceId !== lifecycleInstanceId) {
|
|
29295
|
+
logger.info(`[perkos-a2a] Waiting for previous relay generation to stop for ${pluginConfig.agentName}`);
|
|
29296
|
+
await previous.server.stop({ reason: "replaced", timeoutMs: 5e3, drainMs: 3e3 });
|
|
29297
|
+
}
|
|
29298
|
+
registry.set(lifecycleKey, { instanceId: lifecycleInstanceId, server });
|
|
29299
|
+
await server.start();
|
|
28968
29300
|
logger.info(`[perkos-a2a] A2A server started for ${pluginConfig.agentName}`);
|
|
28969
29301
|
},
|
|
28970
|
-
stop: () => {
|
|
29302
|
+
stop: async () => {
|
|
28971
29303
|
logger.info("[perkos-a2a] A2A server stopping");
|
|
29304
|
+
await server.stop({ reason: "openclaw-service-stop", timeoutMs: 5e3, drainMs: 3e3 });
|
|
29305
|
+
const registry = lifecycleRegistry();
|
|
29306
|
+
if (registry.get(lifecycleKey)?.instanceId === lifecycleInstanceId) registry.delete(lifecycleKey);
|
|
28972
29307
|
}
|
|
28973
29308
|
});
|
|
28974
29309
|
const chatApiKey = pluginConfig.chat?.apiKey || pluginConfig.relay?.apiKey;
|
|
@@ -29114,7 +29449,7 @@ function register(api) {
|
|
|
29114
29449
|
url: pluginConfig.platform?.heartbeatUrl,
|
|
29115
29450
|
relayKey: pluginConfig.relay?.apiKey,
|
|
29116
29451
|
runtimeKind: "openclaw",
|
|
29117
|
-
version:
|
|
29452
|
+
version: pluginVersion,
|
|
29118
29453
|
intervalMs: pluginConfig.platform?.heartbeatIntervalMs
|
|
29119
29454
|
});
|
|
29120
29455
|
},
|
|
@@ -29443,18 +29778,18 @@ function register(api) {
|
|
|
29443
29778
|
let portAvailable = true;
|
|
29444
29779
|
try {
|
|
29445
29780
|
const netMod = await import("net");
|
|
29446
|
-
await new Promise((
|
|
29781
|
+
await new Promise((resolve3, reject) => {
|
|
29447
29782
|
const srv = netMod.createServer();
|
|
29448
29783
|
srv.once("error", (err) => {
|
|
29449
29784
|
if (err.code === "EADDRINUSE") {
|
|
29450
29785
|
portAvailable = false;
|
|
29451
|
-
|
|
29786
|
+
resolve3();
|
|
29452
29787
|
} else {
|
|
29453
29788
|
reject(err);
|
|
29454
29789
|
}
|
|
29455
29790
|
});
|
|
29456
29791
|
srv.listen(pluginConfig.port, () => {
|
|
29457
|
-
srv.close(() =>
|
|
29792
|
+
srv.close(() => resolve3());
|
|
29458
29793
|
});
|
|
29459
29794
|
});
|
|
29460
29795
|
} catch {
|
|
@@ -29505,6 +29840,7 @@ export {
|
|
|
29505
29840
|
ChatStore,
|
|
29506
29841
|
RelayClient,
|
|
29507
29842
|
RelayHub,
|
|
29843
|
+
a2aLifecycleKey,
|
|
29508
29844
|
alignOpenClawChatSessionModel,
|
|
29509
29845
|
register as default,
|
|
29510
29846
|
detectNetworking,
|