@perkos/perkos-a2a 0.12.18 → 0.12.22

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 (40) hide show
  1. package/README.md +31 -0
  2. package/dist/bridge-agent.js +25 -54
  3. package/dist/bridge-agent.js.map +1 -1
  4. package/dist/bridge-chat-reply.d.ts +14 -1
  5. package/dist/bridge-chat-reply.d.ts.map +1 -1
  6. package/dist/bridge-chat-reply.js +68 -3
  7. package/dist/bridge-chat-reply.js.map +1 -1
  8. package/dist/chat-types.d.ts +4 -0
  9. package/dist/chat-types.d.ts.map +1 -1
  10. package/dist/gateway-health.d.ts +5 -11
  11. package/dist/gateway-health.d.ts.map +1 -1
  12. package/dist/gateway-health.js +53 -15
  13. package/dist/gateway-health.js.map +1 -1
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +360 -20
  17. package/dist/index.js.map +4 -4
  18. package/dist/platform-heartbeat.d.ts +22 -0
  19. package/dist/platform-heartbeat.d.ts.map +1 -0
  20. package/dist/platform-heartbeat.js +72 -0
  21. package/dist/platform-heartbeat.js.map +1 -0
  22. package/dist/project-chat-context.d.ts +3 -0
  23. package/dist/project-chat-context.d.ts.map +1 -0
  24. package/dist/project-chat-context.js +16 -0
  25. package/dist/project-chat-context.js.map +1 -0
  26. package/dist/relay-client.d.ts +21 -2
  27. package/dist/relay-client.d.ts.map +1 -1
  28. package/dist/relay-client.js +82 -17
  29. package/dist/relay-client.js.map +1 -1
  30. package/dist/server.d.ts.map +1 -1
  31. package/dist/server.js +5 -2
  32. package/dist/server.js.map +1 -1
  33. package/dist/task-context.d.ts +18 -0
  34. package/dist/task-context.d.ts.map +1 -0
  35. package/dist/task-context.js +35 -0
  36. package/dist/task-context.js.map +1 -0
  37. package/dist/types.d.ts +4 -1
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/types.js.map +1 -1
  40. package/package.json +4 -2
package/dist/index.js CHANGED
@@ -22632,6 +22632,218 @@ 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
+
22635
22847
  // node_modules/ws/lib/buffer-util.js
22636
22848
  var require_buffer_util = __commonJS({
22637
22849
  "node_modules/ws/lib/buffer-util.js"(exports, module) {
@@ -22692,7 +22904,7 @@ var require_buffer_util = __commonJS({
22692
22904
  };
22693
22905
  if (!process.env.WS_NO_BUFFER_UTIL) {
22694
22906
  try {
22695
- const bufferUtil = __require("bufferutil");
22907
+ const bufferUtil = require_bufferutil();
22696
22908
  module.exports.mask = function(source, mask, output, offset, length) {
22697
22909
  if (length < 48) _mask(source, mask, output, offset, length);
22698
22910
  else bufferUtil.mask(source, mask, output, offset, length);
@@ -23140,6 +23352,55 @@ var require_permessage_deflate = __commonJS({
23140
23352
  }
23141
23353
  });
23142
23354
 
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
+
23143
23404
  // node_modules/ws/lib/validation.js
23144
23405
  var require_validation = __commonJS({
23145
23406
  "node_modules/ws/lib/validation.js"(exports, module) {
@@ -23331,7 +23592,7 @@ var require_validation = __commonJS({
23331
23592
  };
23332
23593
  } else if (!process.env.WS_NO_UTF_8_VALIDATE) {
23333
23594
  try {
23334
- const isValidUTF8 = __require("utf-8-validate");
23595
+ const isValidUTF8 = require_utf_8_validate();
23335
23596
  module.exports.isValidUTF8 = function(buf) {
23336
23597
  return buf.length < 32 ? _isValidUTF8(buf) : isValidUTF8(buf);
23337
23598
  };
@@ -26252,6 +26513,8 @@ import { randomUUID } from "crypto";
26252
26513
  var MIN_RECONNECT_MS = 1e3;
26253
26514
  var MAX_RECONNECT_MS = 6e4;
26254
26515
  var HEARTBEAT_INTERVAL_MS = 25e3;
26516
+ var ORPHAN_TTL_MS = 10 * 6e4;
26517
+ var ORPHAN_MAX = 256;
26255
26518
  var RelayClient = class {
26256
26519
  ws = null;
26257
26520
  options;
@@ -26262,6 +26525,8 @@ var RelayClient = class {
26262
26525
  connected = false;
26263
26526
  stopped = false;
26264
26527
  pendingCallbacks = /* @__PURE__ */ new Map();
26528
+ /** Responses that arrived with nobody waiting, claimable by a retry. */
26529
+ orphanResponses = /* @__PURE__ */ new Map();
26265
26530
  constructor(options) {
26266
26531
  this.options = options;
26267
26532
  this.logger = options.logger || { info: console.log, error: console.error };
@@ -26282,12 +26547,25 @@ var RelayClient = class {
26282
26547
  }
26283
26548
  this.connected = false;
26284
26549
  }
26285
- /** Send a task to another agent via the relay */
26286
- async sendTask(targetAgent, payload) {
26550
+ /**
26551
+ * Send a task to another agent via the relay.
26552
+ *
26553
+ * Pass `taskId` to retry a task that already went out: the relay dedups on
26554
+ * the id and will not run the work twice, and if the first attempt's response
26555
+ * arrived after we gave up it is claimed from the orphan buffer instead of
26556
+ * waiting again. Omit it for genuinely new work.
26557
+ */
26558
+ async sendTask(targetAgent, payload, taskId) {
26559
+ const id = taskId ?? randomUUID();
26560
+ const alreadyAnswered = this.claimOrphan(id);
26561
+ if (alreadyAnswered) {
26562
+ this.logger.info(`[perkos-a2a] Reusing buffered response for task ${id}`);
26563
+ return alreadyAnswered;
26564
+ }
26287
26565
  return this.sendAndWait({
26288
26566
  type: "task",
26289
26567
  to: targetAgent,
26290
- id: randomUUID(),
26568
+ id,
26291
26569
  from: this.options.agentName,
26292
26570
  payload,
26293
26571
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -26372,10 +26650,10 @@ var RelayClient = class {
26372
26650
  this.send(msg);
26373
26651
  }
26374
26652
  handleMessage(msg) {
26375
- const callback = this.pendingCallbacks.get(msg.id);
26376
- if (callback) {
26653
+ const pending = this.pendingCallbacks.get(msg.id);
26654
+ if (pending && (msg.type === pending.expectedType || msg.type === "error")) {
26377
26655
  this.pendingCallbacks.delete(msg.id);
26378
- callback(msg);
26656
+ pending.settle(msg);
26379
26657
  return;
26380
26658
  }
26381
26659
  switch (msg.type) {
@@ -26385,8 +26663,15 @@ var RelayClient = class {
26385
26663
  case "task":
26386
26664
  this.options.onTask(msg);
26387
26665
  break;
26666
+ case "task_ack":
26667
+ if (msg.payload?.deduplicated) {
26668
+ this.logger.info(`[perkos-a2a] Relay deduplicated task ${msg.id}; awaiting the original response`);
26669
+ }
26670
+ break;
26388
26671
  case "task_response":
26389
- this.logger.info(`[perkos-a2a] Received unmatched task response: ${msg.id}`);
26672
+ this.bufferOrphan(msg);
26673
+ this.logger.info(`[perkos-a2a] Buffered unmatched task response: ${msg.id}`);
26674
+ this.options.onOrphanTaskResponse?.(msg);
26390
26675
  break;
26391
26676
  case "discover_response":
26392
26677
  if (this.options.onDiscoverResponse) {
@@ -26407,18 +26692,39 @@ var RelayClient = class {
26407
26692
  this.ws.send(JSON.stringify(msg));
26408
26693
  }
26409
26694
  }
26695
+ /** Take a buffered response for `id`, if one arrived while nobody waited. */
26696
+ claimOrphan(id) {
26697
+ const entry = this.orphanResponses.get(id);
26698
+ if (!entry) return null;
26699
+ this.orphanResponses.delete(id);
26700
+ if (entry.expiresAt <= Date.now()) return null;
26701
+ return entry.msg;
26702
+ }
26703
+ bufferOrphan(msg) {
26704
+ const now = Date.now();
26705
+ for (const [id, entry] of this.orphanResponses) {
26706
+ if (entry.expiresAt <= now) this.orphanResponses.delete(id);
26707
+ }
26708
+ this.orphanResponses.set(msg.id, { msg, expiresAt: now + ORPHAN_TTL_MS });
26709
+ while (this.orphanResponses.size > ORPHAN_MAX) {
26710
+ this.orphanResponses.delete(this.orphanResponses.keys().next().value);
26711
+ }
26712
+ }
26410
26713
  async sendAndWait(msg, expectedType, timeoutMs) {
26411
26714
  return new Promise((resolve, reject) => {
26412
26715
  const timer = setTimeout(() => {
26413
26716
  this.pendingCallbacks.delete(msg.id);
26414
26717
  reject(new Error(`Relay request timed out after ${timeoutMs}ms`));
26415
26718
  }, timeoutMs);
26416
- this.pendingCallbacks.set(msg.id, (response) => {
26417
- clearTimeout(timer);
26418
- if (response.type === "error") {
26419
- reject(new Error(`Relay error: ${response.payload.message}`));
26420
- } else {
26421
- resolve(response);
26719
+ this.pendingCallbacks.set(msg.id, {
26720
+ expectedType,
26721
+ settle: (response) => {
26722
+ clearTimeout(timer);
26723
+ if (response.type === "error") {
26724
+ reject(new Error(`Relay error: ${response.payload.message}`));
26725
+ } else {
26726
+ resolve(response);
26727
+ }
26422
26728
  }
26423
26729
  });
26424
26730
  this.send(msg);
@@ -27034,6 +27340,7 @@ var A2AServer = class {
27034
27340
  messages: [message],
27035
27341
  artifacts: [],
27036
27342
  metadata: {
27343
+ ...message?.metadata,
27037
27344
  fromAgent: message?.metadata?.fromAgent || "unknown"
27038
27345
  },
27039
27346
  sessionKeyHint: "agent:main"
@@ -27281,9 +27588,9 @@ var A2AServer = class {
27281
27588
  const payload = msg.payload;
27282
27589
  const params = payload.params || payload;
27283
27590
  const rpcId = payload.id || msg.id;
27284
- const acceptQueued = params?.message?.metadata?.acceptQueued === true;
27285
27591
  this.handleSendMessage(params, rpcId).then(async (response) => {
27286
27592
  const task = response.result;
27593
+ const acceptQueued = task?.messages?.[0]?.metadata?.acceptQueued === true;
27287
27594
  if (task?.id && !isTerminalTaskState(task.status?.state)) {
27288
27595
  if (acceptQueued) {
27289
27596
  this.relayClient?.sendTaskResponse(
@@ -27301,7 +27608,12 @@ var A2AServer = class {
27301
27608
  return;
27302
27609
  }
27303
27610
  }
27304
- this.relayClient?.sendTaskResponse(msg, response);
27611
+ if (!acceptQueued) {
27612
+ this.relayClient?.sendTaskResponse(
27613
+ msg,
27614
+ response
27615
+ );
27616
+ }
27305
27617
  }).catch((err) => {
27306
27618
  const errMsg2 = err instanceof Error ? err.message : String(err);
27307
27619
  this.logger.error(`[perkos-a2a] Failed to process relay task: ${errMsg2}`);
@@ -28439,11 +28751,24 @@ function openclawEventSessionKey(config) {
28439
28751
  if (!key) return "agent:main:main";
28440
28752
  return key.endsWith(":main") ? key : `${key}:main`;
28441
28753
  }
28754
+ function openclawChatSessionKey(config, convId) {
28755
+ const configured = config.runtime?.sessionKey || "agent:main";
28756
+ const safe = convId.replace(/[^a-zA-Z0-9_.-]/g, "-").slice(0, 120) || "unknown";
28757
+ return `${configured}:perkos-chat-${safe}`;
28758
+ }
28442
28759
  function parseWalletFromIdentity(identity) {
28443
28760
  if (typeof identity !== "string" || !identity.startsWith("user:")) return null;
28444
28761
  const addr = identity.slice("user:".length);
28445
28762
  return addr.startsWith("0x") ? addr.toLowerCase() : null;
28446
28763
  }
28764
+ var CHAT_CONTEXT_MESSAGE_LIMIT = 12;
28765
+ var CHAT_CONTEXT_CHAR_LIMIT = 6e3;
28766
+ function formatRecentChatContext(messages, currentMessageId) {
28767
+ const prior = messages.filter((message) => message.id !== currentMessageId).slice(-CHAT_CONTEXT_MESSAGE_LIMIT).map((message) => `${message.from}: ${message.text.trim()}`).filter((line) => line.length > 0);
28768
+ if (prior.length === 0) return "(No earlier messages in this conversation.)";
28769
+ const transcript = prior.join("\n");
28770
+ return transcript.length <= CHAT_CONTEXT_CHAR_LIMIT ? transcript : `\u2026${transcript.slice(-CHAT_CONTEXT_CHAR_LIMIT)}`;
28771
+ }
28447
28772
  async function deliverToHermes(config, message, logger) {
28448
28773
  const runtime = config.runtime || {};
28449
28774
  const baseUrl = (runtime.hermesUrl || "http://127.0.0.1:8642").replace(/\/+$/, "");
@@ -28676,10 +29001,18 @@ function register(api) {
28676
29001
  onChatDeliver: async (frame) => {
28677
29002
  const walletAddress = parseWalletFromIdentity(frame.from);
28678
29003
  const marker = `[PERKOS_CHAT:${frame.convId}]`;
29004
+ const recentPage = await chatClient?.getStore().readPage(frame.convId, {
29005
+ limit: CHAT_CONTEXT_MESSAGE_LIMIT + 1
29006
+ });
29007
+ const recentContext = formatRecentChatContext(
29008
+ recentPage?.messages ?? [],
29009
+ frame.id
29010
+ );
28679
29011
  const eventText = [
28680
29012
  marker,
28681
29013
  `From: ${frame.from}`,
28682
29014
  `Conversation: ${frame.convId}`,
29015
+ frame.projectId ? `Canonical projectId: ${frame.projectId}` : "",
28683
29016
  walletAddress ? `Wallet: ${walletAddress}` : "",
28684
29017
  "",
28685
29018
  "A user is messaging you in a PerkOS chat conversation.",
@@ -28688,6 +29021,11 @@ function register(api) {
28688
29021
  walletAddress ? ` walletAddress: "${walletAddress}"` : ` walletAddress: <derived from From>`,
28689
29022
  ` text: <your reply>`,
28690
29023
  "",
29024
+ "Recent conversation context (oldest to newest):",
29025
+ recentContext,
29026
+ "",
29027
+ "Use the recent context. Do not ask again for information the user already provided.",
29028
+ "",
28691
29029
  "Message body:",
28692
29030
  frame.text
28693
29031
  ].filter(Boolean).join("\n");
@@ -28705,13 +29043,13 @@ function register(api) {
28705
29043
  return;
28706
29044
  }
28707
29045
  if (enqueueSystemEvent) {
28708
- enqueueSystemEvent(eventText, { sessionKey: openclawEventSessionKey(pluginConfig) });
29046
+ enqueueSystemEvent(eventText, { sessionKey: openclawChatSessionKey(pluginConfig, frame.convId) });
28709
29047
  }
28710
29048
  wakeGatewayAgent(
28711
29049
  requestHeartbeatNow,
28712
29050
  `[PerkOS-Chat] inbound message in ${frame.convId}`,
28713
29051
  logger,
28714
- openclawWakeSessionKey(pluginConfig)
29052
+ openclawChatSessionKey(pluginConfig, frame.convId)
28715
29053
  );
28716
29054
  },
28717
29055
  onChannelJoin: async (frame) => {
@@ -29118,7 +29456,9 @@ export {
29118
29456
  RelayHub,
29119
29457
  register as default,
29120
29458
  detectNetworking,
29121
- isWebhookEvent
29459
+ formatRecentChatContext,
29460
+ isWebhookEvent,
29461
+ openclawChatSessionKey
29122
29462
  };
29123
29463
  /*! Bundled license information:
29124
29464