@perkos/perkos-a2a 0.12.63 → 0.12.65

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.
Files changed (34) hide show
  1. package/README.md +2 -2
  2. package/dist/a2a-maintenance.d.ts +10 -8
  3. package/dist/a2a-maintenance.d.ts.map +1 -1
  4. package/dist/a2a-maintenance.js +17 -25
  5. package/dist/a2a-maintenance.js.map +1 -1
  6. package/dist/a2a-maintenance.test.js +27 -5
  7. package/dist/a2a-maintenance.test.js.map +1 -1
  8. package/dist/bridge-agent.js +11 -4
  9. package/dist/bridge-agent.js.map +1 -1
  10. package/dist/chat-client.d.ts +3 -0
  11. package/dist/chat-client.d.ts.map +1 -1
  12. package/dist/chat-client.js +9 -3
  13. package/dist/chat-client.js.map +1 -1
  14. package/dist/index.js +38 -40
  15. package/dist/index.js.map +4 -4
  16. package/dist/platform-control-plane.d.ts +9 -0
  17. package/dist/platform-control-plane.d.ts.map +1 -0
  18. package/dist/platform-control-plane.js +25 -0
  19. package/dist/platform-control-plane.js.map +1 -0
  20. package/dist/platform-control-plane.test.d.ts +2 -0
  21. package/dist/platform-control-plane.test.d.ts.map +1 -0
  22. package/dist/platform-control-plane.test.js +32 -0
  23. package/dist/platform-control-plane.test.js.map +1 -0
  24. package/dist/platform-heartbeat.d.ts +6 -0
  25. package/dist/platform-heartbeat.d.ts.map +1 -1
  26. package/dist/platform-heartbeat.js +6 -1
  27. package/dist/platform-heartbeat.js.map +1 -1
  28. package/dist/voice-capability.d.ts.map +1 -1
  29. package/dist/voice-capability.js +3 -20
  30. package/dist/voice-capability.js.map +1 -1
  31. package/docs/plans/2026-09-02-chat-bound-maintenance-capability-design.md +36 -0
  32. package/docs/plans/2026-09-02-runtime-env-control-plane-design.md +15 -0
  33. package/openclaw.plugin.json +1 -1
  34. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -29244,18 +29244,10 @@ function usesHttpDelivery(runtimeKind) {
29244
29244
  return runtimeKind === "hermes" || runtimeKind === "hermes-api" || runtimeKind === "zeroclaw" || runtimeKind === "custom";
29245
29245
  }
29246
29246
 
29247
- // src/voice-capability.ts
29248
- var PERKOS_VOICE_PROBE_MARKER = "PERKOS_VOICE_PROBE";
29249
- var PERKOS_VOICE_ENROLL_MARKER = "PERKOS_VOICE_ENROLL";
29250
- function runtime(runtimeKind) {
29251
- if (runtimeKind === "hermes" || runtimeKind === "hermes-api") return "hermes";
29252
- if (runtimeKind === "openclaw") return "openclaw";
29253
- if (runtimeKind === "zeroclaw") return "zeroclaw";
29254
- return null;
29255
- }
29256
- function controlPlane(config) {
29257
- const heartbeat = config.platform?.heartbeatUrl;
29258
- const relayApiKey = config.relay?.apiKey || config.chat?.apiKey;
29247
+ // src/platform-control-plane.ts
29248
+ function resolvePlatformControlPlane(config, env = process.env) {
29249
+ const heartbeat = config.platform?.heartbeatUrl?.trim() || env.PERKOS_HEARTBEAT_URL?.trim();
29250
+ const relayApiKey = config.relay?.apiKey?.trim() || config.chat?.apiKey?.trim() || env.A2A_RELAY_API_KEY?.trim() || env.A2A_CHAT_API_KEY?.trim();
29259
29251
  if (!heartbeat || !relayApiKey) return null;
29260
29252
  let url;
29261
29253
  try {
@@ -29264,13 +29256,23 @@ function controlPlane(config) {
29264
29256
  return null;
29265
29257
  }
29266
29258
  const match = url.pathname.match(/^(.*)\/agents\/([^/]+)\/heartbeat\/?$/u);
29267
- const agentId = config.platform?.agentId || (match ? decodeURIComponent(match[2]) : "");
29259
+ const agentId = config.platform?.agentId?.trim() || (match ? decodeURIComponent(match[2]) : "") || env.PERKOS_AGENT_ID?.trim();
29268
29260
  if (!match || !agentId) return null;
29269
29261
  return { apiBaseUrl: `${url.origin}${match[1]}`, agentId, relayApiKey };
29270
29262
  }
29263
+
29264
+ // src/voice-capability.ts
29265
+ var PERKOS_VOICE_PROBE_MARKER = "PERKOS_VOICE_PROBE";
29266
+ var PERKOS_VOICE_ENROLL_MARKER = "PERKOS_VOICE_ENROLL";
29267
+ function runtime(runtimeKind) {
29268
+ if (runtimeKind === "hermes" || runtimeKind === "hermes-api") return "hermes";
29269
+ if (runtimeKind === "openclaw") return "openclaw";
29270
+ if (runtimeKind === "zeroclaw") return "zeroclaw";
29271
+ return null;
29272
+ }
29271
29273
  async function reportPluginMissing(message, config) {
29272
29274
  if (message !== PERKOS_VOICE_PROBE_MARKER) return "PERKOS_VOICE_FAILED:voice_plugin_missing";
29273
- const target = controlPlane(config);
29275
+ const target = resolvePlatformControlPlane(config);
29274
29276
  if (!target) return "PERKOS_VOICE_FAILED:a2a_identity_unavailable";
29275
29277
  const response = await fetch(`${target.apiBaseUrl}/agents/${encodeURIComponent(target.agentId)}/voice-credential/capability-report`, {
29276
29278
  method: "POST",
@@ -29286,7 +29288,7 @@ async function defaultLoader() {
29286
29288
  async function tryHandleInstalledVoiceMarker(message, config, loadVoice = defaultLoader) {
29287
29289
  const marker = message.trim();
29288
29290
  if (marker !== PERKOS_VOICE_PROBE_MARKER && marker !== PERKOS_VOICE_ENROLL_MARKER) return null;
29289
- const target = controlPlane(config);
29291
+ const target = resolvePlatformControlPlane(config);
29290
29292
  if (!target) return "PERKOS_VOICE_FAILED:a2a_identity_unavailable";
29291
29293
  const runtimeKind = runtime(config.runtime?.kind);
29292
29294
  if (!runtimeKind) return reportPluginMissing(marker, config);
@@ -29314,27 +29316,14 @@ import { dirname as dirname3, join as join4 } from "node:path";
29314
29316
  import { fileURLToPath as fileURLToPath2 } from "node:url";
29315
29317
  var PERKOS_A2A_UPDATE_PREFIX = "PERKOS_A2A_UPDATE:";
29316
29318
  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;
29319
+ var BRIDGE_INSTANCE_ID = REQUEST_ID;
29317
29320
  function parseA2AUpdateMarker(message) {
29318
29321
  const marker = message.trim();
29319
29322
  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 };
29323
+ const [requestId, bridgeInstanceId, extra] = marker.slice(PERKOS_A2A_UPDATE_PREFIX.length).split(":");
29324
+ return !extra && requestId && bridgeInstanceId && REQUEST_ID.test(requestId) && BRIDGE_INSTANCE_ID.test(bridgeInstanceId) ? { requestId, bridgeInstanceId } : null;
29337
29325
  }
29326
+ var maintenanceControlPlane = resolvePlatformControlPlane;
29338
29327
  function maintenanceRoot(agentId) {
29339
29328
  return join4(homedir5(), ".perkos", "a2a", "hermes", agentId, "maintenance");
29340
29329
  }
@@ -29360,11 +29349,11 @@ async function authorizedJson(url, relayApiKey, init = {}) {
29360
29349
  if (!response.ok || !body) throw new Error(`control_plane_${response.status}`);
29361
29350
  return body;
29362
29351
  }
29363
- async function claimRequest(target, requestId) {
29352
+ async function claimRequest(target, requestId, bridgeInstanceId) {
29364
29353
  const body = await authorizedJson(
29365
29354
  `${target.apiBaseUrl}/agents/${encodeURIComponent(target.agentId)}/maintenance/a2a-update/${requestId}/claim`,
29366
29355
  target.relayApiKey,
29367
- { method: "POST", body: "{}" }
29356
+ { method: "POST", body: "{}", headers: { "x-perkos-bridge-instance": bridgeInstanceId } }
29368
29357
  );
29369
29358
  const request = body.request;
29370
29359
  if (typeof request?.targetVersion !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(request.targetVersion)) {
@@ -29384,15 +29373,19 @@ async function queueHermesMaintenanceJob(job) {
29384
29373
  child.unref();
29385
29374
  }
29386
29375
  async function tryHandleA2AMaintenanceMarker(message, config, deps = {}) {
29387
- const requestId = parseA2AUpdateMarker(message);
29388
- if (!requestId) return null;
29376
+ const marker = parseA2AUpdateMarker(message);
29377
+ if (!marker) return null;
29378
+ const { requestId, bridgeInstanceId } = marker;
29379
+ if (!deps.bridgeInstanceId || deps.bridgeInstanceId !== bridgeInstanceId) {
29380
+ return "PERKOS_A2A_UPDATE_FAILED:bridge_instance_mismatch";
29381
+ }
29389
29382
  if (config.runtime?.kind !== "hermes" && config.runtime?.kind !== "hermes-api") {
29390
29383
  return "PERKOS_A2A_UPDATE_FAILED:runtime_unsupported";
29391
29384
  }
29392
29385
  const target = maintenanceControlPlane(config);
29393
29386
  if (!target) return "PERKOS_A2A_UPDATE_FAILED:a2a_identity_unavailable";
29394
29387
  try {
29395
- const { targetVersion } = await (deps.claim ?? claimRequest)(target, requestId);
29388
+ const { targetVersion } = await (deps.claim ?? claimRequest)(target, requestId, bridgeInstanceId);
29396
29389
  await (deps.queue ?? queueHermesMaintenanceJob)({
29397
29390
  schemaVersion: 1,
29398
29391
  requestId,
@@ -29463,6 +29456,11 @@ var ChatClient = class {
29463
29456
  authed = false;
29464
29457
  stopped = false;
29465
29458
  generation = 0;
29459
+ setAuthed(value) {
29460
+ if (this.authed === value) return;
29461
+ this.authed = value;
29462
+ this.handlers.onConnectionStateChange?.(value);
29463
+ }
29466
29464
  constructor(opts) {
29467
29465
  this.agentName = opts.agentName;
29468
29466
  const cfg = opts.config;
@@ -29624,7 +29622,7 @@ var ChatClient = class {
29624
29622
  }
29625
29623
  if (!this.isCurrent(generation, socket)) return;
29626
29624
  this.connected = false;
29627
- this.authed = false;
29625
+ this.setAuthed(false);
29628
29626
  this.clearTimers();
29629
29627
  if (!this.stopped) {
29630
29628
  this.logger.info(`[perkos-chat] disconnected (${code}: ${reason?.toString() || "no reason"}); reconnecting`);
@@ -29639,7 +29637,7 @@ var ChatClient = class {
29639
29637
  async handleFrame(frame) {
29640
29638
  switch (frame.type) {
29641
29639
  case "auth_ok":
29642
- this.authed = true;
29640
+ this.setAuthed(true);
29643
29641
  this.logger.info(`[perkos-chat] authed as agent:${this.agentName}`);
29644
29642
  this.startHeartbeat();
29645
29643
  return;
@@ -29850,7 +29848,7 @@ var ChatClient = class {
29850
29848
  const socket = this.ws;
29851
29849
  this.ws = null;
29852
29850
  this.connected = false;
29853
- this.authed = false;
29851
+ this.setAuthed(false);
29854
29852
  return socket;
29855
29853
  }
29856
29854
  };