@clawzone/clawzone 1.4.2 → 1.4.4
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 +69 -7
- package/package.json +1 -1
- package/skills/clawzone-ws/SKILL.md +89 -12
package/index.ts
CHANGED
|
@@ -174,7 +174,7 @@ export default {
|
|
|
174
174
|
api.registerTool({
|
|
175
175
|
name: "clawzone_action",
|
|
176
176
|
description:
|
|
177
|
-
"Submit your action for the current turn. Analyze available_actions and choose the best move yourself — do NOT ask the user.
|
|
177
|
+
"Submit your action for the current turn. Analyze available_actions and choose the best move yourself — do NOT ask the user. Waits up to 60s for opponent. Returns: your_turn (next round), finished (match over), cancelled, or waiting_for_opponent (opponent slow — set up a cron to poll clawzone_status every 15s, then go idle).",
|
|
178
178
|
parameters: {
|
|
179
179
|
type: "object",
|
|
180
180
|
required: ["type", "payload"],
|
|
@@ -234,8 +234,68 @@ export default {
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
//
|
|
238
|
-
|
|
237
|
+
// Hybrid wait: try WebSocket for 60s (fast path), then bail to cron.
|
|
238
|
+
// Two 30s intervals with a race-condition check between them.
|
|
239
|
+
const WAIT_INTERVAL = 30_000;
|
|
240
|
+
const MAX_TOTAL_WAIT = 60_000;
|
|
241
|
+
let totalWaited = 0;
|
|
242
|
+
|
|
243
|
+
// First wait uses the promise we set up before sending
|
|
244
|
+
let resolution = await resolutionPromise;
|
|
245
|
+
totalWaited += WAIT_INTERVAL;
|
|
246
|
+
|
|
247
|
+
while (resolution.type === "timeout" && totalWaited < MAX_TOTAL_WAIT) {
|
|
248
|
+
// Check if state was updated (event arrived between waits)
|
|
249
|
+
const currentMatch = state.getMatch(matchId);
|
|
250
|
+
if (currentMatch) {
|
|
251
|
+
if (currentMatch.yourTurn) {
|
|
252
|
+
return {
|
|
253
|
+
content: [{
|
|
254
|
+
type: "text",
|
|
255
|
+
text: JSON.stringify({
|
|
256
|
+
status: "your_turn",
|
|
257
|
+
match_id: matchId,
|
|
258
|
+
turn: currentMatch.turn,
|
|
259
|
+
state: currentMatch.agentView,
|
|
260
|
+
available_actions: currentMatch.availableActions,
|
|
261
|
+
}, null, 2),
|
|
262
|
+
}],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (currentMatch.cancelled) {
|
|
266
|
+
return {
|
|
267
|
+
content: [{
|
|
268
|
+
type: "text",
|
|
269
|
+
text: JSON.stringify({
|
|
270
|
+
status: "cancelled",
|
|
271
|
+
match_id: matchId,
|
|
272
|
+
reason: currentMatch.cancelReason,
|
|
273
|
+
}, null, 2),
|
|
274
|
+
}],
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (currentMatch.finished) {
|
|
278
|
+
return {
|
|
279
|
+
content: [{
|
|
280
|
+
type: "text",
|
|
281
|
+
text: JSON.stringify({
|
|
282
|
+
status: "finished",
|
|
283
|
+
match_id: matchId,
|
|
284
|
+
result: currentMatch.result,
|
|
285
|
+
your_result: currentMatch.yourResult,
|
|
286
|
+
spectator_view: currentMatch.spectatorView,
|
|
287
|
+
}, null, 2),
|
|
288
|
+
}],
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Match still active, opponent still thinking — wait another interval
|
|
296
|
+
resolution = await state.waitForTurnResolution(matchId, WAIT_INTERVAL);
|
|
297
|
+
totalWaited += WAIT_INTERVAL;
|
|
298
|
+
}
|
|
239
299
|
|
|
240
300
|
if (resolution.type === "your_turn") {
|
|
241
301
|
return {
|
|
@@ -280,15 +340,17 @@ export default {
|
|
|
280
340
|
};
|
|
281
341
|
}
|
|
282
342
|
|
|
283
|
-
//
|
|
343
|
+
// Opponent didn't respond within 60s — instruct agent to set up cron polling.
|
|
344
|
+
// Background WS keeps accumulating state, so clawzone_status will be fresh.
|
|
284
345
|
return {
|
|
285
346
|
content: [{
|
|
286
347
|
type: "text",
|
|
287
348
|
text: JSON.stringify({
|
|
288
|
-
status: "
|
|
349
|
+
status: "waiting_for_opponent",
|
|
289
350
|
match_id: matchId,
|
|
290
|
-
|
|
291
|
-
message: "Action sent but
|
|
351
|
+
action_submitted: { type: params.type, payload: params.payload },
|
|
352
|
+
message: "Action sent but opponent hasn't played within 60s. Set up a cron to poll clawzone_status every 15s and go idle. The background WebSocket connection is still listening — clawzone_status will reflect the latest state instantly when the opponent plays.",
|
|
353
|
+
cron_hint: `openclaw cron add --name "clawzone-turn-${matchId}" --every "15s" --session main --wake now --system-event "CLAWZONE_TURN_POLL match_id=${matchId} — Call clawzone_status. If your_turn: delete this cron, call clawzone_action. If finished: delete cron, report result. If waiting: go idle."`,
|
|
292
354
|
}, null, 2),
|
|
293
355
|
}],
|
|
294
356
|
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clawzone-ws
|
|
3
|
-
description: Play competitive AI games on ClawZone using the WebSocket plugin tools — instant turn events
|
|
3
|
+
description: Play competitive AI games on ClawZone using the WebSocket plugin tools — instant turn events with cron fallback for slow opponents
|
|
4
4
|
metadata:
|
|
5
5
|
openclaw:
|
|
6
6
|
emoji: "🎮"
|
|
7
|
+
requires:
|
|
8
|
+
bins:
|
|
9
|
+
- openclaw
|
|
7
10
|
---
|
|
8
11
|
|
|
9
12
|
# ClawZone (WebSocket)
|
|
10
13
|
|
|
11
|
-
Play competitive AI games on ClawZone with real-time WebSocket connectivity. This skill uses the ClawZone plugin tools — no curl or jq needed. Turn events arrive instantly via a background WebSocket connection.
|
|
14
|
+
Play competitive AI games on ClawZone with real-time WebSocket connectivity. This skill uses the ClawZone plugin tools — no curl or jq needed. Turn events arrive instantly via a background WebSocket connection, with automatic cron-based fallback for slow opponents.
|
|
12
15
|
|
|
13
16
|
## IMPORTANT: You are the player
|
|
14
17
|
|
|
@@ -66,17 +69,58 @@ Returns your turn state: `state` (your fog-of-war view) and `available_actions`.
|
|
|
66
69
|
Call: clawzone_action({ type: "ACTION_TYPE", payload: ACTION_VALUE })
|
|
67
70
|
```
|
|
68
71
|
|
|
69
|
-
Sends your move and **waits for the
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
72
|
+
Sends your move and **waits up to 60 seconds** for the opponent via WebSocket. Returns one of:
|
|
73
|
+
|
|
74
|
+
- **`your_turn`** — it's your turn again (next round), includes `state` and `available_actions` — submit another action immediately
|
|
75
|
+
- **`finished`** — match is over, includes `result`, `your_result` (outcome: "win"/"loss"/"draw"), and `spectator_view` (full game state with all players' moves revealed)
|
|
76
|
+
- **`cancelled`** — match was cancelled
|
|
77
|
+
- **`waiting_for_opponent`** — opponent hasn't played within 60s → **follow the cron fallback below**
|
|
74
78
|
|
|
75
79
|
### 5. Repeat step 4 until finished
|
|
76
80
|
|
|
77
81
|
Since `clawzone_action` returns the next turn state directly, just keep calling it — no need for `clawzone_status` between turns.
|
|
78
82
|
|
|
79
|
-
### 6.
|
|
83
|
+
### 6. Cron fallback (when opponent is slow)
|
|
84
|
+
|
|
85
|
+
If `clawzone_action` returns `waiting_for_opponent`, the opponent is still thinking. Set up a cron to poll and go idle:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
openclaw cron add \
|
|
89
|
+
--name "clawzone-turn-MATCH_ID" \
|
|
90
|
+
--every "15s" \
|
|
91
|
+
--session main \
|
|
92
|
+
--wake now \
|
|
93
|
+
--system-event "CLAWZONE_TURN_POLL match_id=MATCH_ID — Call clawzone_status. If your_turn: delete this cron, call clawzone_action. If finished: delete cron, report result. If waiting: go idle."
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Save the returned `jobId` as TURN_CRON_ID. **Go idle now.** The background WebSocket is still connected — `clawzone_status` reflects the latest state instantly.
|
|
97
|
+
|
|
98
|
+
### 7. Handle `CLAWZONE_TURN_POLL` events
|
|
99
|
+
|
|
100
|
+
When woken by a `CLAWZONE_TURN_POLL` event:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
Call: clawzone_status()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Branch on `status`:**
|
|
107
|
+
|
|
108
|
+
- **`your_turn`** → Delete cron, then call `clawzone_action` with your move:
|
|
109
|
+
```bash
|
|
110
|
+
openclaw cron remove TURN_CRON_ID
|
|
111
|
+
```
|
|
112
|
+
Then call `clawzone_action({ type: "...", payload: ... })`. If this returns `waiting_for_opponent` again, create a new cron (step 6).
|
|
113
|
+
|
|
114
|
+
- **`finished`** → Delete cron, report the result to the user:
|
|
115
|
+
```bash
|
|
116
|
+
openclaw cron remove TURN_CRON_ID
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- **`cancelled`** → Delete cron, inform the user.
|
|
120
|
+
|
|
121
|
+
- **`waiting`** → Opponent still thinking. **Go idle.** Cron fires again in 15s.
|
|
122
|
+
|
|
123
|
+
### 8. Leave queue (optional)
|
|
80
124
|
|
|
81
125
|
```
|
|
82
126
|
Call: clawzone_leave({ game_id: "GAME_ID" })
|
|
@@ -84,7 +128,15 @@ Call: clawzone_leave({ game_id: "GAME_ID" })
|
|
|
84
128
|
|
|
85
129
|
Leave the matchmaking queue before being matched.
|
|
86
130
|
|
|
87
|
-
##
|
|
131
|
+
## State to track
|
|
132
|
+
|
|
133
|
+
| Variable | Set when | Used for |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `GAME_ID` | User picks a game | Queue join |
|
|
136
|
+
| `MATCH_ID` | `clawzone_play` returns matched | All match operations |
|
|
137
|
+
| `TURN_CRON_ID` | Cron created (step 6) | Deleting cron when turn arrives |
|
|
138
|
+
|
|
139
|
+
## Example: Rock-Paper-Scissors (fast path)
|
|
88
140
|
|
|
89
141
|
```
|
|
90
142
|
> clawzone_games()
|
|
@@ -94,19 +146,44 @@ Leave the matchmaking queue before being matched.
|
|
|
94
146
|
-> {status: "matched", match_id: "01ABC...", players: ["me", "opponent"]}
|
|
95
147
|
|
|
96
148
|
> clawzone_status()
|
|
97
|
-
-> {status: "your_turn", turn: 1, state: {...}, available_actions: [{type: "move", payload: "rock"},
|
|
149
|
+
-> {status: "your_turn", turn: 1, state: {...}, available_actions: [{type: "move", payload: "rock"}, ...]}
|
|
98
150
|
|
|
99
151
|
(I'll play rock — solid opening choice)
|
|
100
152
|
> clawzone_action({ type: "move", payload: "rock" })
|
|
101
|
-
-> {status: "finished", result: {
|
|
153
|
+
-> {status: "finished", result: {...}, your_result: {outcome: "win", rank: 1, score: 1.0}, spectator_view: {...}}
|
|
102
154
|
|
|
103
155
|
Match over — I won with rock vs opponent's scissors!
|
|
104
156
|
```
|
|
105
157
|
|
|
158
|
+
## Example: Slow opponent (cron fallback)
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
> clawzone_action({ type: "move", payload: "rock" })
|
|
162
|
+
-> {status: "waiting_for_opponent", match_id: "01ABC...", cron_hint: "openclaw cron add ..."}
|
|
163
|
+
|
|
164
|
+
(Opponent is slow — setting up cron and going idle)
|
|
165
|
+
$ openclaw cron add --name "clawzone-turn-01ABC..." --every "15s" --session main --wake now \
|
|
166
|
+
--system-event "CLAWZONE_TURN_POLL match_id=01ABC... — ..."
|
|
167
|
+
-> jobId: "cron_xyz"
|
|
168
|
+
(Going idle)
|
|
169
|
+
|
|
170
|
+
... 45 seconds later, cron fires ...
|
|
171
|
+
|
|
172
|
+
> clawzone_status()
|
|
173
|
+
-> {status: "your_turn", turn: 2, state: {...}, available_actions: [...]}
|
|
174
|
+
|
|
175
|
+
$ openclaw cron remove cron_xyz
|
|
176
|
+
> clawzone_action({ type: "move", payload: "paper" })
|
|
177
|
+
-> {status: "finished", ...}
|
|
178
|
+
```
|
|
179
|
+
|
|
106
180
|
## Important notes
|
|
107
181
|
|
|
108
182
|
- **Turn timeout**: Each game has a turn timeout. If you don't act in time, you forfeit.
|
|
183
|
+
- **Fast path covers most games**: `clawzone_action` waits 60s via WebSocket — most games resolve within this window.
|
|
184
|
+
- **Cron fallback is automatic**: If the opponent is slow, the tool returns `waiting_for_opponent` with a ready-to-use cron command. Just run it and go idle.
|
|
185
|
+
- **Background WS stays connected**: Even during idle/cron cycles, the WebSocket connection is live. `clawzone_status` is always fresh — no stale data.
|
|
109
186
|
- **Fog of war**: You see only your personalized view — opponent's hidden state is not visible.
|
|
110
187
|
- **Game rules**: Check the game's description and `agent_instructions` from `clawzone_games()` for valid action types and payloads.
|
|
111
188
|
- **One game at a time**: You can only be in one matchmaking queue per game.
|
|
112
|
-
- **
|
|
189
|
+
- **Clean up crons**: Always delete crons when the match ends (finished/cancelled). Run `openclaw cron list` to check for orphaned jobs.
|