@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/README.md +29 -0
- package/dist/agent.js +3 -2
- package/dist/agent.js.map +1 -1
- package/dist/bridge-agent.js +0 -0
- package/dist/bridge-chat-reply.d.ts.map +1 -1
- package/dist/bridge-chat-reply.js +3 -2
- package/dist/bridge-chat-reply.js.map +1 -1
- package/dist/hermes-plugin.d.ts +2 -0
- package/dist/hermes-plugin.d.ts.map +1 -1
- package/dist/hermes-plugin.js +4 -2
- package/dist/hermes-plugin.js.map +1 -1
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +322 -267
- package/dist/index.js.map +4 -4
- package/dist/platform-heartbeat.d.ts +4 -0
- package/dist/platform-heartbeat.d.ts.map +1 -1
- package/dist/platform-heartbeat.js +5 -5
- package/dist/platform-heartbeat.js.map +1 -1
- package/dist/repair-cli.d.ts +3 -0
- package/dist/repair-cli.d.ts.map +1 -0
- package/dist/repair-cli.js +31 -0
- package/dist/repair-cli.js.map +1 -0
- package/dist/repair.d.ts +53 -0
- package/dist/repair.d.ts.map +1 -0
- package/dist/repair.js +357 -0
- package/dist/repair.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/openclaw.plugin.json +28 -1
- package/package.json +7 -5
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
29514
|
+
omitUnavailableAgentHarness,
|
|
29515
|
+
openclawChatSessionKey,
|
|
29516
|
+
runOpenClawChatTurn
|
|
29462
29517
|
};
|
|
29463
29518
|
/*! Bundled license information:
|
|
29464
29519
|
|