@perkos/perkos-a2a 0.12.62 → 0.12.63
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 +2 -2
- package/dist/a2a-maintenance.d.ts +39 -0
- package/dist/a2a-maintenance.d.ts.map +1 -0
- package/dist/a2a-maintenance.js +137 -0
- package/dist/a2a-maintenance.js.map +1 -0
- package/dist/a2a-maintenance.test.d.ts +2 -0
- package/dist/a2a-maintenance.test.d.ts.map +1 -0
- package/dist/a2a-maintenance.test.js +48 -0
- package/dist/a2a-maintenance.test.js.map +1 -0
- package/dist/bridge-agent.js +9 -0
- package/dist/bridge-agent.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +157 -11
- package/dist/index.js.map +4 -4
- package/dist/maintenance-runner.d.ts +3 -0
- package/dist/maintenance-runner.d.ts.map +1 -0
- package/dist/maintenance-runner.js +80 -0
- package/dist/maintenance-runner.js.map +1 -0
- package/docs/plans/2026-09-02-managed-a2a-self-update-design.md +66 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18441,10 +18441,10 @@ var require_view = __commonJS({
|
|
|
18441
18441
|
var debug = require_src()("express:view");
|
|
18442
18442
|
var path = __require("path");
|
|
18443
18443
|
var fs = __require("fs");
|
|
18444
|
-
var
|
|
18444
|
+
var dirname5 = path.dirname;
|
|
18445
18445
|
var basename = path.basename;
|
|
18446
18446
|
var extname = path.extname;
|
|
18447
|
-
var
|
|
18447
|
+
var join5 = path.join;
|
|
18448
18448
|
var resolve4 = path.resolve;
|
|
18449
18449
|
module.exports = View;
|
|
18450
18450
|
function View(name, options) {
|
|
@@ -18480,7 +18480,7 @@ var require_view = __commonJS({
|
|
|
18480
18480
|
for (var i = 0; i < roots.length && !path2; i++) {
|
|
18481
18481
|
var root = roots[i];
|
|
18482
18482
|
var loc = resolve4(root, name);
|
|
18483
|
-
var dir =
|
|
18483
|
+
var dir = dirname5(loc);
|
|
18484
18484
|
var file = basename(loc);
|
|
18485
18485
|
path2 = this.resolve(dir, file);
|
|
18486
18486
|
}
|
|
@@ -18492,12 +18492,12 @@ var require_view = __commonJS({
|
|
|
18492
18492
|
};
|
|
18493
18493
|
View.prototype.resolve = function resolve5(dir, file) {
|
|
18494
18494
|
var ext = this.ext;
|
|
18495
|
-
var path2 =
|
|
18495
|
+
var path2 = join5(dir, file);
|
|
18496
18496
|
var stat = tryStat(path2);
|
|
18497
18497
|
if (stat && stat.isFile()) {
|
|
18498
18498
|
return path2;
|
|
18499
18499
|
}
|
|
18500
|
-
path2 =
|
|
18500
|
+
path2 = join5(dir, basename(file, ext), "index" + ext);
|
|
18501
18501
|
stat = tryStat(path2);
|
|
18502
18502
|
if (stat && stat.isFile()) {
|
|
18503
18503
|
return path2;
|
|
@@ -19130,7 +19130,7 @@ var require_send = __commonJS({
|
|
|
19130
19130
|
var Stream = __require("stream");
|
|
19131
19131
|
var util = __require("util");
|
|
19132
19132
|
var extname = path.extname;
|
|
19133
|
-
var
|
|
19133
|
+
var join5 = path.join;
|
|
19134
19134
|
var normalize = path.normalize;
|
|
19135
19135
|
var resolve4 = path.resolve;
|
|
19136
19136
|
var sep = path.sep;
|
|
@@ -19349,7 +19349,7 @@ var require_send = __commonJS({
|
|
|
19349
19349
|
return res;
|
|
19350
19350
|
}
|
|
19351
19351
|
parts = path2.split(sep);
|
|
19352
|
-
path2 = normalize(
|
|
19352
|
+
path2 = normalize(join5(root, path2));
|
|
19353
19353
|
} else {
|
|
19354
19354
|
if (UP_PATH_REGEXP.test(path2)) {
|
|
19355
19355
|
debug('malicious path "%s"', path2);
|
|
@@ -19484,7 +19484,7 @@ var require_send = __commonJS({
|
|
|
19484
19484
|
if (err) return self.onStatError(err);
|
|
19485
19485
|
return self.error(404);
|
|
19486
19486
|
}
|
|
19487
|
-
var p =
|
|
19487
|
+
var p = join5(path2, self._index[i]);
|
|
19488
19488
|
debug('stat "%s"', p);
|
|
19489
19489
|
fs.stat(p, function(err2, stat) {
|
|
19490
19490
|
if (err2) return next(err2);
|
|
@@ -27779,8 +27779,8 @@ var A2AServer = class {
|
|
|
27779
27779
|
// src/index.ts
|
|
27780
27780
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
27781
27781
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
27782
|
-
import { dirname as
|
|
27783
|
-
import { fileURLToPath as
|
|
27782
|
+
import { dirname as dirname4, resolve as resolve3 } from "node:path";
|
|
27783
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
27784
27784
|
|
|
27785
27785
|
// src/task-context.ts
|
|
27786
27786
|
function perkosTaskContext(task, trustedDispatcher = process.env.PERKOS_DISPATCHER_AGENT_NAME || "perkos-ci-probe-dispatcher") {
|
|
@@ -29306,6 +29306,138 @@ async function tryHandleInstalledVoiceMarker(message, config, loadVoice = defaul
|
|
|
29306
29306
|
});
|
|
29307
29307
|
}
|
|
29308
29308
|
|
|
29309
|
+
// src/a2a-maintenance.ts
|
|
29310
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
29311
|
+
import { chmod, mkdir as mkdir2, readdir, readFile as readFile2, rename, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
29312
|
+
import { homedir as homedir5 } from "node:os";
|
|
29313
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
29314
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
29315
|
+
var PERKOS_A2A_UPDATE_PREFIX = "PERKOS_A2A_UPDATE:";
|
|
29316
|
+
var REQUEST_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
29317
|
+
function parseA2AUpdateMarker(message) {
|
|
29318
|
+
const marker = message.trim();
|
|
29319
|
+
if (!marker.startsWith(PERKOS_A2A_UPDATE_PREFIX)) return null;
|
|
29320
|
+
const requestId = marker.slice(PERKOS_A2A_UPDATE_PREFIX.length);
|
|
29321
|
+
return REQUEST_ID.test(requestId) ? requestId : null;
|
|
29322
|
+
}
|
|
29323
|
+
function maintenanceControlPlane(config) {
|
|
29324
|
+
const heartbeat = config.platform?.heartbeatUrl;
|
|
29325
|
+
const relayApiKey = config.relay?.apiKey || config.chat?.apiKey;
|
|
29326
|
+
if (!heartbeat || !relayApiKey) return null;
|
|
29327
|
+
let url;
|
|
29328
|
+
try {
|
|
29329
|
+
url = new URL(heartbeat);
|
|
29330
|
+
} catch {
|
|
29331
|
+
return null;
|
|
29332
|
+
}
|
|
29333
|
+
const match = url.pathname.match(/^(.*)\/agents\/([^/]+)\/heartbeat\/?$/u);
|
|
29334
|
+
const agentId = config.platform?.agentId || (match ? decodeURIComponent(match[2]) : "");
|
|
29335
|
+
if (!match || !agentId) return null;
|
|
29336
|
+
return { apiBaseUrl: `${url.origin}${match[1]}`, agentId, relayApiKey };
|
|
29337
|
+
}
|
|
29338
|
+
function maintenanceRoot(agentId) {
|
|
29339
|
+
return join4(homedir5(), ".perkos", "a2a", "hermes", agentId, "maintenance");
|
|
29340
|
+
}
|
|
29341
|
+
async function atomicPrivateJson(path, value) {
|
|
29342
|
+
await mkdir2(dirname3(path), { recursive: true, mode: 448 });
|
|
29343
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
29344
|
+
await writeFile2(temporary, `${JSON.stringify(value)}
|
|
29345
|
+
`, { mode: 384 });
|
|
29346
|
+
await chmod(temporary, 384);
|
|
29347
|
+
await rename(temporary, path);
|
|
29348
|
+
}
|
|
29349
|
+
async function authorizedJson(url, relayApiKey, init = {}) {
|
|
29350
|
+
const response = await fetch(url, {
|
|
29351
|
+
...init,
|
|
29352
|
+
headers: {
|
|
29353
|
+
authorization: `Bearer ${relayApiKey}`,
|
|
29354
|
+
"content-type": "application/json",
|
|
29355
|
+
...init.headers ?? {}
|
|
29356
|
+
},
|
|
29357
|
+
signal: AbortSignal.timeout(15e3)
|
|
29358
|
+
});
|
|
29359
|
+
const body = await response.json().catch(() => null);
|
|
29360
|
+
if (!response.ok || !body) throw new Error(`control_plane_${response.status}`);
|
|
29361
|
+
return body;
|
|
29362
|
+
}
|
|
29363
|
+
async function claimRequest(target, requestId) {
|
|
29364
|
+
const body = await authorizedJson(
|
|
29365
|
+
`${target.apiBaseUrl}/agents/${encodeURIComponent(target.agentId)}/maintenance/a2a-update/${requestId}/claim`,
|
|
29366
|
+
target.relayApiKey,
|
|
29367
|
+
{ method: "POST", body: "{}" }
|
|
29368
|
+
);
|
|
29369
|
+
const request = body.request;
|
|
29370
|
+
if (typeof request?.targetVersion !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(request.targetVersion)) {
|
|
29371
|
+
throw new Error("invalid_target_version");
|
|
29372
|
+
}
|
|
29373
|
+
return { targetVersion: request.targetVersion };
|
|
29374
|
+
}
|
|
29375
|
+
async function queueHermesMaintenanceJob(job) {
|
|
29376
|
+
const root = maintenanceRoot(job.agentId);
|
|
29377
|
+
const jobPath = join4(root, "jobs", `${job.requestId}.json`);
|
|
29378
|
+
await atomicPrivateJson(jobPath, job);
|
|
29379
|
+
const runnerPath = fileURLToPath2(new URL("./maintenance-runner.js", import.meta.url));
|
|
29380
|
+
const child = spawn2(process.execPath, [runnerPath, "--job", jobPath], {
|
|
29381
|
+
detached: true,
|
|
29382
|
+
stdio: "ignore"
|
|
29383
|
+
});
|
|
29384
|
+
child.unref();
|
|
29385
|
+
}
|
|
29386
|
+
async function tryHandleA2AMaintenanceMarker(message, config, deps = {}) {
|
|
29387
|
+
const requestId = parseA2AUpdateMarker(message);
|
|
29388
|
+
if (!requestId) return null;
|
|
29389
|
+
if (config.runtime?.kind !== "hermes" && config.runtime?.kind !== "hermes-api") {
|
|
29390
|
+
return "PERKOS_A2A_UPDATE_FAILED:runtime_unsupported";
|
|
29391
|
+
}
|
|
29392
|
+
const target = maintenanceControlPlane(config);
|
|
29393
|
+
if (!target) return "PERKOS_A2A_UPDATE_FAILED:a2a_identity_unavailable";
|
|
29394
|
+
try {
|
|
29395
|
+
const { targetVersion } = await (deps.claim ?? claimRequest)(target, requestId);
|
|
29396
|
+
await (deps.queue ?? queueHermesMaintenanceJob)({
|
|
29397
|
+
schemaVersion: 1,
|
|
29398
|
+
requestId,
|
|
29399
|
+
agentId: target.agentId,
|
|
29400
|
+
targetVersion,
|
|
29401
|
+
apiBaseUrl: target.apiBaseUrl,
|
|
29402
|
+
relayApiKey: target.relayApiKey
|
|
29403
|
+
});
|
|
29404
|
+
return `PERKOS_A2A_UPDATE_ACCEPTED:${requestId}`;
|
|
29405
|
+
} catch {
|
|
29406
|
+
return "PERKOS_A2A_UPDATE_FAILED:request_rejected";
|
|
29407
|
+
}
|
|
29408
|
+
}
|
|
29409
|
+
async function reportReceipt(receipt, target) {
|
|
29410
|
+
await authorizedJson(
|
|
29411
|
+
`${target.apiBaseUrl}/agents/${encodeURIComponent(target.agentId)}/maintenance/a2a-update/${receipt.requestId}/report`,
|
|
29412
|
+
target.relayApiKey,
|
|
29413
|
+
{
|
|
29414
|
+
method: "POST",
|
|
29415
|
+
body: JSON.stringify({
|
|
29416
|
+
state: receipt.state,
|
|
29417
|
+
...receipt.installedVersion ? { installedVersion: receipt.installedVersion } : {},
|
|
29418
|
+
...receipt.errorCode ? { errorCode: receipt.errorCode } : {}
|
|
29419
|
+
})
|
|
29420
|
+
}
|
|
29421
|
+
);
|
|
29422
|
+
}
|
|
29423
|
+
async function flushMaintenanceReceipts(config) {
|
|
29424
|
+
const target = maintenanceControlPlane(config);
|
|
29425
|
+
if (!target) return;
|
|
29426
|
+
const receiptsDir = join4(maintenanceRoot(target.agentId), "receipts");
|
|
29427
|
+
const files = await readdir(receiptsDir).catch(() => []);
|
|
29428
|
+
for (const file of files) {
|
|
29429
|
+
if (!file.endsWith(".json")) continue;
|
|
29430
|
+
const path = join4(receiptsDir, file);
|
|
29431
|
+
try {
|
|
29432
|
+
const receipt = JSON.parse(await readFile2(path, "utf8"));
|
|
29433
|
+
if (receipt.agentId !== target.agentId || receipt.apiBaseUrl !== target.apiBaseUrl || !REQUEST_ID.test(receipt.requestId)) continue;
|
|
29434
|
+
await reportReceipt(receipt, target);
|
|
29435
|
+
await unlink(path);
|
|
29436
|
+
} catch {
|
|
29437
|
+
}
|
|
29438
|
+
}
|
|
29439
|
+
}
|
|
29440
|
+
|
|
29309
29441
|
// src/chat-client.ts
|
|
29310
29442
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
29311
29443
|
var DEFAULT_URL = "wss://chat.perkos.xyz/chat";
|
|
@@ -30164,7 +30296,7 @@ function register(api) {
|
|
|
30164
30296
|
);
|
|
30165
30297
|
const logger = api.logger || console;
|
|
30166
30298
|
const pluginVersion = loadedPluginVersion();
|
|
30167
|
-
const pluginRoot = resolve3(
|
|
30299
|
+
const pluginRoot = resolve3(dirname4(fileURLToPath3(import.meta.url)), "..");
|
|
30168
30300
|
const server = new A2AServer(pluginConfig, logger, { relayOwnership: "external-required" });
|
|
30169
30301
|
const lifecycleKey = a2aLifecycleKey(pluginConfig);
|
|
30170
30302
|
const lifecycleInstanceId = randomUUID7();
|
|
@@ -30349,6 +30481,13 @@ function register(api) {
|
|
|
30349
30481
|
let supervisorClient;
|
|
30350
30482
|
const handleSupervisorChatDeliver = async (frame) => {
|
|
30351
30483
|
const walletAddress = parseWalletFromIdentity(frame.from);
|
|
30484
|
+
const maintenanceReply = await tryHandleA2AMaintenanceMarker(frame.text, pluginConfig);
|
|
30485
|
+
if (maintenanceReply) {
|
|
30486
|
+
if (walletAddress) {
|
|
30487
|
+
await supervisorClient.sendChatReply({ convId: frame.convId, walletAddress, text: maintenanceReply, replyTo: frame.id });
|
|
30488
|
+
}
|
|
30489
|
+
return;
|
|
30490
|
+
}
|
|
30352
30491
|
const voiceReply = await tryHandleInstalledVoiceMarker(frame.text, pluginConfig);
|
|
30353
30492
|
if (voiceReply) {
|
|
30354
30493
|
if (walletAddress) {
|
|
@@ -30485,6 +30624,7 @@ function register(api) {
|
|
|
30485
30624
|
start: async () => {
|
|
30486
30625
|
try {
|
|
30487
30626
|
await supervisorClient.start();
|
|
30627
|
+
void flushMaintenanceReceipts(pluginConfig);
|
|
30488
30628
|
const ownerHealth = supervisorClient.currentHealth();
|
|
30489
30629
|
recordRuntimeLoadEvidence({
|
|
30490
30630
|
config: pluginConfig,
|
|
@@ -30887,6 +31027,7 @@ export {
|
|
|
30887
31027
|
A2AServer,
|
|
30888
31028
|
ChatClient,
|
|
30889
31029
|
ChatStore,
|
|
31030
|
+
PERKOS_A2A_UPDATE_PREFIX,
|
|
30890
31031
|
PERKOS_VOICE_ENROLL_MARKER,
|
|
30891
31032
|
PERKOS_VOICE_PROBE_MARKER,
|
|
30892
31033
|
RelayClient,
|
|
@@ -30896,15 +31037,20 @@ export {
|
|
|
30896
31037
|
register as default,
|
|
30897
31038
|
detectNetworking,
|
|
30898
31039
|
extractOpenClawChatReply,
|
|
31040
|
+
flushMaintenanceReceipts,
|
|
30899
31041
|
formatRecentChatContext,
|
|
30900
31042
|
isWebhookEvent,
|
|
31043
|
+
maintenanceControlPlane,
|
|
30901
31044
|
normalizeOpenClawPluginConfig,
|
|
30902
31045
|
omitUnavailableAgentHarness,
|
|
30903
31046
|
openclawChatSessionKey,
|
|
31047
|
+
parseA2AUpdateMarker,
|
|
30904
31048
|
prepareOpenClawChatSession,
|
|
31049
|
+
queueHermesMaintenanceJob,
|
|
30905
31050
|
resolveOpenClawRuntimeModelRoute,
|
|
30906
31051
|
runOpenClawChatTurn,
|
|
30907
31052
|
stripPerkosChatTransportMarker,
|
|
31053
|
+
tryHandleA2AMaintenanceMarker,
|
|
30908
31054
|
tryHandleInstalledVoiceMarker,
|
|
30909
31055
|
useNativeHarnessForResolvedRoute
|
|
30910
31056
|
};
|