chitin-openclaw-plugin 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * 1. Wallet tools — check balance, claim faucet, deposit to table
6
6
  * 2. Game tools — discover, lobby, create table, register, send action
7
7
  * 3. WebSocket connection — persistent connection to poker table, your_turn events
8
- * injected into agent session via hooks
8
+ * injected into agent session via openclaw agent --message
9
9
  */
10
10
  declare const plugin: {
11
11
  id: string;
package/dist/index.js CHANGED
@@ -12,6 +12,12 @@ import { privateKeyToAccount } from "viem/accounts";
12
12
  import { baseSepolia } from "viem/chains";
13
13
  import WebSocket from "ws";
14
14
  import { execFile } from "child_process";
15
+ import { trace, SpanStatusCode } from "@opentelemetry/api";
16
+ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
17
+ import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
18
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
19
+ import { Resource } from "@opentelemetry/resources";
20
+ import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
15
21
  var ERC20_ABI = parseAbi([
16
22
  "function balanceOf(address) view returns (uint256)",
17
23
  "function transferAndCall(address to, uint256 amount, bytes data) returns (bool)",
@@ -32,43 +38,42 @@ function connectToTable(gameServerUrl, roomCode, playerId, name, walletAddress,
32
38
  function connect() {
33
39
  if (aborted) return;
34
40
  const wsUrl = gameServerUrl.replace(/^http/, "ws") + `/parties/poker/${roomCode}`;
35
- logger.info(`[chitin] Connecting WebSocket to ${wsUrl}`);
41
+ logger.info(`[chitin] WS connecting to ${wsUrl}`);
36
42
  ws = new WebSocket(wsUrl);
37
43
  ws.on("open", () => {
38
- logger.info(`[chitin] WebSocket connected to table ${roomCode}`);
44
+ logger.info(`[chitin] WS connected to ${roomCode}`);
39
45
  wsStore.set(roomCode, ws);
40
- ws.send(JSON.stringify({
41
- type: "enter",
42
- playerId,
43
- name,
44
- walletAddress
45
- }));
46
+ const enterMsg = { type: "enter", playerId, name, walletAddress };
47
+ logger.info(`[chitin] WS sending enter: ${JSON.stringify(enterMsg)}`);
48
+ ws.send(JSON.stringify(enterMsg));
46
49
  });
47
50
  ws.on("message", (data) => {
48
51
  try {
49
52
  const msg = JSON.parse(data.toString());
50
53
  handleMessage(msg, roomCode, logger);
51
54
  } catch (err) {
52
- logger.warn(`[chitin] Failed to parse message: ${err.message}`);
55
+ logger.warn(`[chitin] WS parse error: ${err.message}`);
53
56
  }
54
57
  });
55
- ws.on("close", () => {
58
+ ws.on("close", (code) => {
56
59
  if (aborted) return;
57
- logger.info(`[chitin] WebSocket closed for table ${roomCode}, reconnecting in 3s...`);
60
+ logger.info(`[chitin] WS closed (code ${code}), reconnecting in 3s...`);
58
61
  setTimeout(connect, 3e3);
59
62
  });
60
63
  ws.on("error", (err) => {
61
- logger.warn(`[chitin] WebSocket error: ${err.message}`);
64
+ logger.error(`[chitin] WS error: ${err.message}`);
62
65
  });
63
66
  }
64
67
  const latestState = /* @__PURE__ */ new Map();
65
68
  function handleMessage(msg, roomCode2, logger2) {
66
69
  if (msg.type === "game_state") {
67
70
  latestState.set(roomCode2, msg);
71
+ logger2.info(`[chitin] game_state: players=${msg.players?.length || 0} hand=${msg.isHandInProgress ? "yes" : "no"}`);
68
72
  } else if (msg.type === "your_turn") {
69
73
  const state = latestState.get(roomCode2);
70
74
  const legalActions = msg.legalActions?.join(", ") || "";
71
75
  const chipRange = msg.chipRange ? `${msg.chipRange.min}-${msg.chipRange.max}` : "";
76
+ logger2.info(`[chitin] YOUR TURN: actions=[${legalActions}] range=${chipRange}`);
72
77
  const parts = [`POKER: It's your turn at table ${roomCode2}.`];
73
78
  if (state?.holeCards) {
74
79
  const cards = state.holeCards.map((c) => `${c.rank}${c.suit[0]}`).join(" ");
@@ -92,25 +97,30 @@ function connectToTable(gameServerUrl, roomCode, playerId, name, walletAddress,
92
97
  } else if (msg.type === "hand_result") {
93
98
  const winners = msg.result?.winners?.flat() || [];
94
99
  const winnerNames = winners.map((w) => `${w.playerId}(${w.amount})`).join(", ");
100
+ logger2.info(`[chitin] HAND RESULT: winners=${winnerNames} rake=${msg.result?.rake || 0}`);
95
101
  const text = `POKER HAND RESULT at table ${roomCode2}: Winners: ${winnerNames}. Rake: ${msg.result?.rake || 0}.`;
96
102
  promptAgent(text, logger2);
103
+ } else {
104
+ logger2.info(`[chitin] WS msg: type=${msg.type}`);
97
105
  }
98
106
  }
99
107
  function promptAgent(text, logger2) {
100
- execFile("openclaw", ["agent", "--local", "--message", text], (err, _stdout, stderr) => {
108
+ logger2.info(`[chitin] Prompting agent: ${text.slice(0, 100)}...`);
109
+ const child = execFile("openclaw", ["agent", "--local", "--message", text], {
110
+ timeout: 12e4,
111
+ env: process.env
112
+ // Inherit HOME pointing to workspace
113
+ }, (err, stdout, stderr) => {
101
114
  if (err) {
102
- logger2.error(`[chitin] Failed to prompt agent: ${err.message}`);
115
+ logger2.error(`[chitin] Agent prompt failed: ${err.message}`);
116
+ if (stderr) logger2.error(`[chitin] Agent stderr: ${stderr.slice(0, 500)}`);
103
117
  return;
104
118
  }
105
- if (stderr) logger2.warn(`[chitin] Agent stderr: ${stderr}`);
106
- logger2.info(`[chitin] Agent prompted successfully`);
119
+ if (stdout) logger2.info(`[chitin] Agent response: ${stdout.slice(0, 300)}`);
120
+ if (stderr) logger2.warn(`[chitin] Agent stderr: ${stderr.slice(0, 300)}`);
121
+ logger2.info(`[chitin] Agent prompted ok`);
107
122
  });
108
123
  }
109
- const sendAction = (action) => {
110
- if (ws?.readyState === WebSocket.OPEN) {
111
- ws.send(JSON.stringify({ type: "action", action }));
112
- }
113
- };
114
124
  connect();
115
125
  return () => {
116
126
  aborted = true;
@@ -123,29 +133,57 @@ var plugin = {
123
133
  description: "Play poker at Chitin Casino \u2014 wallet management and real-time game connection",
124
134
  configSchema: {
125
135
  type: "object",
126
- additionalProperties: false,
136
+ additionalProperties: true,
127
137
  properties: {}
128
138
  },
129
139
  register(api) {
130
140
  api.logger.info("[chitin] Chitin Casino plugin loaded");
141
+ let tracer = trace.getTracer("chitin-plugin");
142
+ try {
143
+ const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
144
+ const headers = process.env.OTEL_EXPORTER_OTLP_HEADERS;
145
+ if (endpoint) {
146
+ const headerObj = {};
147
+ if (headers) {
148
+ for (const pair of headers.split(",")) {
149
+ const [k, ...v] = pair.split("=");
150
+ if (k) headerObj[k.trim()] = v.join("=").trim();
151
+ }
152
+ }
153
+ const provider = new NodeTracerProvider({
154
+ resource: new Resource({ [ATTR_SERVICE_NAME]: "chitin-plugin" }),
155
+ spanProcessors: [
156
+ new BatchSpanProcessor(new OTLPTraceExporter({
157
+ url: `${endpoint}/v1/traces`,
158
+ headers: headerObj
159
+ }))
160
+ ]
161
+ });
162
+ provider.register();
163
+ tracer = trace.getTracer("chitin-plugin");
164
+ api.logger.info(`[chitin] OTel initialized \u2014 endpoint=${endpoint}`);
165
+ const span = tracer.startSpan("plugin.loaded");
166
+ span.setAttributes({ "plugin.name": "chitin-openclaw-plugin" });
167
+ span.setStatus({ code: SpanStatusCode.OK });
168
+ span.end();
169
+ api.logger.info("[chitin] OTel test span emitted: plugin.loaded");
170
+ } else {
171
+ api.logger.info("[chitin] OTel skipped \u2014 no OTEL_EXPORTER_OTLP_ENDPOINT");
172
+ }
173
+ } catch (err) {
174
+ api.logger.warn(`[chitin] OTel init failed (non-fatal): ${err.message}`);
175
+ }
131
176
  const gameServerUrl = process.env.GAME_SERVER_URL || "http://localhost:3665";
177
+ const adminServerUrl = process.env.ADMIN_SERVER_URL || "http://localhost:3667";
132
178
  const activeConnections = /* @__PURE__ */ new Map();
133
179
  const wsInstances = /* @__PURE__ */ new Map();
134
- api.registerTool({
135
- name: "chitin_test",
136
- label: "Chitin Test",
137
- description: "A test tool that returns hello world",
138
- parameters: { type: "object", properties: {} },
139
- execute: async (_toolCallId) => {
140
- return { message: "Hello from Chitin Casino plugin!" };
141
- }
142
- });
143
180
  api.registerTool({
144
181
  name: "chitin_wallet_address",
145
182
  description: "Get your Chitin Casino wallet address",
146
183
  parameters: {},
147
- execute: async (_toolCallId) => {
184
+ execute: async () => {
148
185
  const { account } = getWalletClients();
186
+ api.logger.info(`[chitin] wallet_address: ${account.address}`);
149
187
  return { address: account.address };
150
188
  }
151
189
  });
@@ -160,7 +198,9 @@ var plugin = {
160
198
  },
161
199
  execute: async (_toolCallId, params) => {
162
200
  const { account, publicClient } = getWalletClients();
201
+ api.logger.info(`[chitin] wallet_balance: checking ${account.address}`);
163
202
  const ethBalance = await publicClient.getBalance({ address: account.address });
203
+ api.logger.info(`[chitin] wallet_balance: ETH=${formatEther(ethBalance)}`);
164
204
  let tokenBalance = BigInt(0);
165
205
  let tokenAddr = params.tokenAddress;
166
206
  if (!tokenAddr) {
@@ -168,7 +208,9 @@ var plugin = {
168
208
  const res = await fetch(`${gameServerUrl}/contracts`);
169
209
  const data = await res.json();
170
210
  tokenAddr = data.token;
171
- } catch {
211
+ api.logger.info(`[chitin] wallet_balance: token=${tokenAddr}`);
212
+ } catch (err) {
213
+ api.logger.error(`[chitin] wallet_balance: failed to fetch token address: ${err.message}`);
172
214
  }
173
215
  }
174
216
  if (tokenAddr) {
@@ -178,6 +220,7 @@ var plugin = {
178
220
  functionName: "balanceOf",
179
221
  args: [account.address]
180
222
  });
223
+ api.logger.info(`[chitin] wallet_balance: NUMERO=${formatEther(tokenBalance)}`);
181
224
  }
182
225
  return {
183
226
  address: account.address,
@@ -188,21 +231,18 @@ var plugin = {
188
231
  });
189
232
  api.registerTool({
190
233
  name: "chitin_faucet_claim",
191
- description: "Claim NUMERO tokens from the faucet. This is a free server-side operation \u2014 no gas needed.",
234
+ description: "Claim NUMERO tokens from the faucet. Free \u2014 no gas needed.",
192
235
  parameters: {},
193
- execute: async (_toolCallId) => {
236
+ execute: async () => {
194
237
  const { account } = getWalletClients();
195
- const adminServerUrl = process.env.ADMIN_SERVER_URL || "http://localhost:3667";
196
- const serviceKey = process.env.SERVICE_KEY || "";
238
+ api.logger.info(`[chitin] faucet_claim: wallet=${account.address} server=${adminServerUrl}`);
197
239
  const res = await fetch(`${adminServerUrl}/faucet/claim`, {
198
240
  method: "POST",
199
- headers: {
200
- "Content-Type": "application/json",
201
- "x-service-key": serviceKey
202
- },
241
+ headers: { "Content-Type": "application/json" },
203
242
  body: JSON.stringify({ walletAddress: account.address })
204
243
  });
205
244
  const data = await res.json();
245
+ api.logger.info(`[chitin] faucet_claim: status=${res.status} response=${JSON.stringify(data)}`);
206
246
  if (!res.ok) throw new Error(data.error || "Faucet claim failed");
207
247
  return data;
208
248
  }
@@ -220,21 +260,27 @@ var plugin = {
220
260
  required: ["amount", "tableId", "gameId"]
221
261
  },
222
262
  execute: async (_toolCallId, params) => {
263
+ api.logger.info(`[chitin] deposit: amount=${params.amount} tableId=${params.tableId} gameId=${params.gameId}`);
223
264
  const { publicClient, walletClient } = getWalletClients();
265
+ api.logger.info(`[chitin] deposit: fetching contracts from ${gameServerUrl}/contracts`);
224
266
  const res = await fetch(`${gameServerUrl}/contracts`);
225
267
  const contracts = await res.json();
268
+ api.logger.info(`[chitin] deposit: token=${contracts.token} diamond=${contracts.diamond}`);
226
269
  const amountWei = parseEther(params.amount);
227
270
  const data = encodeAbiParameters(
228
271
  [{ type: "bytes32" }, { type: "bytes32" }],
229
272
  [params.tableId, params.gameId]
230
273
  );
274
+ api.logger.info(`[chitin] deposit: sending transferAndCall amount=${amountWei} to=${contracts.diamond}`);
231
275
  const hash = await walletClient.writeContract({
232
276
  address: contracts.token,
233
277
  abi: ERC20_ABI,
234
278
  functionName: "transferAndCall",
235
279
  args: [contracts.diamond, amountWei, data]
236
280
  });
281
+ api.logger.info(`[chitin] deposit: tx sent hash=${hash}`);
237
282
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
283
+ api.logger.info(`[chitin] deposit: confirmed block=${receipt.blockNumber} status=${receipt.status}`);
238
284
  return { txHash: hash, blockNumber: Number(receipt.blockNumber) };
239
285
  }
240
286
  });
@@ -242,18 +288,24 @@ var plugin = {
242
288
  name: "chitin_discover",
243
289
  description: "Discover Chitin Casino \u2014 games, contracts, faucet info",
244
290
  parameters: {},
245
- execute: async (_toolCallId) => {
291
+ execute: async () => {
292
+ api.logger.info(`[chitin] discover: ${gameServerUrl}/discover`);
246
293
  const res = await fetch(`${gameServerUrl}/discover`);
247
- return await res.json();
294
+ const data = await res.json();
295
+ api.logger.info(`[chitin] discover: status=${res.status}`);
296
+ return data;
248
297
  }
249
298
  });
250
299
  api.registerTool({
251
300
  name: "chitin_lobby",
252
301
  description: "List poker tables with open seats",
253
302
  parameters: {},
254
- execute: async (_toolCallId) => {
303
+ execute: async () => {
304
+ api.logger.info(`[chitin] lobby: ${gameServerUrl}/parties/pokerlobby/main/tables`);
255
305
  const res = await fetch(`${gameServerUrl}/parties/pokerlobby/main/tables`);
256
- return await res.json();
306
+ const data = await res.json();
307
+ api.logger.info(`[chitin] lobby: status=${res.status} tables=${Array.isArray(data) ? data.length : "?"}`);
308
+ return data;
257
309
  }
258
310
  });
259
311
  api.registerTool({
@@ -269,19 +321,21 @@ var plugin = {
269
321
  }
270
322
  },
271
323
  execute: async (_toolCallId, params) => {
324
+ const settings = {
325
+ smallBlind: params.smallBlind || 1,
326
+ bigBlind: params.bigBlind || 2,
327
+ minBuyIn: params.minBuyIn || 50,
328
+ maxBuyIn: params.maxBuyIn || 200
329
+ };
330
+ api.logger.info(`[chitin] create_table: ${JSON.stringify(settings)}`);
272
331
  const res = await fetch(`${gameServerUrl}/parties/poker/create`, {
273
332
  method: "POST",
274
333
  headers: { "Content-Type": "application/json" },
275
- body: JSON.stringify({
276
- settings: {
277
- smallBlind: params.smallBlind || 1,
278
- bigBlind: params.bigBlind || 2,
279
- minBuyIn: params.minBuyIn || 50,
280
- maxBuyIn: params.maxBuyIn || 200
281
- }
282
- })
334
+ body: JSON.stringify({ settings })
283
335
  });
284
- return await res.json();
336
+ const data = await res.json();
337
+ api.logger.info(`[chitin] create_table: status=${res.status} response=${JSON.stringify(data)}`);
338
+ return data;
285
339
  }
286
340
  });
287
341
  api.registerTool({
@@ -299,6 +353,7 @@ var plugin = {
299
353
  execute: async (_toolCallId, params) => {
300
354
  const { account } = getWalletClients();
301
355
  const botName = params.name || "ClawBot";
356
+ api.logger.info(`[chitin] register: room=${params.roomCode} name=${botName} buyIn=${params.buyIn || 200} wallet=${account.address}`);
302
357
  const res = await fetch(`${gameServerUrl}/parties/poker/${params.roomCode}/register`, {
303
358
  method: "POST",
304
359
  headers: { "Content-Type": "application/json" },
@@ -309,6 +364,7 @@ var plugin = {
309
364
  })
310
365
  });
311
366
  const result = await res.json();
367
+ api.logger.info(`[chitin] register: status=${res.status} response=${JSON.stringify(result)}`);
312
368
  if (!res.ok) return result;
313
369
  const cleanup = connectToTable(
314
370
  gameServerUrl,
@@ -319,8 +375,7 @@ var plugin = {
319
375
  api.logger,
320
376
  wsInstances
321
377
  );
322
- activeConnections.set(params.roomCode, { cleanup, ws: null });
323
- api.logger.info(`[chitin] Registered at ${params.roomCode}, WebSocket connected`);
378
+ activeConnections.set(params.roomCode, { cleanup });
324
379
  return { ...result, wsConnected: true };
325
380
  }
326
381
  });
@@ -337,13 +392,16 @@ var plugin = {
337
392
  required: ["roomCode", "action"]
338
393
  },
339
394
  execute: async (_toolCallId, params) => {
395
+ api.logger.info(`[chitin] poker_action: room=${params.roomCode} action=${params.action} amount=${params.amount ?? "n/a"}`);
340
396
  const ws = wsInstances.get(params.roomCode);
341
397
  if (!ws || ws.readyState !== WebSocket.OPEN) {
398
+ api.logger.error(`[chitin] poker_action: NOT CONNECTED to ${params.roomCode} (ws=${ws ? "exists" : "null"} state=${ws?.readyState})`);
342
399
  return { error: "Not connected to table. Call chitin_register first." };
343
400
  }
344
401
  const action = { type: params.action };
345
402
  if (params.amount !== void 0) action.amount = params.amount;
346
403
  ws.send(JSON.stringify({ type: "action", action }));
404
+ api.logger.info(`[chitin] poker_action: sent ${JSON.stringify(action)}`);
347
405
  return { ok: true, sent: action };
348
406
  }
349
407
  });
@@ -359,22 +417,27 @@ var plugin = {
359
417
  required: ["roomCode", "token"]
360
418
  },
361
419
  execute: async (_toolCallId, params) => {
420
+ api.logger.info(`[chitin] leave_table: room=${params.roomCode}`);
421
+ const ws = wsInstances.get(params.roomCode);
422
+ if (ws?.readyState === WebSocket.OPEN) {
423
+ ws.send(JSON.stringify({ type: "leave" }));
424
+ api.logger.info(`[chitin] leave_table: sent leave via WS`);
425
+ }
362
426
  const conn = activeConnections.get(params.roomCode);
363
427
  if (conn) {
364
428
  conn.cleanup();
365
429
  activeConnections.delete(params.roomCode);
366
430
  }
367
431
  wsInstances.delete(params.roomCode);
368
- const ws = wsInstances.get(params.roomCode);
369
- if (ws?.readyState === WebSocket.OPEN) {
370
- ws.send(JSON.stringify({ type: "leave" }));
371
- return { ok: true };
432
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
433
+ api.logger.info(`[chitin] leave_table: fallback to HTTP leave`);
434
+ const res = await fetch(`${gameServerUrl}/parties/poker/${params.roomCode}/leave`, {
435
+ method: "POST",
436
+ headers: { "Authorization": `Bearer ${params.token}` }
437
+ });
438
+ return await res.json();
372
439
  }
373
- const res = await fetch(`${gameServerUrl}/parties/poker/${params.roomCode}/leave`, {
374
- method: "POST",
375
- headers: { "Authorization": `Bearer ${params.token}` }
376
- });
377
- return await res.json();
440
+ return { ok: true };
378
441
  }
379
442
  });
380
443
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "chitin-openclaw-plugin",
3
3
  "name": "Chitin Casino",
4
- "version": "0.1.0",
4
+ "version": "0.2.2",
5
5
  "description": "Play poker at Chitin Casino — wallet management and real-time game connection",
6
6
  "author": "Chitin Casino",
7
7
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chitin-openclaw-plugin",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "OpenClaw plugin for Chitin Casino — wallet management and poker game connection",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,6 +30,11 @@
30
30
  "prepublishOnly": "npm run build"
31
31
  },
32
32
  "dependencies": {
33
+ "@opentelemetry/api": "^1.9.0",
34
+ "@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
35
+ "@opentelemetry/resources": "^1.30.0",
36
+ "@opentelemetry/sdk-trace-node": "^1.30.0",
37
+ "@opentelemetry/semantic-conventions": "^1.28.0",
33
38
  "viem": "^2.0.0",
34
39
  "ws": "^8.0.0"
35
40
  },