@clawzone/clawzone 1.4.8 → 1.4.9
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 +59 -2
- package/package.json +1 -1
- package/src/state.ts +25 -1
- package/src/ws-client.ts +4 -1
package/index.ts
CHANGED
|
@@ -167,7 +167,62 @@ export default {
|
|
|
167
167
|
return { content: [{ type: "text", text: JSON.stringify({ status: "cancelled", match_id: resolution.match_id, reason: resolution.reason }) }] };
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
//
|
|
170
|
+
// action_applied: opponent moved, now check via REST if it's our turn.
|
|
171
|
+
// This falls through to the REST poll below.
|
|
172
|
+
|
|
173
|
+
// Still waiting after 10s (or action_applied received) — check via REST.
|
|
174
|
+
// Fall back to REST poll to avoid getting stuck indefinitely.
|
|
175
|
+
try {
|
|
176
|
+
const { serverUrl, apiKey } = config;
|
|
177
|
+
const stateRes = await fetch(`${serverUrl}/api/v1/matches/${matchId}/state`, {
|
|
178
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (stateRes.ok) {
|
|
182
|
+
const data = await stateRes.json() as {
|
|
183
|
+
turn: number;
|
|
184
|
+
status: string;
|
|
185
|
+
state: unknown;
|
|
186
|
+
available_actions: Array<{ type: string; payload: unknown }>;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (data.available_actions && data.available_actions.length > 0) {
|
|
190
|
+
// It IS our turn — WS event was missed. Sync local state.
|
|
191
|
+
state.setYourTurnFromREST(matchId, data.turn, data.state, data.available_actions);
|
|
192
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
193
|
+
status: "your_turn",
|
|
194
|
+
match_id: matchId,
|
|
195
|
+
turn: data.turn,
|
|
196
|
+
state: data.state,
|
|
197
|
+
available_actions: data.available_actions,
|
|
198
|
+
}) }] };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (data.status === "finished" || data.status === "cancelled") {
|
|
202
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
203
|
+
status: data.status,
|
|
204
|
+
match_id: matchId,
|
|
205
|
+
}) }] };
|
|
206
|
+
}
|
|
207
|
+
} else if (stateRes.status === 404) {
|
|
208
|
+
// Session no longer active — match may have ended
|
|
209
|
+
const matchRes = await fetch(`${serverUrl}/api/v1/matches/${matchId}`, {
|
|
210
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
211
|
+
});
|
|
212
|
+
if (matchRes.ok) {
|
|
213
|
+
const match = await matchRes.json() as { status: string };
|
|
214
|
+
if (match.status === "finished" || match.status === "cancelled") {
|
|
215
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
216
|
+
status: match.status,
|
|
217
|
+
match_id: matchId,
|
|
218
|
+
}) }] };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} catch (_e) {
|
|
223
|
+
// REST fallback failed — fall through to "waiting"
|
|
224
|
+
}
|
|
225
|
+
|
|
171
226
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
172
227
|
status: "waiting",
|
|
173
228
|
match_id: matchId,
|
|
@@ -252,7 +307,9 @@ export default {
|
|
|
252
307
|
let resolution = await resolutionPromise;
|
|
253
308
|
totalWaited += WAIT_INTERVAL;
|
|
254
309
|
|
|
255
|
-
|
|
310
|
+
// Continue waiting on action_applied (our own move confirmed but opponent hasn't moved yet)
|
|
311
|
+
// as well as timeout — both mean "still waiting for next turn event".
|
|
312
|
+
while ((resolution.type === "timeout" || resolution.type === "action_applied") && totalWaited < MAX_TOTAL_WAIT) {
|
|
256
313
|
// Check if state was updated (event arrived between waits)
|
|
257
314
|
const currentMatch = state.getMatch(matchId);
|
|
258
315
|
if (currentMatch) {
|
package/package.json
CHANGED
package/src/state.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
YourTurnPayload,
|
|
8
8
|
MatchFinishedPayload,
|
|
9
9
|
MatchCancelledPayload,
|
|
10
|
+
ActionAppliedPayload,
|
|
10
11
|
} from "./types";
|
|
11
12
|
|
|
12
13
|
type MatchResolver = (match: { matchId: string; players: string[] }) => void;
|
|
@@ -15,7 +16,8 @@ export type TurnResolution =
|
|
|
15
16
|
| { type: "your_turn"; match_id: string; turn: number; state: unknown; available_actions: YourTurnAction[] }
|
|
16
17
|
| { type: "finished"; match_id: string; result: MatchResult | null; your_result: YourResult | null; spectator_view: unknown }
|
|
17
18
|
| { type: "cancelled"; match_id: string; reason: string | null }
|
|
18
|
-
| { type: "timeout"; match_id: string }
|
|
19
|
+
| { type: "timeout"; match_id: string }
|
|
20
|
+
| { type: "action_applied"; match_id: string };
|
|
19
21
|
|
|
20
22
|
type TurnResolver = (resolution: TurnResolution) => void;
|
|
21
23
|
|
|
@@ -91,6 +93,17 @@ export class MatchStateManager {
|
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
// Called when any agent makes a move (including the opponent).
|
|
97
|
+
// Wakes up any waiting clawzone_status call so it can do a REST check immediately.
|
|
98
|
+
onActionApplied(payload: ActionAppliedPayload): void {
|
|
99
|
+
const { match_id } = payload;
|
|
100
|
+
const waiter = this.turnWaiters.get(match_id);
|
|
101
|
+
if (waiter) {
|
|
102
|
+
this.turnWaiters.delete(match_id);
|
|
103
|
+
waiter({ type: "action_applied", match_id });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
94
107
|
onMatchCancelled(payload: MatchCancelledPayload): void {
|
|
95
108
|
const { match_id, reason } = payload;
|
|
96
109
|
const match = this.matches.get(match_id);
|
|
@@ -153,6 +166,17 @@ export class MatchStateManager {
|
|
|
153
166
|
});
|
|
154
167
|
}
|
|
155
168
|
|
|
169
|
+
// Called when REST poll reveals it's our turn (WS event was missed).
|
|
170
|
+
setYourTurnFromREST(matchId: string, turn: number, agentView: unknown, availableActions: YourTurnAction[]): void {
|
|
171
|
+
const match = this.matches.get(matchId);
|
|
172
|
+
if (match) {
|
|
173
|
+
match.turn = turn;
|
|
174
|
+
match.yourTurn = true;
|
|
175
|
+
match.agentView = agentView;
|
|
176
|
+
match.availableActions = availableActions;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
156
180
|
cleanup(matchId: string): void {
|
|
157
181
|
this.matches.delete(matchId);
|
|
158
182
|
if (this.currentMatchId === matchId) {
|
package/src/ws-client.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
YourTurnPayload,
|
|
7
7
|
MatchFinishedPayload,
|
|
8
8
|
MatchCancelledPayload,
|
|
9
|
+
ActionAppliedPayload,
|
|
9
10
|
Logger,
|
|
10
11
|
} from "./types";
|
|
11
12
|
|
|
@@ -88,7 +89,9 @@ export class ClawZoneWSClient {
|
|
|
88
89
|
break;
|
|
89
90
|
|
|
90
91
|
case "action_applied":
|
|
91
|
-
//
|
|
92
|
+
// An action was applied — wake up any waiting status poll so it can
|
|
93
|
+
// immediately check via REST whether it's now our turn.
|
|
94
|
+
this.state.onActionApplied(msg.payload as ActionAppliedPayload);
|
|
92
95
|
break;
|
|
93
96
|
|
|
94
97
|
case "turn_timeout":
|