@perkos/perkos-a2a 0.12.22 → 0.12.27

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
@@ -22632,218 +22632,6 @@ var require_constants = __commonJS({
22632
22632
  }
22633
22633
  });
22634
22634
 
22635
- // ../../../../node_modules/node-gyp-build/node-gyp-build.js
22636
- var require_node_gyp_build = __commonJS({
22637
- "../../../../node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
22638
- var fs = __require("fs");
22639
- var path = __require("path");
22640
- var os = __require("os");
22641
- var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
22642
- var vars = process.config && process.config.variables || {};
22643
- var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
22644
- var abi = process.versions.modules;
22645
- var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
22646
- var arch = process.env.npm_config_arch || os.arch();
22647
- var platform = process.env.npm_config_platform || os.platform();
22648
- var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
22649
- var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
22650
- var uv = (process.versions.uv || "").split(".")[0];
22651
- module.exports = load;
22652
- function load(dir) {
22653
- return runtimeRequire(load.resolve(dir));
22654
- }
22655
- load.resolve = load.path = function(dir) {
22656
- dir = path.resolve(dir || ".");
22657
- try {
22658
- var name = runtimeRequire(path.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
22659
- if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
22660
- } catch (err) {
22661
- }
22662
- if (!prebuildsOnly) {
22663
- var release = getFirst(path.join(dir, "build/Release"), matchBuild);
22664
- if (release) return release;
22665
- var debug = getFirst(path.join(dir, "build/Debug"), matchBuild);
22666
- if (debug) return debug;
22667
- }
22668
- var prebuild = resolve(dir);
22669
- if (prebuild) return prebuild;
22670
- var nearby = resolve(path.dirname(process.execPath));
22671
- if (nearby) return nearby;
22672
- var target = [
22673
- "platform=" + platform,
22674
- "arch=" + arch,
22675
- "runtime=" + runtime,
22676
- "abi=" + abi,
22677
- "uv=" + uv,
22678
- armv ? "armv=" + armv : "",
22679
- "libc=" + libc,
22680
- "node=" + process.versions.node,
22681
- process.versions.electron ? "electron=" + process.versions.electron : "",
22682
- typeof __webpack_require__ === "function" ? "webpack=true" : ""
22683
- // eslint-disable-line
22684
- ].filter(Boolean).join(" ");
22685
- throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
22686
- function resolve(dir2) {
22687
- var tuples = readdirSync(path.join(dir2, "prebuilds")).map(parseTuple);
22688
- var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
22689
- if (!tuple) return;
22690
- var prebuilds = path.join(dir2, "prebuilds", tuple.name);
22691
- var parsed = readdirSync(prebuilds).map(parseTags);
22692
- var candidates = parsed.filter(matchTags(runtime, abi));
22693
- var winner = candidates.sort(compareTags(runtime))[0];
22694
- if (winner) return path.join(prebuilds, winner.file);
22695
- }
22696
- };
22697
- function readdirSync(dir) {
22698
- try {
22699
- return fs.readdirSync(dir);
22700
- } catch (err) {
22701
- return [];
22702
- }
22703
- }
22704
- function getFirst(dir, filter) {
22705
- var files = readdirSync(dir).filter(filter);
22706
- return files[0] && path.join(dir, files[0]);
22707
- }
22708
- function matchBuild(name) {
22709
- return /\.node$/.test(name);
22710
- }
22711
- function parseTuple(name) {
22712
- var arr = name.split("-");
22713
- if (arr.length !== 2) return;
22714
- var platform2 = arr[0];
22715
- var architectures = arr[1].split("+");
22716
- if (!platform2) return;
22717
- if (!architectures.length) return;
22718
- if (!architectures.every(Boolean)) return;
22719
- return { name, platform: platform2, architectures };
22720
- }
22721
- function matchTuple(platform2, arch2) {
22722
- return function(tuple) {
22723
- if (tuple == null) return false;
22724
- if (tuple.platform !== platform2) return false;
22725
- return tuple.architectures.includes(arch2);
22726
- };
22727
- }
22728
- function compareTuples(a, b) {
22729
- return a.architectures.length - b.architectures.length;
22730
- }
22731
- function parseTags(file) {
22732
- var arr = file.split(".");
22733
- var extension2 = arr.pop();
22734
- var tags = { file, specificity: 0 };
22735
- if (extension2 !== "node") return;
22736
- for (var i = 0; i < arr.length; i++) {
22737
- var tag = arr[i];
22738
- if (tag === "node" || tag === "electron" || tag === "node-webkit") {
22739
- tags.runtime = tag;
22740
- } else if (tag === "napi") {
22741
- tags.napi = true;
22742
- } else if (tag.slice(0, 3) === "abi") {
22743
- tags.abi = tag.slice(3);
22744
- } else if (tag.slice(0, 2) === "uv") {
22745
- tags.uv = tag.slice(2);
22746
- } else if (tag.slice(0, 4) === "armv") {
22747
- tags.armv = tag.slice(4);
22748
- } else if (tag === "glibc" || tag === "musl") {
22749
- tags.libc = tag;
22750
- } else {
22751
- continue;
22752
- }
22753
- tags.specificity++;
22754
- }
22755
- return tags;
22756
- }
22757
- function matchTags(runtime2, abi2) {
22758
- return function(tags) {
22759
- if (tags == null) return false;
22760
- if (tags.runtime && tags.runtime !== runtime2 && !runtimeAgnostic(tags)) return false;
22761
- if (tags.abi && tags.abi !== abi2 && !tags.napi) return false;
22762
- if (tags.uv && tags.uv !== uv) return false;
22763
- if (tags.armv && tags.armv !== armv) return false;
22764
- if (tags.libc && tags.libc !== libc) return false;
22765
- return true;
22766
- };
22767
- }
22768
- function runtimeAgnostic(tags) {
22769
- return tags.runtime === "node" && tags.napi;
22770
- }
22771
- function compareTags(runtime2) {
22772
- return function(a, b) {
22773
- if (a.runtime !== b.runtime) {
22774
- return a.runtime === runtime2 ? -1 : 1;
22775
- } else if (a.abi !== b.abi) {
22776
- return a.abi ? -1 : 1;
22777
- } else if (a.specificity !== b.specificity) {
22778
- return a.specificity > b.specificity ? -1 : 1;
22779
- } else {
22780
- return 0;
22781
- }
22782
- };
22783
- }
22784
- function isNwjs() {
22785
- return !!(process.versions && process.versions.nw);
22786
- }
22787
- function isElectron() {
22788
- if (process.versions && process.versions.electron) return true;
22789
- if (process.env.ELECTRON_RUN_AS_NODE) return true;
22790
- return typeof window !== "undefined" && window.process && window.process.type === "renderer";
22791
- }
22792
- function isAlpine(platform2) {
22793
- return platform2 === "linux" && fs.existsSync("/etc/alpine-release");
22794
- }
22795
- load.parseTags = parseTags;
22796
- load.matchTags = matchTags;
22797
- load.compareTags = compareTags;
22798
- load.parseTuple = parseTuple;
22799
- load.matchTuple = matchTuple;
22800
- load.compareTuples = compareTuples;
22801
- }
22802
- });
22803
-
22804
- // ../../../../node_modules/node-gyp-build/index.js
22805
- var require_node_gyp_build2 = __commonJS({
22806
- "../../../../node_modules/node-gyp-build/index.js"(exports, module) {
22807
- var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
22808
- if (typeof runtimeRequire.addon === "function") {
22809
- module.exports = runtimeRequire.addon.bind(runtimeRequire);
22810
- } else {
22811
- module.exports = require_node_gyp_build();
22812
- }
22813
- }
22814
- });
22815
-
22816
- // ../../../../node_modules/bufferutil/fallback.js
22817
- var require_fallback = __commonJS({
22818
- "../../../../node_modules/bufferutil/fallback.js"(exports, module) {
22819
- "use strict";
22820
- var mask = (source, mask2, output, offset, length) => {
22821
- for (var i = 0; i < length; i++) {
22822
- output[offset + i] = source[i] ^ mask2[i & 3];
22823
- }
22824
- };
22825
- var unmask = (buffer, mask2) => {
22826
- const length = buffer.length;
22827
- for (var i = 0; i < length; i++) {
22828
- buffer[i] ^= mask2[i & 3];
22829
- }
22830
- };
22831
- module.exports = { mask, unmask };
22832
- }
22833
- });
22834
-
22835
- // ../../../../node_modules/bufferutil/index.js
22836
- var require_bufferutil = __commonJS({
22837
- "../../../../node_modules/bufferutil/index.js"(exports, module) {
22838
- "use strict";
22839
- try {
22840
- module.exports = require_node_gyp_build2()(__dirname);
22841
- } catch (e) {
22842
- module.exports = require_fallback();
22843
- }
22844
- }
22845
- });
22846
-
22847
22635
  // node_modules/ws/lib/buffer-util.js
22848
22636
  var require_buffer_util = __commonJS({
22849
22637
  "node_modules/ws/lib/buffer-util.js"(exports, module) {
@@ -22904,7 +22692,7 @@ var require_buffer_util = __commonJS({
22904
22692
  };
22905
22693
  if (!process.env.WS_NO_BUFFER_UTIL) {
22906
22694
  try {
22907
- const bufferUtil = require_bufferutil();
22695
+ const bufferUtil = __require("bufferutil");
22908
22696
  module.exports.mask = function(source, mask, output, offset, length) {
22909
22697
  if (length < 48) _mask(source, mask, output, offset, length);
22910
22698
  else bufferUtil.mask(source, mask, output, offset, length);
@@ -23352,55 +23140,6 @@ var require_permessage_deflate = __commonJS({
23352
23140
  }
23353
23141
  });
23354
23142
 
23355
- // ../../../../node_modules/utf-8-validate/fallback.js
23356
- var require_fallback2 = __commonJS({
23357
- "../../../../node_modules/utf-8-validate/fallback.js"(exports, module) {
23358
- "use strict";
23359
- function isValidUTF8(buf) {
23360
- const len = buf.length;
23361
- let i = 0;
23362
- while (i < len) {
23363
- if ((buf[i] & 128) === 0) {
23364
- i++;
23365
- } else if ((buf[i] & 224) === 192) {
23366
- if (i + 1 === len || (buf[i + 1] & 192) !== 128 || (buf[i] & 254) === 192) {
23367
- return false;
23368
- }
23369
- i += 2;
23370
- } else if ((buf[i] & 240) === 224) {
23371
- if (i + 2 >= len || (buf[i + 1] & 192) !== 128 || (buf[i + 2] & 192) !== 128 || buf[i] === 224 && (buf[i + 1] & 224) === 128 || // overlong
23372
- buf[i] === 237 && (buf[i + 1] & 224) === 160) {
23373
- return false;
23374
- }
23375
- i += 3;
23376
- } else if ((buf[i] & 248) === 240) {
23377
- if (i + 3 >= len || (buf[i + 1] & 192) !== 128 || (buf[i + 2] & 192) !== 128 || (buf[i + 3] & 192) !== 128 || buf[i] === 240 && (buf[i + 1] & 240) === 128 || // overlong
23378
- buf[i] === 244 && buf[i + 1] > 143 || buf[i] > 244) {
23379
- return false;
23380
- }
23381
- i += 4;
23382
- } else {
23383
- return false;
23384
- }
23385
- }
23386
- return true;
23387
- }
23388
- module.exports = isValidUTF8;
23389
- }
23390
- });
23391
-
23392
- // ../../../../node_modules/utf-8-validate/index.js
23393
- var require_utf_8_validate = __commonJS({
23394
- "../../../../node_modules/utf-8-validate/index.js"(exports, module) {
23395
- "use strict";
23396
- try {
23397
- module.exports = require_node_gyp_build2()(__dirname);
23398
- } catch (e) {
23399
- module.exports = require_fallback2();
23400
- }
23401
- }
23402
- });
23403
-
23404
23143
  // node_modules/ws/lib/validation.js
23405
23144
  var require_validation = __commonJS({
23406
23145
  "node_modules/ws/lib/validation.js"(exports, module) {
@@ -23592,7 +23331,7 @@ var require_validation = __commonJS({
23592
23331
  };
23593
23332
  } else if (!process.env.WS_NO_UTF_8_VALIDATE) {
23594
23333
  try {
23595
- const isValidUTF8 = require_utf_8_validate();
23334
+ const isValidUTF8 = __require("utf-8-validate");
23596
23335
  module.exports.isValidUTF8 = function(buf) {
23597
23336
  return buf.length < 32 ? _isValidUTF8(buf) : isValidUTF8(buf);
23598
23337
  };
@@ -28724,6 +28463,69 @@ function errMsg(err) {
28724
28463
  return err instanceof Error ? err.message : String(err);
28725
28464
  }
28726
28465
 
28466
+ // src/platform-heartbeat.ts
28467
+ var DEFAULT_INTERVAL_MS = 6e4;
28468
+ var MIN_INTERVAL_MS = 1e4;
28469
+ function startPlatformHeartbeat(opts) {
28470
+ const env = opts.env ?? process.env;
28471
+ const fetcher = opts.fetcher ?? fetch;
28472
+ const url = opts.url?.trim() || env.PERKOS_HEARTBEAT_URL?.trim();
28473
+ const relayKey = opts.relayKey?.trim() || env.A2A_RELAY_API_KEY?.trim();
28474
+ if (!url || !relayKey) return { stop: () => void 0 };
28475
+ const configuredInterval = Number(
28476
+ opts.intervalMs ?? env.PERKOS_HEARTBEAT_INTERVAL_MS ?? DEFAULT_INTERVAL_MS
28477
+ );
28478
+ const intervalMs = Number.isFinite(configuredInterval) ? Math.max(MIN_INTERVAL_MS, configuredInterval) : DEFAULT_INTERVAL_MS;
28479
+ const a2aRuntime = env.A2A_RUNTIME?.trim() || "hermes-api";
28480
+ const runtimeKind = opts.runtimeKind ?? (a2aRuntime === "openclaw" ? "openclaw" : a2aRuntime === "custom" ? "custom" : "hermes");
28481
+ let stopped = false;
28482
+ let firstSuccess = true;
28483
+ const report = async () => {
28484
+ if (stopped) return;
28485
+ try {
28486
+ const res = await fetcher(url, {
28487
+ method: "POST",
28488
+ headers: {
28489
+ "content-type": "application/json",
28490
+ authorization: `Bearer ${relayKey}`,
28491
+ "x-relay-key": relayKey
28492
+ },
28493
+ body: JSON.stringify({
28494
+ runtimeKind,
28495
+ version: opts.version?.trim() || env.PERKOS_A2A_VERSION?.trim() || "0.12.26",
28496
+ ts: Date.now()
28497
+ })
28498
+ });
28499
+ if (res.ok) {
28500
+ if (firstSuccess) {
28501
+ opts.logger.info(
28502
+ `[perkos-heartbeat] reported online to ${url} (status=${res.status}, every=${intervalMs}ms)`
28503
+ );
28504
+ firstSuccess = false;
28505
+ }
28506
+ } else {
28507
+ opts.logger.warn(
28508
+ `[perkos-heartbeat] ${res.status} from ${url} (continuing)`
28509
+ );
28510
+ }
28511
+ } catch (err) {
28512
+ const msg = err instanceof Error ? err.message : String(err);
28513
+ opts.logger.warn(
28514
+ `[perkos-heartbeat] POST failed to ${url}: ${msg} (continuing)`
28515
+ );
28516
+ }
28517
+ };
28518
+ void report();
28519
+ const timer = setInterval(() => void report(), intervalMs);
28520
+ timer.unref?.();
28521
+ return {
28522
+ stop: () => {
28523
+ stopped = true;
28524
+ clearInterval(timer);
28525
+ }
28526
+ };
28527
+ }
28528
+
28727
28529
  // src/types.ts
28728
28530
  function isWebhookEvent(msg) {
28729
28531
  return msg.type === "webhook_event";
@@ -28756,6 +28558,191 @@ function openclawChatSessionKey(config, convId) {
28756
28558
  const safe = convId.replace(/[^a-zA-Z0-9_.-]/g, "-").slice(0, 120) || "unknown";
28757
28559
  return `${configured}:perkos-chat-${safe}`;
28758
28560
  }
28561
+ var MISSING_AGENT_HARNESS_RE = /Requested agent harness "([^"]+)" is not registered\./u;
28562
+ function missingAgentHarnessId(error) {
28563
+ const message = error instanceof Error ? error.message : String(error);
28564
+ return message.match(MISSING_AGENT_HARNESS_RE)?.[1]?.trim() || null;
28565
+ }
28566
+ function omitUnavailableAgentHarness(value, unavailableHarnessId) {
28567
+ if (Array.isArray(value)) {
28568
+ return value.map((entry) => omitUnavailableAgentHarness(entry, unavailableHarnessId));
28569
+ }
28570
+ if (!value || typeof value !== "object") return value;
28571
+ const source = value;
28572
+ const next = {};
28573
+ for (const [key, entry] of Object.entries(source)) {
28574
+ if (key === "agentRuntime" && entry && typeof entry === "object" && String(entry.id ?? "").trim() === unavailableHarnessId) {
28575
+ continue;
28576
+ }
28577
+ if ((key === "embeddedHarness" || key === "agentRuntimeOverride" || key === "agentHarnessId") && typeof entry === "string" && entry.trim() === unavailableHarnessId) {
28578
+ continue;
28579
+ }
28580
+ next[key] = omitUnavailableAgentHarness(entry, unavailableHarnessId);
28581
+ }
28582
+ return next;
28583
+ }
28584
+ function alignOpenClawChatSessionModel(entry, configuredModel) {
28585
+ const next = { ...entry };
28586
+ for (const key of [
28587
+ "providerOverride",
28588
+ "modelOverride",
28589
+ "modelOverrideSource",
28590
+ "modelOverrideFallbackOriginProvider",
28591
+ "modelOverrideFallbackOriginModel",
28592
+ "agentRuntimeOverride",
28593
+ "agentHarnessId",
28594
+ "modelProvider",
28595
+ "model",
28596
+ "fallbackNoticeSelectedModel",
28597
+ "fallbackNoticeActiveModel",
28598
+ "fallbackNoticeReason"
28599
+ ]) {
28600
+ delete next[key];
28601
+ }
28602
+ const modelRef = configuredModel?.trim();
28603
+ const separator = modelRef?.indexOf("/") ?? -1;
28604
+ if (modelRef && separator > 0 && separator < modelRef.length - 1) {
28605
+ const provider = modelRef.slice(0, separator);
28606
+ const model = modelRef.slice(separator + 1);
28607
+ next.providerOverride = provider;
28608
+ next.modelOverride = model;
28609
+ next.modelOverrideSource = "user";
28610
+ next.modelProvider = provider;
28611
+ next.model = model;
28612
+ }
28613
+ return next;
28614
+ }
28615
+ async function prepareOpenClawChatSession(api, config, sessionKey, logger) {
28616
+ const patchSessionEntry = api.runtime?.agent?.session?.patchSessionEntry;
28617
+ if (typeof patchSessionEntry !== "function") {
28618
+ logger.info("[perkos-chat] OpenClaw session metadata API unavailable \u2014 using gateway session defaults");
28619
+ return;
28620
+ }
28621
+ try {
28622
+ await patchSessionEntry({
28623
+ sessionKey,
28624
+ fallbackEntry: { sessionId: randomUUID6(), updatedAt: Date.now() },
28625
+ preserveActivity: true,
28626
+ replaceEntry: true,
28627
+ update: (entry) => alignOpenClawChatSessionModel(entry, config.runtime?.model)
28628
+ });
28629
+ logger.info(
28630
+ config.runtime?.model ? `[perkos-chat] session ${sessionKey} pinned to ${config.runtime.model}` : `[perkos-chat] session ${sessionKey} aligned to the gateway's active default model`
28631
+ );
28632
+ } catch (err) {
28633
+ logger.error(
28634
+ `[perkos-chat] failed to align OpenClaw session model: ${err instanceof Error ? err.message : String(err)}`
28635
+ );
28636
+ }
28637
+ }
28638
+ async function forceNativeOpenClawChatRuntime(api, config, sessionKey, logger) {
28639
+ const patchSessionEntry = api.runtime?.agent?.session?.patchSessionEntry;
28640
+ if (typeof patchSessionEntry !== "function") return;
28641
+ try {
28642
+ await patchSessionEntry({
28643
+ sessionKey,
28644
+ fallbackEntry: { sessionId: randomUUID6(), updatedAt: Date.now() },
28645
+ preserveActivity: true,
28646
+ replaceEntry: true,
28647
+ update: (entry) => ({
28648
+ ...alignOpenClawChatSessionModel(entry, config.runtime?.model),
28649
+ // OpenClaw <= 2026.5.x calls its built-in runtime "pi". Newer
28650
+ // gateways normalize the alias to their native "openclaw" harness.
28651
+ agentRuntimeOverride: "pi",
28652
+ agentHarnessId: "pi"
28653
+ })
28654
+ });
28655
+ logger.info(`[perkos-chat] session ${sessionKey} retrying with the native OpenClaw harness`);
28656
+ } catch (err) {
28657
+ logger.error(
28658
+ `[perkos-chat] failed to select native OpenClaw harness: ${err instanceof Error ? err.message : String(err)}`
28659
+ );
28660
+ }
28661
+ }
28662
+ function extractOpenClawChatReply(result) {
28663
+ const finalText = result.meta?.finalAssistantVisibleText?.trim() || result.meta?.finalAssistantRawText?.trim();
28664
+ if (finalText) return finalText;
28665
+ const payloadText = result.payloads?.filter((payload) => !payload.isError && !payload.isReasoning && !payload.isCommentary).map((payload) => payload.text?.trim()).filter((text) => Boolean(text)).join("\n\n").trim();
28666
+ return payloadText || null;
28667
+ }
28668
+ function openclawAgentId(config) {
28669
+ const sessionKey = config.runtime?.sessionKey || "agent:main";
28670
+ const parts = sessionKey.split(":");
28671
+ return parts[0] === "agent" && parts[1] ? parts[1] : "main";
28672
+ }
28673
+ async function runOpenClawChatTurn(api, config, sessionKey, prompt, transcriptPrompt, logger) {
28674
+ const runEmbeddedAgent = api.runtime?.agent?.runEmbeddedAgent;
28675
+ const currentConfig = api.runtime?.config?.current;
28676
+ const resolveAgentWorkspaceDir = api.runtime?.agent?.resolveAgentWorkspaceDir;
28677
+ if (typeof runEmbeddedAgent !== "function" || typeof currentConfig !== "function" || typeof resolveAgentWorkspaceDir !== "function") {
28678
+ logger.info("[perkos-chat] embedded OpenClaw runner unavailable \u2014 using gateway wake fallback");
28679
+ return null;
28680
+ }
28681
+ const agentId = openclawAgentId(config);
28682
+ const runtimeConfig = currentConfig();
28683
+ const workspaceDir = resolveAgentWorkspaceDir(runtimeConfig, agentId);
28684
+ const entry = api.runtime?.agent?.session?.getSessionEntry?.({ agentId, sessionKey });
28685
+ const sessionId = typeof entry?.sessionId === "string" && entry.sessionId.trim() ? entry.sessionId : randomUUID6();
28686
+ const configuredTimeout = api.runtime?.agent?.resolveAgentTimeoutMs?.(runtimeConfig);
28687
+ const timeoutMs = Math.min(
28688
+ 75e3,
28689
+ typeof configuredTimeout === "number" && configuredTimeout > 0 ? configuredTimeout : 75e3
28690
+ );
28691
+ const run = async (configOverride, retryNative = false) => runEmbeddedAgent({
28692
+ sessionId,
28693
+ sessionKey,
28694
+ agentId,
28695
+ workspaceDir,
28696
+ config: configOverride,
28697
+ prompt,
28698
+ transcriptPrompt,
28699
+ timeoutMs,
28700
+ runId: randomUUID6(),
28701
+ trigger: "user",
28702
+ messageChannel: "perkos-chat",
28703
+ disableTools: true,
28704
+ disableMessageTool: true,
28705
+ terminalReplyExpectation: "required",
28706
+ suppressLiveStreamOutput: true,
28707
+ cleanupBundleMcpOnRunEnd: true,
28708
+ ...retryNative ? {
28709
+ // Cross-version compatibility: 2026.5.x recognizes `pi`, while
28710
+ // current gateways recognize the explicit native runtime override.
28711
+ agentHarnessId: "pi",
28712
+ agentHarnessRuntimeOverride: "openclaw",
28713
+ suppressNextUserMessagePersistence: true
28714
+ } : {}
28715
+ });
28716
+ try {
28717
+ let result;
28718
+ try {
28719
+ result = await run(runtimeConfig);
28720
+ } catch (err) {
28721
+ const unavailableHarnessId = missingAgentHarnessId(err);
28722
+ if (!unavailableHarnessId) throw err;
28723
+ logger.info(
28724
+ `[perkos-chat] configured harness ${unavailableHarnessId} is unavailable; retrying with the native OpenClaw runtime and the same gateway-selected model`
28725
+ );
28726
+ await forceNativeOpenClawChatRuntime(api, config, sessionKey, logger);
28727
+ result = await run(
28728
+ omitUnavailableAgentHarness(runtimeConfig, unavailableHarnessId),
28729
+ true
28730
+ );
28731
+ }
28732
+ const reply = extractOpenClawChatReply(result);
28733
+ if (!reply) {
28734
+ logger.error(`[perkos-chat] embedded OpenClaw run completed without reply text for ${sessionKey}`);
28735
+ return null;
28736
+ }
28737
+ logger.info(`[perkos-chat] embedded OpenClaw run completed for ${sessionKey}`);
28738
+ return reply;
28739
+ } catch (err) {
28740
+ logger.error(
28741
+ `[perkos-chat] embedded OpenClaw run failed for ${sessionKey}: ${err instanceof Error ? err.message : String(err)}`
28742
+ );
28743
+ return null;
28744
+ }
28745
+ }
28759
28746
  function parseWalletFromIdentity(identity) {
28760
28747
  if (typeof identity !== "string" || !identity.startsWith("user:")) return null;
28761
28748
  const addr = identity.slice("user:".length);
@@ -28783,7 +28770,8 @@ async function deliverToHermes(config, message, logger) {
28783
28770
  };
28784
28771
  if (token) headers.authorization = `Bearer ${token}`;
28785
28772
  const normalizedEndpoint = endpoint.replace(/^\/?/, "/");
28786
- const body = normalizedEndpoint.startsWith("/v1/chat/completions") ? { model: "hermes-agent", messages: [{ role: "user", content: message }], stream: false } : normalizedEndpoint.startsWith("/v1/runs") ? { input: message, session_id: sessionKey } : normalizedEndpoint.startsWith("/v1/responses") ? { model: "hermes-agent", input: message, store: true } : { sessionKey, message };
28773
+ const model = runtime.model?.trim();
28774
+ const body = normalizedEndpoint.startsWith("/v1/chat/completions") ? { ...model ? { model } : {}, messages: [{ role: "user", content: message }], stream: false } : normalizedEndpoint.startsWith("/v1/runs") ? { input: message, session_id: sessionKey } : normalizedEndpoint.startsWith("/v1/responses") ? { ...model ? { model } : {}, input: message, store: true } : { sessionKey, message };
28787
28775
  const response = await fetch(url, {
28788
28776
  method: "POST",
28789
28777
  headers,
@@ -29029,6 +29017,22 @@ function register(api) {
29029
29017
  "Message body:",
29030
29018
  frame.text
29031
29019
  ].filter(Boolean).join("\n");
29020
+ const directRunPrompt = [
29021
+ marker,
29022
+ `From: ${frame.from}`,
29023
+ `Conversation: ${frame.convId}`,
29024
+ frame.projectId ? `Canonical projectId: ${frame.projectId}` : "",
29025
+ "",
29026
+ "Reply to this PerkOS chat message using your current runtime configuration.",
29027
+ "Return only the reply text. Do not call messaging tools; the PerkOS plugin delivers the returned text.",
29028
+ "Use the recent context and do not ask again for information already provided.",
29029
+ "",
29030
+ "Recent conversation context (oldest to newest):",
29031
+ recentContext,
29032
+ "",
29033
+ "Message body:",
29034
+ frame.text
29035
+ ].filter(Boolean).join("\n");
29032
29036
  const runtimeKind = pluginConfig.runtime?.kind || "openclaw";
29033
29037
  if (runtimeKind === "hermes" || runtimeKind === "hermes-api") {
29034
29038
  try {
@@ -29042,14 +29046,38 @@ function register(api) {
29042
29046
  logger.info(`[perkos-chat] dropped chat_deliver for ${frame.convId} (runtime=none)`);
29043
29047
  return;
29044
29048
  }
29049
+ const chatSessionKey = openclawChatSessionKey(pluginConfig, frame.convId);
29050
+ await prepareOpenClawChatSession(api, pluginConfig, chatSessionKey, logger);
29051
+ if (walletAddress) {
29052
+ const reply = await runOpenClawChatTurn(
29053
+ api,
29054
+ pluginConfig,
29055
+ chatSessionKey,
29056
+ directRunPrompt,
29057
+ frame.text,
29058
+ logger
29059
+ );
29060
+ if (reply) {
29061
+ const result = await chatClient?.sendReply({
29062
+ convId: frame.convId,
29063
+ walletAddress,
29064
+ text: reply,
29065
+ replyTo: frame.id
29066
+ });
29067
+ logger.info(
29068
+ `[perkos-chat] embedded reply ${result?.delivered ? "delivered" : "queued (offline)"} for ${frame.convId}`
29069
+ );
29070
+ return;
29071
+ }
29072
+ }
29045
29073
  if (enqueueSystemEvent) {
29046
- enqueueSystemEvent(eventText, { sessionKey: openclawChatSessionKey(pluginConfig, frame.convId) });
29074
+ enqueueSystemEvent(eventText, { sessionKey: chatSessionKey });
29047
29075
  }
29048
29076
  wakeGatewayAgent(
29049
29077
  requestHeartbeatNow,
29050
29078
  `[PerkOS-Chat] inbound message in ${frame.convId}`,
29051
29079
  logger,
29052
- openclawChatSessionKey(pluginConfig, frame.convId)
29080
+ chatSessionKey
29053
29081
  );
29054
29082
  },
29055
29083
  onChannelJoin: async (frame) => {
@@ -29073,6 +29101,29 @@ function register(api) {
29073
29101
  }
29074
29102
  });
29075
29103
  }
29104
+ if (pluginConfig.platform?.heartbeatUrl) {
29105
+ let platformHeartbeat = null;
29106
+ api.registerService({
29107
+ id: "perkos-a2a-platform-heartbeat",
29108
+ start: () => {
29109
+ platformHeartbeat = startPlatformHeartbeat({
29110
+ logger: {
29111
+ info: (message) => logger.info(message),
29112
+ warn: (message) => (logger.warn || logger.info).call(logger, message)
29113
+ },
29114
+ url: pluginConfig.platform?.heartbeatUrl,
29115
+ relayKey: pluginConfig.relay?.apiKey,
29116
+ runtimeKind: "openclaw",
29117
+ version: "0.12.26",
29118
+ intervalMs: pluginConfig.platform?.heartbeatIntervalMs
29119
+ });
29120
+ },
29121
+ stop: () => {
29122
+ platformHeartbeat?.stop();
29123
+ platformHeartbeat = null;
29124
+ }
29125
+ });
29126
+ }
29076
29127
  api.registerTool({
29077
29128
  name: "perkos_a2a_send",
29078
29129
  description: "Send a task to another agent via PerkOS A2A. Use this to delegate work, ask a peer to research, or route a chain/circular workflow. The message may name additional agents and a final notification target; the bridge preserves the route and prevents loops.",
@@ -29454,11 +29505,15 @@ export {
29454
29505
  ChatStore,
29455
29506
  RelayClient,
29456
29507
  RelayHub,
29508
+ alignOpenClawChatSessionModel,
29457
29509
  register as default,
29458
29510
  detectNetworking,
29511
+ extractOpenClawChatReply,
29459
29512
  formatRecentChatContext,
29460
29513
  isWebhookEvent,
29461
- openclawChatSessionKey
29514
+ omitUnavailableAgentHarness,
29515
+ openclawChatSessionKey,
29516
+ runOpenClawChatTurn
29462
29517
  };
29463
29518
  /*! Bundled license information:
29464
29519