@dimcool/dimclaw 0.1.13 → 0.1.17

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/dim-client.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  * Handles wallet-based authentication using a Solana keypair.
4
4
  */
5
5
 
6
+ import { mkdir, writeFile, rename } from 'node:fs/promises';
7
+ import path from 'node:path';
6
8
  import { SDK, NodeStorage } from '@dimcool/sdk';
7
9
  import { Keypair, Transaction } from '@solana/web3.js';
8
10
  import bs58 from 'bs58';
@@ -15,6 +17,14 @@ export interface DimClientConfig {
15
17
  apiUrl?: string;
16
18
  /** Referral code to use on first signup */
17
19
  referralCode?: string;
20
+ /** Path to HEARTBEAT.md for OpenClaw's heartbeat cycle */
21
+ heartbeatPath?: string;
22
+ }
23
+
24
+ export interface BufferedEvent {
25
+ event: string;
26
+ payload: unknown;
27
+ at: string;
18
28
  }
19
29
 
20
30
  export class DimClient {
@@ -23,6 +33,8 @@ export class DimClient {
23
33
  private config: DimClientConfig;
24
34
  private authenticated = false;
25
35
  private userId: string | null = null;
36
+ private eventQueue: BufferedEvent[] = [];
37
+ private unsubscribers: Array<() => void> = [];
26
38
 
27
39
  constructor(config: DimClientConfig) {
28
40
  this.config = config;
@@ -96,6 +108,83 @@ export class DimClient {
96
108
  await this.sdk.ensureWebSocketConnected(timeoutMs);
97
109
  }
98
110
 
111
+ /**
112
+ * Subscribe to key WS events and buffer them for agent consumption.
113
+ * Call after authenticate() when the WS transport is connected.
114
+ */
115
+ startEventListeners(): void {
116
+ const events = [
117
+ 'chat:message',
118
+ 'notification',
119
+ 'lobby:matched',
120
+ 'lobby:invitation',
121
+ 'game:turn',
122
+ 'game:completed',
123
+ ];
124
+ for (const event of events) {
125
+ this.unsubscribers.push(
126
+ this.sdk.events.subscribe(event, (payload: unknown) => {
127
+ this.eventQueue.push({
128
+ event,
129
+ payload,
130
+ at: new Date().toISOString(),
131
+ });
132
+ this.writeHeartbeat().catch(() => {});
133
+ }),
134
+ );
135
+ }
136
+ }
137
+
138
+ /** Write HEARTBEAT.md with pending event summary for OpenClaw's heartbeat cycle. */
139
+ private async writeHeartbeat(): Promise<void> {
140
+ if (!this.config.heartbeatPath) return;
141
+ const filePath = this.resolveHeartbeatPath();
142
+ const count = this.eventQueue.length;
143
+ if (count === 0) return;
144
+ const lines = ['# DIM Heartbeat', ''];
145
+ const eventTypes = new Set(this.eventQueue.map((e) => e.event));
146
+ if (eventTypes.has('chat:message')) {
147
+ lines.push('- You have new DMs — call dim_check_notifications');
148
+ }
149
+ if (eventTypes.has('notification')) {
150
+ lines.push(
151
+ '- New notifications (challenges, friend requests, game results) — call dim_check_notifications',
152
+ );
153
+ }
154
+ if (eventTypes.has('lobby:matched') || eventTypes.has('lobby:invitation')) {
155
+ lines.push('- A game match is ready — call dim_get_pending_events');
156
+ }
157
+ if (eventTypes.has('game:turn')) {
158
+ lines.push("- It's your turn in a game — call dim_get_pending_events");
159
+ }
160
+ if (eventTypes.has('game:completed')) {
161
+ lines.push('- A game has completed — call dim_get_pending_events');
162
+ }
163
+ lines.push('');
164
+ await mkdir(path.dirname(filePath), { recursive: true });
165
+ const tmp = `${filePath}.tmp`;
166
+ await writeFile(tmp, lines.join('\n'), 'utf8');
167
+ await rename(tmp, filePath);
168
+ }
169
+
170
+ private resolveHeartbeatPath(): string {
171
+ const p = this.config.heartbeatPath!;
172
+ if (p.startsWith('~/') && process.env.HOME) {
173
+ return path.join(process.env.HOME, p.slice(2));
174
+ }
175
+ return path.resolve(p);
176
+ }
177
+
178
+ /** Drain all buffered events since last call. */
179
+ drainEvents(): BufferedEvent[] {
180
+ return this.eventQueue.splice(0);
181
+ }
182
+
183
+ /** Peek at buffered event count without draining. */
184
+ get pendingEventCount(): number {
185
+ return this.eventQueue.length;
186
+ }
187
+
99
188
  getKeypair(): Keypair {
100
189
  return this.keypair;
101
190
  }
package/dist/index.js CHANGED
@@ -6513,14 +6513,14 @@ var require_url_state_machine = __commonJS({
6513
6513
  return url3.replace(/\u0009|\u000A|\u000D/g, "");
6514
6514
  }
6515
6515
  function shortenPath(url3) {
6516
- const path2 = url3.path;
6517
- if (path2.length === 0) {
6516
+ const path3 = url3.path;
6517
+ if (path3.length === 0) {
6518
6518
  return;
6519
6519
  }
6520
- if (url3.scheme === "file" && path2.length === 1 && isNormalizedWindowsDriveLetter(path2[0])) {
6520
+ if (url3.scheme === "file" && path3.length === 1 && isNormalizedWindowsDriveLetter(path3[0])) {
6521
6521
  return;
6522
6522
  }
6523
- path2.pop();
6523
+ path3.pop();
6524
6524
  }
6525
6525
  function includesCredentials(url3) {
6526
6526
  return url3.username !== "" || url3.password !== "";
@@ -7577,7 +7577,7 @@ var require_node_gyp_build = __commonJS({
7577
7577
  "../../node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
7578
7578
  "use strict";
7579
7579
  var fs = __require("fs");
7580
- var path2 = __require("path");
7580
+ var path3 = __require("path");
7581
7581
  var os = __require("os");
7582
7582
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
7583
7583
  var vars = process.config && process.config.variables || {};
@@ -7594,21 +7594,21 @@ var require_node_gyp_build = __commonJS({
7594
7594
  return runtimeRequire(load.resolve(dir));
7595
7595
  }
7596
7596
  load.resolve = load.path = function(dir) {
7597
- dir = path2.resolve(dir || ".");
7597
+ dir = path3.resolve(dir || ".");
7598
7598
  try {
7599
- var name = runtimeRequire(path2.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
7599
+ var name = runtimeRequire(path3.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
7600
7600
  if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
7601
7601
  } catch (err) {
7602
7602
  }
7603
7603
  if (!prebuildsOnly) {
7604
- var release = getFirst(path2.join(dir, "build/Release"), matchBuild);
7604
+ var release = getFirst(path3.join(dir, "build/Release"), matchBuild);
7605
7605
  if (release) return release;
7606
- var debug12 = getFirst(path2.join(dir, "build/Debug"), matchBuild);
7606
+ var debug12 = getFirst(path3.join(dir, "build/Debug"), matchBuild);
7607
7607
  if (debug12) return debug12;
7608
7608
  }
7609
7609
  var prebuild = resolve(dir);
7610
7610
  if (prebuild) return prebuild;
7611
- var nearby = resolve(path2.dirname(process.execPath));
7611
+ var nearby = resolve(path3.dirname(process.execPath));
7612
7612
  if (nearby) return nearby;
7613
7613
  var target = [
7614
7614
  "platform=" + platform,
@@ -7625,14 +7625,14 @@ var require_node_gyp_build = __commonJS({
7625
7625
  ].filter(Boolean).join(" ");
7626
7626
  throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
7627
7627
  function resolve(dir2) {
7628
- var tuples = readdirSync(path2.join(dir2, "prebuilds")).map(parseTuple);
7628
+ var tuples = readdirSync(path3.join(dir2, "prebuilds")).map(parseTuple);
7629
7629
  var tuple3 = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
7630
7630
  if (!tuple3) return;
7631
- var prebuilds = path2.join(dir2, "prebuilds", tuple3.name);
7631
+ var prebuilds = path3.join(dir2, "prebuilds", tuple3.name);
7632
7632
  var parsed = readdirSync(prebuilds).map(parseTags);
7633
7633
  var candidates = parsed.filter(matchTags(runtime, abi));
7634
7634
  var winner = candidates.sort(compareTags(runtime))[0];
7635
- if (winner) return path2.join(prebuilds, winner.file);
7635
+ if (winner) return path3.join(prebuilds, winner.file);
7636
7636
  }
7637
7637
  };
7638
7638
  function readdirSync(dir) {
@@ -7644,7 +7644,7 @@ var require_node_gyp_build = __commonJS({
7644
7644
  }
7645
7645
  function getFirst(dir, filter) {
7646
7646
  var files = readdirSync(dir).filter(filter);
7647
- return files[0] && path2.join(dir, files[0]);
7647
+ return files[0] && path3.join(dir, files[0]);
7648
7648
  }
7649
7649
  function matchBuild(name) {
7650
7650
  return /\.node$/.test(name);
@@ -18589,8 +18589,8 @@ var require_nacl_fast = __commonJS({
18589
18589
  });
18590
18590
 
18591
18591
  // index.ts
18592
- import { mkdir, readFile, rename, writeFile } from "fs/promises";
18593
- import path from "path";
18592
+ import { mkdir as mkdir2, readFile, rename as rename2, writeFile as writeFile2 } from "fs/promises";
18593
+ import path2 from "path";
18594
18594
 
18595
18595
  // ../../node_modules/@solana/web3.js/lib/index.esm.js
18596
18596
  import { Buffer as Buffer2 } from "buffer";
@@ -21589,8 +21589,8 @@ var StructError = class extends TypeError {
21589
21589
  constructor(failure, failures) {
21590
21590
  let cached;
21591
21591
  const { message, explanation, ...rest } = failure;
21592
- const { path: path2 } = failure;
21593
- const msg = path2.length === 0 ? message : `At path: ${path2.join(".")} -- ${message}`;
21592
+ const { path: path3 } = failure;
21593
+ const msg = path3.length === 0 ? message : `At path: ${path3.join(".")} -- ${message}`;
21594
21594
  super(explanation ?? msg);
21595
21595
  if (explanation != null)
21596
21596
  this.cause = msg;
@@ -21628,15 +21628,15 @@ function toFailure(result, context, struct2, value2) {
21628
21628
  } else if (typeof result === "string") {
21629
21629
  result = { message: result };
21630
21630
  }
21631
- const { path: path2, branch } = context;
21631
+ const { path: path3, branch } = context;
21632
21632
  const { type: type3 } = struct2;
21633
21633
  const { refinement, message = `Expected a value of type \`${type3}\`${refinement ? ` with refinement \`${refinement}\`` : ""}, but received: \`${print(value2)}\`` } = result;
21634
21634
  return {
21635
21635
  value: value2,
21636
21636
  type: type3,
21637
21637
  refinement,
21638
- key: path2[path2.length - 1],
21639
- path: path2,
21638
+ key: path3[path3.length - 1],
21639
+ path: path3,
21640
21640
  branch,
21641
21641
  ...result,
21642
21642
  message
@@ -21654,8 +21654,8 @@ function* toFailures(result, context, struct2, value2) {
21654
21654
  }
21655
21655
  }
21656
21656
  function* run(value2, struct2, options = {}) {
21657
- const { path: path2 = [], branch = [value2], coerce: coerce3 = false, mask: mask3 = false } = options;
21658
- const ctx = { path: path2, branch, mask: mask3 };
21657
+ const { path: path3 = [], branch = [value2], coerce: coerce3 = false, mask: mask3 = false } = options;
21658
+ const ctx = { path: path3, branch, mask: mask3 };
21659
21659
  if (coerce3) {
21660
21660
  value2 = struct2.coercer(value2, ctx);
21661
21661
  }
@@ -21667,7 +21667,7 @@ function* run(value2, struct2, options = {}) {
21667
21667
  }
21668
21668
  for (let [k, v, s] of struct2.entries(value2, ctx)) {
21669
21669
  const ts = run(v, s, {
21670
- path: k === void 0 ? path2 : [...path2, k],
21670
+ path: k === void 0 ? path3 : [...path3, k],
21671
21671
  branch: k === void 0 ? branch : [...branch, v],
21672
21672
  coerce: coerce3,
21673
21673
  mask: mask3,
@@ -28583,6 +28583,10 @@ var esm_default = base;
28583
28583
  var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
28584
28584
  var esm_default2 = esm_default(ALPHABET);
28585
28585
 
28586
+ // dim-client.ts
28587
+ import { mkdir, writeFile, rename } from "fs/promises";
28588
+ import path from "path";
28589
+
28586
28590
  // ../sdk/dist/index.js
28587
28591
  import crypto22 from "crypto";
28588
28592
  import crypto32 from "crypto";
@@ -29836,12 +29840,12 @@ function parse3(str) {
29836
29840
  uri.queryKey = queryKey(uri, uri["query"]);
29837
29841
  return uri;
29838
29842
  }
29839
- function pathNames(obj, path2) {
29840
- const regx = /\/{2,9}/g, names = path2.replace(regx, "/").split("/");
29841
- if (path2.slice(0, 1) == "/" || path2.length === 0) {
29843
+ function pathNames(obj, path3) {
29844
+ const regx = /\/{2,9}/g, names = path3.replace(regx, "/").split("/");
29845
+ if (path3.slice(0, 1) == "/" || path3.length === 0) {
29842
29846
  names.splice(0, 1);
29843
29847
  }
29844
- if (path2.slice(-1) == "/") {
29848
+ if (path3.slice(-1) == "/") {
29845
29849
  names.splice(names.length - 1, 1);
29846
29850
  }
29847
29851
  return names;
@@ -30458,7 +30462,7 @@ var protocol2 = Socket.protocol;
30458
30462
  // ../../node_modules/socket.io-client/build/esm-debug/url.js
30459
30463
  var import_debug7 = __toESM(require_src2(), 1);
30460
30464
  var debug7 = (0, import_debug7.default)("socket.io-client:url");
30461
- function url2(uri, path2 = "", loc) {
30465
+ function url2(uri, path3 = "", loc) {
30462
30466
  let obj = uri;
30463
30467
  loc = loc || typeof location !== "undefined" && location;
30464
30468
  if (null == uri)
@@ -30492,7 +30496,7 @@ function url2(uri, path2 = "", loc) {
30492
30496
  obj.path = obj.path || "/";
30493
30497
  const ipv6 = obj.host.indexOf(":") !== -1;
30494
30498
  const host = ipv6 ? "[" + obj.host + "]" : obj.host;
30495
- obj.id = obj.protocol + "://" + host + ":" + obj.port + path2;
30499
+ obj.id = obj.protocol + "://" + host + ":" + obj.port + path3;
30496
30500
  obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
30497
30501
  return obj;
30498
30502
  }
@@ -32116,8 +32120,8 @@ function lookup(uri, opts) {
32116
32120
  const parsed = url2(uri, opts.path || "/socket.io");
32117
32121
  const source = parsed.source;
32118
32122
  const id = parsed.id;
32119
- const path2 = parsed.path;
32120
- const sameNamespace = cache[id] && path2 in cache[id]["nsps"];
32123
+ const path3 = parsed.path;
32124
+ const sameNamespace = cache[id] && path3 in cache[id]["nsps"];
32121
32125
  const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
32122
32126
  let io;
32123
32127
  if (newConnection) {
@@ -38664,14 +38668,14 @@ var require_url_state_machine2 = __commonJS2({
38664
38668
  return url22.replace(/\u0009|\u000A|\u000D/g, "");
38665
38669
  }
38666
38670
  function shortenPath(url22) {
38667
- const path2 = url22.path;
38668
- if (path2.length === 0) {
38671
+ const path3 = url22.path;
38672
+ if (path3.length === 0) {
38669
38673
  return;
38670
38674
  }
38671
- if (url22.scheme === "file" && path2.length === 1 && isNormalizedWindowsDriveLetter(path2[0])) {
38675
+ if (url22.scheme === "file" && path3.length === 1 && isNormalizedWindowsDriveLetter(path3[0])) {
38672
38676
  return;
38673
38677
  }
38674
- path2.pop();
38678
+ path3.pop();
38675
38679
  }
38676
38680
  function includesCredentials(url22) {
38677
38681
  return url22.username !== "" || url22.password !== "";
@@ -39718,7 +39722,7 @@ var require_node_gyp_build3 = __commonJS2({
39718
39722
  "../../node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
39719
39723
  "use strict";
39720
39724
  var fs = __require2("fs");
39721
- var path2 = __require2("path");
39725
+ var path3 = __require2("path");
39722
39726
  var os = __require2("os");
39723
39727
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require2;
39724
39728
  var vars = process.config && process.config.variables || {};
@@ -39735,21 +39739,21 @@ var require_node_gyp_build3 = __commonJS2({
39735
39739
  return runtimeRequire(load.resolve(dir));
39736
39740
  }
39737
39741
  load.resolve = load.path = function(dir) {
39738
- dir = path2.resolve(dir || ".");
39742
+ dir = path3.resolve(dir || ".");
39739
39743
  try {
39740
- var name = runtimeRequire(path2.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
39744
+ var name = runtimeRequire(path3.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
39741
39745
  if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
39742
39746
  } catch (err) {
39743
39747
  }
39744
39748
  if (!prebuildsOnly) {
39745
- var release = getFirst(path2.join(dir, "build/Release"), matchBuild);
39749
+ var release = getFirst(path3.join(dir, "build/Release"), matchBuild);
39746
39750
  if (release) return release;
39747
- var debug12 = getFirst(path2.join(dir, "build/Debug"), matchBuild);
39751
+ var debug12 = getFirst(path3.join(dir, "build/Debug"), matchBuild);
39748
39752
  if (debug12) return debug12;
39749
39753
  }
39750
39754
  var prebuild = resolve(dir);
39751
39755
  if (prebuild) return prebuild;
39752
- var nearby = resolve(path2.dirname(process.execPath));
39756
+ var nearby = resolve(path3.dirname(process.execPath));
39753
39757
  if (nearby) return nearby;
39754
39758
  var target = [
39755
39759
  "platform=" + platform,
@@ -39766,14 +39770,14 @@ var require_node_gyp_build3 = __commonJS2({
39766
39770
  ].filter(Boolean).join(" ");
39767
39771
  throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
39768
39772
  function resolve(dir2) {
39769
- var tuples = readdirSync(path2.join(dir2, "prebuilds")).map(parseTuple);
39773
+ var tuples = readdirSync(path3.join(dir2, "prebuilds")).map(parseTuple);
39770
39774
  var tuple22 = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
39771
39775
  if (!tuple22) return;
39772
- var prebuilds = path2.join(dir2, "prebuilds", tuple22.name);
39776
+ var prebuilds = path3.join(dir2, "prebuilds", tuple22.name);
39773
39777
  var parsed = readdirSync(prebuilds).map(parseTags);
39774
39778
  var candidates = parsed.filter(matchTags(runtime, abi));
39775
39779
  var winner = candidates.sort(compareTags(runtime))[0];
39776
- if (winner) return path2.join(prebuilds, winner.file);
39780
+ if (winner) return path3.join(prebuilds, winner.file);
39777
39781
  }
39778
39782
  };
39779
39783
  function readdirSync(dir) {
@@ -39785,7 +39789,7 @@ var require_node_gyp_build3 = __commonJS2({
39785
39789
  }
39786
39790
  function getFirst(dir, filter) {
39787
39791
  var files = readdirSync(dir).filter(filter);
39788
- return files[0] && path2.join(dir, files[0]);
39792
+ return files[0] && path3.join(dir, files[0]);
39789
39793
  }
39790
39794
  function matchBuild(name) {
39791
39795
  return /\.node$/.test(name);
@@ -46870,8 +46874,8 @@ var StructError2 = class extends TypeError {
46870
46874
  constructor(failure, failures) {
46871
46875
  let cached;
46872
46876
  const { message, explanation, ...rest } = failure;
46873
- const { path: path2 } = failure;
46874
- const msg = path2.length === 0 ? message : `At path: ${path2.join(".")} -- ${message}`;
46877
+ const { path: path3 } = failure;
46878
+ const msg = path3.length === 0 ? message : `At path: ${path3.join(".")} -- ${message}`;
46875
46879
  super(explanation ?? msg);
46876
46880
  if (explanation != null)
46877
46881
  this.cause = msg;
@@ -46909,15 +46913,15 @@ function toFailure2(result, context, struct2, value2) {
46909
46913
  } else if (typeof result === "string") {
46910
46914
  result = { message: result };
46911
46915
  }
46912
- const { path: path2, branch } = context;
46916
+ const { path: path3, branch } = context;
46913
46917
  const { type: type22 } = struct2;
46914
46918
  const { refinement, message = `Expected a value of type \`${type22}\`${refinement ? ` with refinement \`${refinement}\`` : ""}, but received: \`${print2(value2)}\`` } = result;
46915
46919
  return {
46916
46920
  value: value2,
46917
46921
  type: type22,
46918
46922
  refinement,
46919
- key: path2[path2.length - 1],
46920
- path: path2,
46923
+ key: path3[path3.length - 1],
46924
+ path: path3,
46921
46925
  branch,
46922
46926
  ...result,
46923
46927
  message
@@ -46935,8 +46939,8 @@ function* toFailures2(result, context, struct2, value2) {
46935
46939
  }
46936
46940
  }
46937
46941
  function* run2(value2, struct2, options = {}) {
46938
- const { path: path2 = [], branch = [value2], coerce: coerce22 = false, mask: mask22 = false } = options;
46939
- const ctx = { path: path2, branch, mask: mask22 };
46942
+ const { path: path3 = [], branch = [value2], coerce: coerce22 = false, mask: mask22 = false } = options;
46943
+ const ctx = { path: path3, branch, mask: mask22 };
46940
46944
  if (coerce22) {
46941
46945
  value2 = struct2.coercer(value2, ctx);
46942
46946
  }
@@ -46948,7 +46952,7 @@ function* run2(value2, struct2, options = {}) {
46948
46952
  }
46949
46953
  for (let [k, v, s] of struct2.entries(value2, ctx)) {
46950
46954
  const ts = run2(v, s, {
46951
- path: k === void 0 ? path2 : [...path2, k],
46955
+ path: k === void 0 ? path3 : [...path3, k],
46952
46956
  branch: k === void 0 ? branch : [...branch, v],
46953
46957
  coerce: coerce22,
46954
46958
  mask: mask22,
@@ -53700,6 +53704,27 @@ var VoteAccountLayout2 = BufferLayout2.struct([
53700
53704
  BufferLayout2.seq(BufferLayout2.struct([BufferLayout2.nu64("epoch"), BufferLayout2.nu64("credits"), BufferLayout2.nu64("prevCredits")]), BufferLayout2.offset(BufferLayout2.u32(), -8), "epochCredits"),
53701
53705
  BufferLayout2.struct([BufferLayout2.nu64("slot"), BufferLayout2.nu64("timestamp")], "lastTimestamp")
53702
53706
  ]);
53707
+ function base64ToBytes(base64) {
53708
+ if (typeof Buffer !== "undefined") {
53709
+ return Buffer.from(base64, "base64");
53710
+ }
53711
+ const binary = atob(base64);
53712
+ const bytes = new Uint8Array(binary.length);
53713
+ for (let i = 0; i < binary.length; i++) {
53714
+ bytes[i] = binary.charCodeAt(i);
53715
+ }
53716
+ return bytes;
53717
+ }
53718
+ function bytesToBase64(bytes) {
53719
+ if (typeof Buffer !== "undefined") {
53720
+ return Buffer.from(bytes).toString("base64");
53721
+ }
53722
+ let binary = "";
53723
+ for (let i = 0; i < bytes.length; i++) {
53724
+ binary += String.fromCharCode(bytes[i]);
53725
+ }
53726
+ return btoa(binary);
53727
+ }
53703
53728
  var HttpClient = class {
53704
53729
  constructor(baseUrl, storage, logger2, appId, autoPayConfig) {
53705
53730
  this.baseUrl = baseUrl.replace(/\/$/, "");
@@ -53971,11 +53996,11 @@ var HttpClient = class {
53971
53996
  }
53972
53997
  }
53973
53998
  );
53974
- const unsignedTx = Transaction2.from(
53975
- Buffer.from(prepare.transaction, "base64")
53976
- );
53999
+ const unsignedTx = Transaction2.from(base64ToBytes(prepare.transaction));
53977
54000
  const signedTx = await this.signTransactionHandler(unsignedTx);
53978
- const signedTransaction = signedTx.serialize({ requireAllSignatures: false }).toString("base64");
54001
+ const signedTransaction = bytesToBase64(
54002
+ signedTx.serialize({ requireAllSignatures: false })
54003
+ );
53979
54004
  const submitted = await this.post(
53980
54005
  "/payments/submit",
53981
54006
  {
@@ -53989,19 +54014,6 @@ var HttpClient = class {
53989
54014
  return submitted.paymentProof;
53990
54015
  }
53991
54016
  };
53992
- function toBase64(bytes) {
53993
- if (typeof Buffer !== "undefined") {
53994
- return Buffer.from(bytes).toString("base64");
53995
- }
53996
- let binary = "";
53997
- for (let i = 0; i < bytes.length; i++) {
53998
- binary += String.fromCharCode(bytes[i]);
53999
- }
54000
- if (typeof btoa === "undefined") {
54001
- throw new Error("Base64 encoding is not available in this environment");
54002
- }
54003
- return btoa(binary);
54004
- }
54005
54017
  var Auth = class {
54006
54018
  constructor(http22, storage, wallet, logger2) {
54007
54019
  this.http = http22;
@@ -54066,7 +54078,7 @@ var Auth = class {
54066
54078
  }
54067
54079
  const { message } = await this.generateHandshake(address);
54068
54080
  const signatureResult = await this.wallet.signMessage(message);
54069
- const signedMessage = typeof signatureResult === "string" ? signatureResult : toBase64(signatureResult);
54081
+ const signedMessage = typeof signatureResult === "string" ? signatureResult : bytesToBase64(signatureResult);
54070
54082
  this.logger.debug("Auth.loginWithWallet called", {
54071
54083
  address,
54072
54084
  walletMeta: options?.walletMeta
@@ -54540,11 +54552,11 @@ var Games = class {
54540
54552
  );
54541
54553
  }
54542
54554
  const prepared = await this.prepareGameDonation(gameId, amountMinor);
54543
- const unsignedTx = Transaction2.from(
54544
- Buffer.from(prepared.transaction, "base64")
54545
- );
54555
+ const unsignedTx = Transaction2.from(base64ToBytes(prepared.transaction));
54546
54556
  const signedTx = await this.wallet.signTransaction(unsignedTx);
54547
- const signedTransaction = signedTx.serialize({ requireAllSignatures: false }).toString("base64");
54557
+ const signedTransaction = bytesToBase64(
54558
+ signedTx.serialize({ requireAllSignatures: false })
54559
+ );
54548
54560
  const result = await this.donateToGame(
54549
54561
  gameId,
54550
54562
  amountMinor,
@@ -54812,11 +54824,11 @@ var Tips = class {
54812
54824
  }
54813
54825
  const { publicKey: senderAddress } = await this.wallet.loadWallet();
54814
54826
  const prepared = await this.prepare({ recipientUsername, amount });
54815
- const unsignedTx = Transaction2.from(
54816
- Buffer.from(prepared.transaction, "base64")
54817
- );
54827
+ const unsignedTx = Transaction2.from(base64ToBytes(prepared.transaction));
54818
54828
  const signedTx = await this.wallet.signTransaction(unsignedTx);
54819
- const signedTxBase64 = signedTx.serialize({ requireAllSignatures: false }).toString("base64");
54829
+ const signedTxBase64 = bytesToBase64(
54830
+ signedTx.serialize({ requireAllSignatures: false })
54831
+ );
54820
54832
  const transfer = await this.wallet.submitTransfer(
54821
54833
  signedTxBase64,
54822
54834
  senderAddress,
@@ -55181,11 +55193,11 @@ var Wallet = class {
55181
55193
  amount,
55182
55194
  token
55183
55195
  );
55184
- const unsignedTx = Transaction2.from(
55185
- Buffer.from(prepared.transaction, "base64")
55186
- );
55196
+ const unsignedTx = Transaction2.from(base64ToBytes(prepared.transaction));
55187
55197
  const signedTx = await this.signTransaction(unsignedTx);
55188
- const signedTxBase64 = signedTx.serialize({ requireAllSignatures: false }).toString("base64");
55198
+ const signedTxBase64 = bytesToBase64(
55199
+ signedTx.serialize({ requireAllSignatures: false })
55200
+ );
55189
55201
  const submitted = await this.submitTransfer(
55190
55202
  signedTxBase64,
55191
55203
  senderAddress,
@@ -57297,6 +57309,8 @@ var DimClient = class {
57297
57309
  config;
57298
57310
  authenticated = false;
57299
57311
  userId = null;
57312
+ eventQueue = [];
57313
+ unsubscribers = [];
57300
57314
  constructor(config) {
57301
57315
  this.config = config;
57302
57316
  const secretKeyBytes = esm_default2.decode(config.walletPrivateKey);
@@ -57352,6 +57366,79 @@ var DimClient = class {
57352
57366
  async ensureConnected(timeoutMs = 1e4) {
57353
57367
  await this.sdk.ensureWebSocketConnected(timeoutMs);
57354
57368
  }
57369
+ /**
57370
+ * Subscribe to key WS events and buffer them for agent consumption.
57371
+ * Call after authenticate() when the WS transport is connected.
57372
+ */
57373
+ startEventListeners() {
57374
+ const events = [
57375
+ "chat:message",
57376
+ "notification",
57377
+ "lobby:matched",
57378
+ "lobby:invitation",
57379
+ "game:turn",
57380
+ "game:completed"
57381
+ ];
57382
+ for (const event of events) {
57383
+ this.unsubscribers.push(
57384
+ this.sdk.events.subscribe(event, (payload) => {
57385
+ this.eventQueue.push({
57386
+ event,
57387
+ payload,
57388
+ at: (/* @__PURE__ */ new Date()).toISOString()
57389
+ });
57390
+ this.writeHeartbeat().catch(() => {
57391
+ });
57392
+ })
57393
+ );
57394
+ }
57395
+ }
57396
+ /** Write HEARTBEAT.md with pending event summary for OpenClaw's heartbeat cycle. */
57397
+ async writeHeartbeat() {
57398
+ if (!this.config.heartbeatPath) return;
57399
+ const filePath = this.resolveHeartbeatPath();
57400
+ const count = this.eventQueue.length;
57401
+ if (count === 0) return;
57402
+ const lines = ["# DIM Heartbeat", ""];
57403
+ const eventTypes = new Set(this.eventQueue.map((e) => e.event));
57404
+ if (eventTypes.has("chat:message")) {
57405
+ lines.push("- You have new DMs \u2014 call dim_check_notifications");
57406
+ }
57407
+ if (eventTypes.has("notification")) {
57408
+ lines.push(
57409
+ "- New notifications (challenges, friend requests, game results) \u2014 call dim_check_notifications"
57410
+ );
57411
+ }
57412
+ if (eventTypes.has("lobby:matched") || eventTypes.has("lobby:invitation")) {
57413
+ lines.push("- A game match is ready \u2014 call dim_get_pending_events");
57414
+ }
57415
+ if (eventTypes.has("game:turn")) {
57416
+ lines.push("- It's your turn in a game \u2014 call dim_get_pending_events");
57417
+ }
57418
+ if (eventTypes.has("game:completed")) {
57419
+ lines.push("- A game has completed \u2014 call dim_get_pending_events");
57420
+ }
57421
+ lines.push("");
57422
+ await mkdir(path.dirname(filePath), { recursive: true });
57423
+ const tmp = `${filePath}.tmp`;
57424
+ await writeFile(tmp, lines.join("\n"), "utf8");
57425
+ await rename(tmp, filePath);
57426
+ }
57427
+ resolveHeartbeatPath() {
57428
+ const p = this.config.heartbeatPath;
57429
+ if (p.startsWith("~/") && process.env.HOME) {
57430
+ return path.join(process.env.HOME, p.slice(2));
57431
+ }
57432
+ return path.resolve(p);
57433
+ }
57434
+ /** Drain all buffered events since last call. */
57435
+ drainEvents() {
57436
+ return this.eventQueue.splice(0);
57437
+ }
57438
+ /** Peek at buffered event count without draining. */
57439
+ get pendingEventCount() {
57440
+ return this.eventQueue.length;
57441
+ }
57355
57442
  getKeypair() {
57356
57443
  return this.keypair;
57357
57444
  }
@@ -57365,8 +57452,8 @@ function getPluginConfig(api) {
57365
57452
  return dimclawEntry?.config ?? null;
57366
57453
  }
57367
57454
  function resolveStorePath(storePath) {
57368
- const expanded = storePath.startsWith("~/") && process.env.HOME ? path.join(process.env.HOME, storePath.slice(2)) : storePath;
57369
- return path.resolve(expanded);
57455
+ const expanded = storePath.startsWith("~/") && process.env.HOME ? path2.join(process.env.HOME, storePath.slice(2)) : storePath;
57456
+ return path2.resolve(expanded);
57370
57457
  }
57371
57458
  async function readWalletFile(storePath) {
57372
57459
  try {
@@ -57386,14 +57473,14 @@ async function readWalletFile(storePath) {
57386
57473
  }
57387
57474
  }
57388
57475
  async function writeWalletFile(storePath, record3) {
57389
- await mkdir(path.dirname(storePath), { recursive: true });
57476
+ await mkdir2(path2.dirname(storePath), { recursive: true });
57390
57477
  const tmp = `${storePath}.tmp`;
57391
- await writeFile(tmp, `${JSON.stringify(record3, null, 2)}
57478
+ await writeFile2(tmp, `${JSON.stringify(record3, null, 2)}
57392
57479
  `, {
57393
57480
  encoding: "utf8",
57394
57481
  mode: 384
57395
57482
  });
57396
- await rename(tmp, storePath);
57483
+ await rename2(tmp, storePath);
57397
57484
  }
57398
57485
  function createWalletRecord() {
57399
57486
  const keypair = Keypair.generate();
@@ -57453,6 +57540,7 @@ function register(api) {
57453
57540
  if ("error" in c) return c.error;
57454
57541
  try {
57455
57542
  const result = await c.authenticate();
57543
+ c.startEventListeners();
57456
57544
  const nextSteps = [];
57457
57545
  if (result.username == null || result.username === "") {
57458
57546
  nextSteps.push(
@@ -57684,7 +57772,15 @@ function register(api) {
57684
57772
  name: "dim_redeem_shares",
57685
57773
  description: "Redeem shares after market resolution."
57686
57774
  },
57687
- { name: "dim_get_market_analytics", description: "Get market analytics." }
57775
+ { name: "dim_get_market_analytics", description: "Get market analytics." },
57776
+ {
57777
+ name: "dim_get_pending_events",
57778
+ description: "Drain buffered real-time events (DMs, challenges, game turns). Call regularly."
57779
+ },
57780
+ {
57781
+ name: "dim_check_notifications",
57782
+ description: "Check unread notifications, DMs, and friend requests in one call."
57783
+ }
57688
57784
  ];
57689
57785
  api.registerTool({
57690
57786
  name: "dim_list_instructions",
@@ -59178,6 +59274,72 @@ function register(api) {
59178
59274
  }
59179
59275
  }
59180
59276
  });
59277
+ api.registerTool({
59278
+ name: "dim_get_pending_events",
59279
+ description: "Drain buffered real-time events (DMs, challenges, game turns, match notifications). Call this regularly during game loops or idle time to stay aware of incoming activity.",
59280
+ parameters: { type: "object", properties: {}, additionalProperties: false },
59281
+ async execute() {
59282
+ const c = await requireClient();
59283
+ if ("error" in c) return c.error;
59284
+ try {
59285
+ const events = c.drainEvents();
59286
+ return textResult(
59287
+ JSON.stringify(
59288
+ {
59289
+ count: events.length,
59290
+ events,
59291
+ hint: events.length === 0 ? "No new events since last check." : "Process these events and take action as needed."
59292
+ },
59293
+ null,
59294
+ 2
59295
+ )
59296
+ );
59297
+ } catch (err) {
59298
+ return textResult(
59299
+ `Failed to get pending events: ${err instanceof Error ? err.message : String(err)}`,
59300
+ true
59301
+ );
59302
+ }
59303
+ }
59304
+ });
59305
+ api.registerTool({
59306
+ name: "dim_check_notifications",
59307
+ description: "Check all pending items in one call: unread notifications (challenges, game results), unread DM threads, and incoming friend requests. Use this to catch up after being idle.",
59308
+ parameters: { type: "object", properties: {}, additionalProperties: false },
59309
+ async execute() {
59310
+ const c = await requireClient();
59311
+ if ("error" in c) return c.error;
59312
+ try {
59313
+ const [notifications, dmThreads, friendRequests] = await Promise.all([
59314
+ c.sdk.notifications.list({ page: 1, limit: 20 }),
59315
+ c.sdk.chat.listDmThreads(),
59316
+ c.sdk.users.getIncomingFriendRequests()
59317
+ ]);
59318
+ const unreadDms = dmThreads.filter(
59319
+ (t) => (t.unreadCount ?? 0) > 0
59320
+ );
59321
+ return textResult(
59322
+ JSON.stringify(
59323
+ {
59324
+ unreadNotificationCount: notifications.unreadCount,
59325
+ notifications: notifications.notifications.filter((n) => !n.read),
59326
+ unreadDmThreads: unreadDms,
59327
+ incomingFriendRequests: friendRequests,
59328
+ pendingWsEvents: c.pendingEventCount,
59329
+ hint: "Use dim_get_pending_events to drain buffered real-time events."
59330
+ },
59331
+ null,
59332
+ 2
59333
+ )
59334
+ );
59335
+ } catch (err) {
59336
+ return textResult(
59337
+ `Failed to check notifications: ${err instanceof Error ? err.message : String(err)}`,
59338
+ true
59339
+ );
59340
+ }
59341
+ }
59342
+ });
59181
59343
  }
59182
59344
  export {
59183
59345
  register as default
package/index.ts CHANGED
@@ -157,6 +157,7 @@ export default function register(api: {
157
157
  if ('error' in c) return c.error;
158
158
  try {
159
159
  const result = await c.authenticate();
160
+ c.startEventListeners();
160
161
  const nextSteps: string[] = [];
161
162
  if (result.username == null || result.username === '') {
162
163
  nextSteps.push(
@@ -345,7 +346,8 @@ export default function register(api: {
345
346
  },
346
347
  {
347
348
  name: 'dim_join_queue',
348
- description: 'Join matchmaking queue. If not matched immediately, poll dim_get_lobby until gameId appears.',
349
+ description:
350
+ 'Join matchmaking queue. If not matched immediately, poll dim_get_lobby until gameId appears.',
349
351
  },
350
352
  { name: 'dim_get_lobby', description: 'Get lobby details by ID.' },
351
353
  {
@@ -403,6 +405,16 @@ export default function register(api: {
403
405
  description: 'Redeem shares after market resolution.',
404
406
  },
405
407
  { name: 'dim_get_market_analytics', description: 'Get market analytics.' },
408
+ {
409
+ name: 'dim_get_pending_events',
410
+ description:
411
+ 'Drain buffered real-time events (DMs, challenges, game turns). Call regularly.',
412
+ },
413
+ {
414
+ name: 'dim_check_notifications',
415
+ description:
416
+ 'Check unread notifications, DMs, and friend requests in one call.',
417
+ },
406
418
  ];
407
419
  api.registerTool({
408
420
  name: 'dim_list_instructions',
@@ -2026,4 +2038,79 @@ export default function register(api: {
2026
2038
  }
2027
2039
  },
2028
2040
  });
2041
+
2042
+ // ── Real-time event awareness ─────────────────────────────────────────
2043
+
2044
+ api.registerTool({
2045
+ name: 'dim_get_pending_events',
2046
+ description:
2047
+ 'Drain buffered real-time events (DMs, challenges, game turns, match notifications). Call this regularly during game loops or idle time to stay aware of incoming activity.',
2048
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
2049
+ async execute() {
2050
+ const c = await requireClient();
2051
+ if ('error' in c) return c.error;
2052
+ try {
2053
+ const events = c.drainEvents();
2054
+ return textResult(
2055
+ JSON.stringify(
2056
+ {
2057
+ count: events.length,
2058
+ events,
2059
+ hint:
2060
+ events.length === 0
2061
+ ? 'No new events since last check.'
2062
+ : 'Process these events and take action as needed.',
2063
+ },
2064
+ null,
2065
+ 2,
2066
+ ),
2067
+ );
2068
+ } catch (err) {
2069
+ return textResult(
2070
+ `Failed to get pending events: ${err instanceof Error ? err.message : String(err)}`,
2071
+ true,
2072
+ );
2073
+ }
2074
+ },
2075
+ });
2076
+
2077
+ api.registerTool({
2078
+ name: 'dim_check_notifications',
2079
+ description:
2080
+ 'Check all pending items in one call: unread notifications (challenges, game results), unread DM threads, and incoming friend requests. Use this to catch up after being idle.',
2081
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
2082
+ async execute() {
2083
+ const c = await requireClient();
2084
+ if ('error' in c) return c.error;
2085
+ try {
2086
+ const [notifications, dmThreads, friendRequests] = await Promise.all([
2087
+ c.sdk.notifications.list({ page: 1, limit: 20 }),
2088
+ c.sdk.chat.listDmThreads(),
2089
+ c.sdk.users.getIncomingFriendRequests(),
2090
+ ]);
2091
+ const unreadDms = (dmThreads as Array<{ unreadCount?: number }>).filter(
2092
+ (t) => (t.unreadCount ?? 0) > 0,
2093
+ );
2094
+ return textResult(
2095
+ JSON.stringify(
2096
+ {
2097
+ unreadNotificationCount: notifications.unreadCount,
2098
+ notifications: notifications.notifications.filter((n) => !n.read),
2099
+ unreadDmThreads: unreadDms,
2100
+ incomingFriendRequests: friendRequests,
2101
+ pendingWsEvents: c.pendingEventCount,
2102
+ hint: 'Use dim_get_pending_events to drain buffered real-time events.',
2103
+ },
2104
+ null,
2105
+ 2,
2106
+ ),
2107
+ );
2108
+ } catch (err) {
2109
+ return textResult(
2110
+ `Failed to check notifications: ${err instanceof Error ? err.message : String(err)}`,
2111
+ true,
2112
+ );
2113
+ }
2114
+ },
2115
+ });
2029
2116
  }
@@ -13,6 +13,9 @@
13
13
  },
14
14
  "apiUrl": {
15
15
  "type": "string"
16
+ },
17
+ "heartbeatPath": {
18
+ "type": "string"
16
19
  }
17
20
  },
18
21
  "required": []
@@ -29,6 +32,10 @@
29
32
  "apiUrl": {
30
33
  "label": "API URL",
31
34
  "placeholder": "https://api.dim.cool"
35
+ },
36
+ "heartbeatPath": {
37
+ "label": "Heartbeat file path",
38
+ "placeholder": "~/.openclaw/workspace/HEARTBEAT.md"
32
39
  }
33
40
  },
34
41
  "setupHint": "After install, add your Solana wallet private key to plugins.entries.dimclaw.config.walletPrivateKey, then call dim_login to get started.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dimcool/dimclaw",
3
- "version": "0.1.13",
3
+ "version": "0.1.17",
4
4
  "description": "OpenClaw plugin for DIM — play games, chat, send USDC, and earn on DIM using the SDK directly (no MCP subprocess).",
5
5
  "type": "module",
6
6
  "openclaw": {