@dimcool/dimclaw 0.1.14 → 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 +89 -0
- package/dist/index.js +214 -60
- package/index.ts +86 -0
- package/openclaw.plugin.json +7 -0
- package/package.json +1 -1
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
|
|
6517
|
-
if (
|
|
6516
|
+
const path3 = url3.path;
|
|
6517
|
+
if (path3.length === 0) {
|
|
6518
6518
|
return;
|
|
6519
6519
|
}
|
|
6520
|
-
if (url3.scheme === "file" &&
|
|
6520
|
+
if (url3.scheme === "file" && path3.length === 1 && isNormalizedWindowsDriveLetter(path3[0])) {
|
|
6521
6521
|
return;
|
|
6522
6522
|
}
|
|
6523
|
-
|
|
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
|
|
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 =
|
|
7597
|
+
dir = path3.resolve(dir || ".");
|
|
7598
7598
|
try {
|
|
7599
|
-
var name = runtimeRequire(
|
|
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(
|
|
7604
|
+
var release = getFirst(path3.join(dir, "build/Release"), matchBuild);
|
|
7605
7605
|
if (release) return release;
|
|
7606
|
-
var debug12 = getFirst(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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] &&
|
|
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
|
|
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:
|
|
21593
|
-
const msg =
|
|
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:
|
|
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:
|
|
21639
|
-
path:
|
|
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:
|
|
21658
|
-
const ctx = { path:
|
|
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 ?
|
|
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,
|
|
29840
|
-
const regx = /\/{2,9}/g, names =
|
|
29841
|
-
if (
|
|
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 (
|
|
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,
|
|
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 +
|
|
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
|
|
32120
|
-
const sameNamespace = cache[id] &&
|
|
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
|
|
38668
|
-
if (
|
|
38671
|
+
const path3 = url22.path;
|
|
38672
|
+
if (path3.length === 0) {
|
|
38669
38673
|
return;
|
|
38670
38674
|
}
|
|
38671
|
-
if (url22.scheme === "file" &&
|
|
38675
|
+
if (url22.scheme === "file" && path3.length === 1 && isNormalizedWindowsDriveLetter(path3[0])) {
|
|
38672
38676
|
return;
|
|
38673
38677
|
}
|
|
38674
|
-
|
|
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
|
|
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 =
|
|
39742
|
+
dir = path3.resolve(dir || ".");
|
|
39739
39743
|
try {
|
|
39740
|
-
var name = runtimeRequire(
|
|
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(
|
|
39749
|
+
var release = getFirst(path3.join(dir, "build/Release"), matchBuild);
|
|
39746
39750
|
if (release) return release;
|
|
39747
|
-
var debug12 = getFirst(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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] &&
|
|
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:
|
|
46874
|
-
const msg =
|
|
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:
|
|
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:
|
|
46920
|
-
path:
|
|
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:
|
|
46939
|
-
const ctx = { path:
|
|
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 ?
|
|
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,
|
|
@@ -57305,6 +57309,8 @@ var DimClient = class {
|
|
|
57305
57309
|
config;
|
|
57306
57310
|
authenticated = false;
|
|
57307
57311
|
userId = null;
|
|
57312
|
+
eventQueue = [];
|
|
57313
|
+
unsubscribers = [];
|
|
57308
57314
|
constructor(config) {
|
|
57309
57315
|
this.config = config;
|
|
57310
57316
|
const secretKeyBytes = esm_default2.decode(config.walletPrivateKey);
|
|
@@ -57360,6 +57366,79 @@ var DimClient = class {
|
|
|
57360
57366
|
async ensureConnected(timeoutMs = 1e4) {
|
|
57361
57367
|
await this.sdk.ensureWebSocketConnected(timeoutMs);
|
|
57362
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
|
+
}
|
|
57363
57442
|
getKeypair() {
|
|
57364
57443
|
return this.keypair;
|
|
57365
57444
|
}
|
|
@@ -57373,8 +57452,8 @@ function getPluginConfig(api) {
|
|
|
57373
57452
|
return dimclawEntry?.config ?? null;
|
|
57374
57453
|
}
|
|
57375
57454
|
function resolveStorePath(storePath) {
|
|
57376
|
-
const expanded = storePath.startsWith("~/") && process.env.HOME ?
|
|
57377
|
-
return
|
|
57455
|
+
const expanded = storePath.startsWith("~/") && process.env.HOME ? path2.join(process.env.HOME, storePath.slice(2)) : storePath;
|
|
57456
|
+
return path2.resolve(expanded);
|
|
57378
57457
|
}
|
|
57379
57458
|
async function readWalletFile(storePath) {
|
|
57380
57459
|
try {
|
|
@@ -57394,14 +57473,14 @@ async function readWalletFile(storePath) {
|
|
|
57394
57473
|
}
|
|
57395
57474
|
}
|
|
57396
57475
|
async function writeWalletFile(storePath, record3) {
|
|
57397
|
-
await
|
|
57476
|
+
await mkdir2(path2.dirname(storePath), { recursive: true });
|
|
57398
57477
|
const tmp = `${storePath}.tmp`;
|
|
57399
|
-
await
|
|
57478
|
+
await writeFile2(tmp, `${JSON.stringify(record3, null, 2)}
|
|
57400
57479
|
`, {
|
|
57401
57480
|
encoding: "utf8",
|
|
57402
57481
|
mode: 384
|
|
57403
57482
|
});
|
|
57404
|
-
await
|
|
57483
|
+
await rename2(tmp, storePath);
|
|
57405
57484
|
}
|
|
57406
57485
|
function createWalletRecord() {
|
|
57407
57486
|
const keypair = Keypair.generate();
|
|
@@ -57461,6 +57540,7 @@ function register(api) {
|
|
|
57461
57540
|
if ("error" in c) return c.error;
|
|
57462
57541
|
try {
|
|
57463
57542
|
const result = await c.authenticate();
|
|
57543
|
+
c.startEventListeners();
|
|
57464
57544
|
const nextSteps = [];
|
|
57465
57545
|
if (result.username == null || result.username === "") {
|
|
57466
57546
|
nextSteps.push(
|
|
@@ -57692,7 +57772,15 @@ function register(api) {
|
|
|
57692
57772
|
name: "dim_redeem_shares",
|
|
57693
57773
|
description: "Redeem shares after market resolution."
|
|
57694
57774
|
},
|
|
57695
|
-
{ 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
|
+
}
|
|
57696
57784
|
];
|
|
57697
57785
|
api.registerTool({
|
|
57698
57786
|
name: "dim_list_instructions",
|
|
@@ -59186,6 +59274,72 @@ function register(api) {
|
|
|
59186
59274
|
}
|
|
59187
59275
|
}
|
|
59188
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
|
+
});
|
|
59189
59343
|
}
|
|
59190
59344
|
export {
|
|
59191
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(
|
|
@@ -404,6 +405,16 @@ export default function register(api: {
|
|
|
404
405
|
description: 'Redeem shares after market resolution.',
|
|
405
406
|
},
|
|
406
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
|
+
},
|
|
407
418
|
];
|
|
408
419
|
api.registerTool({
|
|
409
420
|
name: 'dim_list_instructions',
|
|
@@ -2027,4 +2038,79 @@ export default function register(api: {
|
|
|
2027
2038
|
}
|
|
2028
2039
|
},
|
|
2029
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
|
+
});
|
|
2030
2116
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -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