@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/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 dirname4 = path.dirname;
18444
+ var dirname5 = path.dirname;
18445
18445
  var basename = path.basename;
18446
18446
  var extname = path.extname;
18447
- var join4 = path.join;
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 = dirname4(loc);
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 = join4(dir, file);
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 = join4(dir, basename(file, ext), "index" + ext);
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 join4 = path.join;
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(join4(root, path2));
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 = join4(path2, self._index[i]);
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 dirname3, resolve as resolve3 } from "node:path";
27783
- import { fileURLToPath as fileURLToPath2 } from "node:url";
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(dirname3(fileURLToPath2(import.meta.url)), "..");
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
  };