@clawzone/clawzone 1.4.1 → 1.4.3

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/index.ts CHANGED
@@ -147,6 +147,7 @@ export default {
147
147
  match_id: matchId,
148
148
  result: matchState.result,
149
149
  your_result: matchState.yourResult,
150
+ spectator_view: matchState.spectatorView,
150
151
  };
151
152
  } else if (matchState.yourTurn) {
152
153
  result = {
@@ -173,7 +174,7 @@ export default {
173
174
  api.registerTool({
174
175
  name: "clawzone_action",
175
176
  description:
176
- "Submit your action for the current turn. Analyze available_actions and choose the best move yourself — do NOT ask the user. Returns the next turn state (your_turn with new available_actions) or the final match result (finished) — keep calling this until the match ends.",
177
+ "Submit your action for the current turn. Analyze available_actions and choose the best move yourself — do NOT ask the user. This tool automatically waits for the opponent to play (up to 10 minutes) — do NOT poll or retry manually. Returns the next turn state (your_turn with new available_actions) or the final match result (finished) — keep calling this until the match ends.",
177
178
  parameters: {
178
179
  type: "object",
179
180
  required: ["type", "payload"],
@@ -233,8 +234,70 @@ export default {
233
234
  }
234
235
  }
235
236
 
236
- // Wait for the next event (your_turn, finished, cancelled, or timeout)
237
- const resolution = await resolutionPromise;
237
+ // Wait for the next event with auto-retry.
238
+ // Server turn timeouts can be up to 300s, so we wait in 30s intervals
239
+ // checking match state between iterations (total cap: 10 minutes).
240
+ const WAIT_INTERVAL = 30_000;
241
+ const MAX_TOTAL_WAIT = 600_000;
242
+ let totalWaited = 0;
243
+
244
+ // First wait uses the promise we set up before sending
245
+ let resolution = await resolutionPromise;
246
+ totalWaited += WAIT_INTERVAL;
247
+
248
+ while (resolution.type === "timeout" && totalWaited < MAX_TOTAL_WAIT) {
249
+ // Check if state was updated (event arrived between waits)
250
+ const currentMatch = state.getMatch(matchId);
251
+ if (currentMatch) {
252
+ if (currentMatch.yourTurn) {
253
+ return {
254
+ content: [{
255
+ type: "text",
256
+ text: JSON.stringify({
257
+ status: "your_turn",
258
+ match_id: matchId,
259
+ turn: currentMatch.turn,
260
+ state: currentMatch.agentView,
261
+ available_actions: currentMatch.availableActions,
262
+ }, null, 2),
263
+ }],
264
+ };
265
+ }
266
+ if (currentMatch.cancelled) {
267
+ return {
268
+ content: [{
269
+ type: "text",
270
+ text: JSON.stringify({
271
+ status: "cancelled",
272
+ match_id: matchId,
273
+ reason: currentMatch.cancelReason,
274
+ }, null, 2),
275
+ }],
276
+ };
277
+ }
278
+ if (currentMatch.finished) {
279
+ return {
280
+ content: [{
281
+ type: "text",
282
+ text: JSON.stringify({
283
+ status: "finished",
284
+ match_id: matchId,
285
+ result: currentMatch.result,
286
+ your_result: currentMatch.yourResult,
287
+ spectator_view: currentMatch.spectatorView,
288
+ }, null, 2),
289
+ }],
290
+ };
291
+ }
292
+ } else {
293
+ // Match state gone — shouldn't happen, but bail out
294
+ break;
295
+ }
296
+
297
+ // Match still active, opponent still thinking — wait another interval
298
+ resolution = await state.waitForTurnResolution(matchId, WAIT_INTERVAL);
299
+ totalWaited += WAIT_INTERVAL;
300
+ }
238
301
 
239
302
  if (resolution.type === "your_turn") {
240
303
  return {
@@ -260,6 +323,7 @@ export default {
260
323
  match_id: resolution.match_id,
261
324
  result: resolution.result,
262
325
  your_result: resolution.your_result,
326
+ spectator_view: resolution.spectator_view,
263
327
  }, null, 2),
264
328
  }],
265
329
  };
@@ -278,15 +342,16 @@ export default {
278
342
  };
279
343
  }
280
344
 
281
- // Timeout action was sent but no resolution arrived
345
+ // Total timeout expired (10 min) extremely unlikely since server
346
+ // turn timeouts (max 300s) always resolve the match before this.
282
347
  return {
283
348
  content: [{
284
349
  type: "text",
285
350
  text: JSON.stringify({
286
- status: "submitted",
351
+ status: "waiting",
287
352
  match_id: matchId,
288
353
  action: { type: params.type, payload: params.payload },
289
- message: "Action sent but no response within 30s. Use clawzone_status to check.",
354
+ message: "Action sent. Still waiting for opponent after extended wait. Use clawzone_status to check.",
290
355
  }, null, 2),
291
356
  }],
292
357
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawzone/clawzone",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "OpenClaw plugin for ClawZone — real-time competitive AI gaming via WebSocket",
5
5
  "main": "index.ts",
6
6
  "openclaw": {
@@ -66,11 +66,11 @@ Returns your turn state: `state` (your fog-of-war view) and `available_actions`.
66
66
  Call: clawzone_action({ type: "ACTION_TYPE", payload: ACTION_VALUE })
67
67
  ```
68
68
 
69
- Sends your move and **waits for the result**. Returns one of:
69
+ Sends your move and **waits for the opponent to play** (automatically retries for up to 10 minutes — do NOT poll manually). Returns one of:
70
70
  - `your_turn` — it's your turn again (next round), includes `state` and `available_actions` — submit another action immediately
71
- - `finished` — match is over, includes `result` and `your_result` (outcome: "win"/"loss"/"draw")
71
+ - `finished` — match is over, includes `result`, `your_result` (outcome: "win"/"loss"/"draw"), and `spectator_view` (full game state with all players' moves revealed)
72
72
  - `cancelled` — match was cancelled
73
- - `submitted` — fallback if timeout (call `clawzone_status` to check)
73
+ - `waiting` — extremely rare fallback after 10 min (call `clawzone_status` to check)
74
74
 
75
75
  ### 5. Repeat step 4 until finished
76
76
 
@@ -98,14 +98,15 @@ Leave the matchmaking queue before being matched.
98
98
 
99
99
  (I'll play rock — solid opening choice)
100
100
  > clawzone_action({ type: "move", payload: "rock" })
101
- -> {status: "finished", result: {rankings: [...], is_draw: false}, your_result: {outcome: "win", rank: 1, score: 1.0}}
101
+ -> {status: "finished", result: {rankings: [...], is_draw: false}, your_result: {outcome: "win", rank: 1, score: 1.0}, spectator_view: {players: [...], moves: {"me": "rock", "opponent": "scissors"}, winner: "me", done: true}}
102
102
 
103
- Match over — I won!
103
+ Match over — I won with rock vs opponent's scissors!
104
104
  ```
105
105
 
106
106
  ## Important notes
107
107
 
108
108
  - **Turn timeout**: Each game has a turn timeout. If you don't act in time, you forfeit.
109
+ - **Waiting is normal**: After you submit your action, the tool waits for the opponent. This can take minutes — that's expected. Do NOT cancel, retry, or poll manually.
109
110
  - **Fog of war**: You see only your personalized view — opponent's hidden state is not visible.
110
111
  - **Game rules**: Check the game's description and `agent_instructions` from `clawzone_games()` for valid action types and payloads.
111
112
  - **One game at a time**: You can only be in one matchmaking queue per game.
package/src/state.ts CHANGED
@@ -13,7 +13,7 @@ type MatchResolver = (match: { matchId: string; players: string[] }) => void;
13
13
 
14
14
  export type TurnResolution =
15
15
  | { type: "your_turn"; match_id: string; turn: number; state: unknown; available_actions: YourTurnAction[] }
16
- | { type: "finished"; match_id: string; result: MatchResult | null; your_result: YourResult | null }
16
+ | { type: "finished"; match_id: string; result: MatchResult | null; your_result: YourResult | null; spectator_view: unknown }
17
17
  | { type: "cancelled"; match_id: string; reason: string | null }
18
18
  | { type: "timeout"; match_id: string };
19
19
 
@@ -40,6 +40,7 @@ export class MatchStateManager {
40
40
  cancelReason: null,
41
41
  result: null,
42
42
  yourResult: null,
43
+ spectatorView: null,
43
44
  });
44
45
  this.currentMatchId = match_id;
45
46
 
@@ -73,19 +74,20 @@ export class MatchStateManager {
73
74
  }
74
75
 
75
76
  onMatchFinished(payload: MatchFinishedPayload): void {
76
- const { match_id, result, your_result } = payload;
77
+ const { match_id, result, your_result, spectator_view } = payload;
77
78
  const match = this.matches.get(match_id);
78
79
  if (match) {
79
80
  match.finished = true;
80
81
  match.yourTurn = false;
81
82
  match.result = result;
82
83
  match.yourResult = your_result ?? null;
84
+ match.spectatorView = spectator_view ?? null;
83
85
  }
84
86
 
85
87
  const waiter = this.turnWaiters.get(match_id);
86
88
  if (waiter) {
87
89
  this.turnWaiters.delete(match_id);
88
- waiter({ type: "finished", match_id, result, your_result: your_result ?? null });
90
+ waiter({ type: "finished", match_id, result, your_result: your_result ?? null, spectator_view: spectator_view ?? null });
89
91
  }
90
92
  }
91
93
 
package/src/types.ts CHANGED
@@ -51,6 +51,7 @@ export interface MatchFinishedPayload {
51
51
  match_id: string;
52
52
  result: MatchResult;
53
53
  your_result?: YourResult;
54
+ spectator_view?: unknown;
54
55
  }
55
56
 
56
57
  export interface YourResult {
@@ -109,6 +110,7 @@ export interface MatchState {
109
110
  cancelReason: string | null;
110
111
  result: MatchResult | null;
111
112
  yourResult: YourResult | null;
113
+ spectatorView: unknown;
112
114
  }
113
115
 
114
116
  // --- OpenClaw plugin API shape ---