@chipzen-ai/bot 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/CHANGELOG.md ADDED
@@ -0,0 +1,220 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `@chipzen-ai/bot` JavaScript / TypeScript
4
+ SDK will be documented in this file.
5
+
6
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
+
9
+ ## [Unreleased]
10
+
11
+ ### Security
12
+
13
+ - **External-API: refuse a cross-origin or `wss`→`ws` gateway URL.**
14
+ `runExternalBot()` now rejects a server-supplied absolute `gateway_ws_url`
15
+ whose origin differs from the lobby's, or that downgrades to cleartext
16
+ `ws://` — so the bot token can never be sent to a different host or
17
+ unencrypted; the offending match is skipped. A relative URL is always
18
+ re-anchored to the lobby origin.
19
+ ([#58](https://github.com/chipzen-ai/chipzen-sdk/issues/58))
20
+
21
+ ## [0.3.0] — 2026-06-13
22
+
23
+ Reaches feature parity with the Python SDK's external-API remote-play
24
+ path (chipzen-ai/chipzen-sdk#56), and ships the just-merged reconnect
25
+ fix from the Python side.
26
+
27
+ ### Added
28
+
29
+ - **External-API remote-play surface.** `runExternalBot()` connects a bot
30
+ to the platform over the public token-authed external-API path — lobby
31
+ (`/ws/external/bot/{botId}`) → `matched` → per-match gateway WS
32
+ (`/ws/external/match/{mid}/{pid}`, token in the `Sec-WebSocket-Protocol`
33
+ header) — and plays every match dispatched to it (a single challenge, or
34
+ each round of a tournament) on one persistent lobby connection. The match
35
+ data plane reuses the same `Bot.decide(GameState) -> Action` loop
36
+ (`_runSession`) as the containerized `runBot()` path, so one bot class
37
+ works on both. ([#56](https://github.com/chipzen-ai/chipzen-sdk/issues/56))
38
+ - `connectToChipzen(botId, env)` — env-aware lobby-URL helper
39
+ (`prod` / `staging` / `local`, honoring `$CHIPZEN_ENV`), returning a
40
+ `ConnectionConfig`.
41
+ - `chipzen.toml` config-file convention: drop your `cz_extbot_` token (and
42
+ optional `url` / `bot_id`) into `[external_api]` once; discovered from
43
+ cwd → `~/.chipzen/` → `/etc/chipzen/`. Explicit kwargs always win. Parsed
44
+ with a minimal inline reader so the package keeps its single runtime
45
+ dependency (`ws`) — no TOML library added.
46
+ - `chipzen-sdk run-external <bot.js>` CLI: loads config, resolves the env
47
+ URL, finds your exported `Bot` subclass, and runs it. Flags mirror the
48
+ Python CLI: `--env`, `--token`, `--bot-id`, `--bot-class`,
49
+ `--max-matches`, `--no-safe-mode`.
50
+ - `RetryPolicy` reconnect/backoff knobs (`maxReconnectAttempts`,
51
+ `initialBackoffMs`, `maxBackoffMs`, `backoffMultiplier`) + the shared
52
+ `DEFAULT_RETRY_POLICY`, accepted by both `runBot()` and
53
+ `runExternalBot()`. Default: 5 attempts, 500 ms initial backoff doubling
54
+ to a 30 s cap.
55
+ - `Bot.onDecisionLatency(latencyMs)` hook — called after each `turn_action`
56
+ is sent, with the wall-clock time your `decide()` took. Default no-op.
57
+ ([#46](https://github.com/chipzen-ai/chipzen-sdk/issues/46))
58
+ - `safeMode` option on `runBot()` / `runExternalBot()` (default `true`,
59
+ preserving the existing fold-on-error behavior). Set `false` for
60
+ dev/eval so an exception in `decide()` raises `BotDecisionError` and
61
+ exits non-zero instead of being silently folded. `BotDecisionError` is
62
+ now exported. ([#52](https://github.com/chipzen-ai/chipzen-sdk/issues/52))
63
+ - A non-default `User-Agent` (`chipzen-sdk-js/<version>`) is now sent on
64
+ the WebSocket handshake (defense-in-depth against the platform's
65
+ Cloudflare bot-fight rule). Override with `userAgent`.
66
+ ([#46](https://github.com/chipzen-ai/chipzen-sdk/issues/46))
67
+ - `VERSION` export — the SDK version string, sourced from `package.json`.
68
+
69
+ ### Changed
70
+
71
+ - `runBot()` now returns the `match_end` payload
72
+ (`Record<string, unknown> | null`) instead of `void`.
73
+ Backward-compatible — callers that ignore the return value are
74
+ unaffected. (`_runSession` likewise returns the payload so the lobby
75
+ loop can distinguish a clean end from a drop.)
76
+ - `runBot()`'s default reconnect cap is now 5 attempts (from the default
77
+ `RetryPolicy`) with the policy's exponential backoff, instead of the
78
+ previous hardcoded `maxRetries=3` with `min(2**n, 8)s`. Pass
79
+ `maxRetries` or a `retryPolicy` to override.
80
+
81
+ ### Fixed
82
+
83
+ - The default `clientVersion` sent in the `hello` handshake now tracks the
84
+ installed package version instead of the hardcoded `"0.2.0"` literal
85
+ that would drift on a release bump.
86
+ ([#41](https://github.com/chipzen-ai/chipzen-sdk/issues/41))
87
+ - **External-API: a mid-match gateway disconnect no longer silently
88
+ forfeits the match.** `runExternalBot()` reconnects a dropped per-match
89
+ gateway socket (bounded by the `RetryPolicy`) and resumes via the
90
+ platform's reconnect-resume — `_runSession` consumes the server
91
+ `reconnected` frame and replays the pending turn, and the bot instance
92
+ keeps its state across the gap. The reconnect budget is bounded, so an
93
+ unrecoverable match is abandoned (`end: null`) rather than hanging.
94
+ - **External-API: in-flight matches survive a lobby reconnect, and no
95
+ match task is orphaned on teardown.** Match-task ownership is hoisted to
96
+ the top-level `runExternalBot` (not the per-lobby-session), so a lobby
97
+ blip no longer abandons a match playing on its own gateway socket; on
98
+ teardown, still-running matches get a short grace window then are
99
+ drained and awaited.
100
+
101
+ ### Added (already shipped pre-0.3.0, in conformance)
102
+
103
+ - Three new conformance scenarios in `validate --check-connectivity`,
104
+ bringing the total from 1 to 4. The previously-shipped scenario only
105
+ covered a clean handshake + 1 hand + match_end; bots could pass it
106
+ and still crash in production. The new scenarios are:
107
+ - `multi_turn_request_id_echo` — drives 3 `turn_request`s across
108
+ preflop/flop/turn and verifies the SDK echoes each `request_id`
109
+ correctly (the previous harness only checked the first action).
110
+ - `action_rejected_recovery` — verifies the SDK retries with a
111
+ safe-fallback `check`/`fold` and the original `request_id` when the
112
+ server sends `action_rejected` (a routine production code path
113
+ that had no harness coverage).
114
+ - `retry_storm_bounded` — verifies the SDK responds reactively to 3
115
+ back-to-back `action_rejected` messages without hanging or entering
116
+ an unbounded send loop.
117
+ - Closes part of
118
+ [#28](https://github.com/chipzen-ai/chipzen-sdk/issues/28) for the
119
+ JavaScript SDK.
120
+ - Public `SCENARIOS` export from `conformance.ts` listing each
121
+ scenario name and runner function. Lets tests and downstream tooling
122
+ enumerate the registered scenarios without parsing CLI output.
123
+
124
+ ### Documentation
125
+
126
+ - `chipzen-sdk validate --help` now enumerates all 4 conformance
127
+ scenarios and notes that the validator is a courtesy linter — the
128
+ authoritative gate is server-side seccomp + cap-drop. Closes part of
129
+ [#28](https://github.com/chipzen-ai/chipzen-sdk/issues/28).
130
+ - Documented a known limitation in `runConformanceChecks`: the
131
+ JavaScript harness does not yet include a hard wall-clock watchdog
132
+ against bots that synchronously block the event loop (busy-loop,
133
+ `Atomics.wait`). The Python SDK has a daemon-thread watchdog; the
134
+ JS equivalent (a Worker) is heavier-weight and deferred.
135
+
136
+ ## [0.2.0] — Initial public release
137
+
138
+ First release of `@chipzen-ai/bot` to npm. Mirrors the Python SDK's
139
+ shape (see [`packages/python/CHANGELOG.md`](../python/CHANGELOG.md))
140
+ so a developer using either language sees the same command surface
141
+ and protocol behavior.
142
+
143
+ ### Scope
144
+
145
+ The published SDK is intentionally narrow:
146
+
147
+ 1. A **protocol adapter** (`Bot` base class plus the WebSocket client)
148
+ so your bot doesn't hand-roll the wire protocol.
149
+ 2. A **`chipzen-sdk validate`** CLI that runs the same pre-upload
150
+ checks the platform performs (size, imports, sandbox-blocked
151
+ modules, `decide()` timeout sniff, optional protocol-conformance
152
+ harness via `--check-connectivity`).
153
+ 3. An **IP-protected Dockerfile recipe** at
154
+ [`starters/javascript/`](starters/javascript/) — multi-stage Bun
155
+ build (`bun build --compile`) that ships a single statically-
156
+ linked binary, not your `.js` source. See
157
+ [`IP-PROTECTION.md`](IP-PROTECTION.md).
158
+
159
+ Local match simulation, hand evaluation, opponent pools, and
160
+ bot-vs-bot strength testing are explicitly out of scope; the platform
161
+ runs that evaluation post-upload.
162
+
163
+ ### CLI surface
164
+
165
+ Two commands. Both have detailed `--help` output.
166
+
167
+ - `chipzen-sdk init <name>` — scaffold a new bot project from the
168
+ IP-protected starter template. Emits `bot.js`, `package.json`
169
+ (depends on `@chipzen-ai/bot`), `Dockerfile` (real
170
+ `bun build --compile` recipe, byte-identical to the canonical
171
+ starter), `.dockerignore`, `.gitignore`, `README.md`.
172
+ - `chipzen-sdk validate <path>` — pre-upload go/no-go. Add
173
+ `--check-connectivity` to also drive the bot through one canned
174
+ full-match exchange.
175
+
176
+ ### Public API (re-exported from `@chipzen-ai/bot`)
177
+
178
+ - **`Bot`** — abstract base class. Override `decide(state) -> Action`.
179
+ Optional lifecycle hooks: `onMatchStart`, `onRoundStart`,
180
+ `onPhaseChange`, `onTurnResult`, `onRoundResult`, `onMatchEnd`.
181
+ - **`Action`** — class with `private constructor` + static factories:
182
+ `Action.fold()`, `Action.check()`, `Action.call()`,
183
+ `Action.raiseTo(amount)`, `Action.allIn()`. `action.toWire()`
184
+ produces the two-layer `turn_action` params schema.
185
+ - **`Card`**, **`GameState`**, **`ActionHistoryEntry`**, **`ActionKind`**
186
+ — types mirroring the wire schema. `parseGameState(message)` and
187
+ `cardFromString("Ah")` bridge the snake_case wire format.
188
+ - **`runBot(url, bot, options)`** — async runner driving the full
189
+ WebSocket lifecycle (handshake, envelope sequence check,
190
+ ping/pong, `action_rejected` retry, reconnect with bounded
191
+ exponential backoff, clean exit on `match_end`).
192
+ - **`runConformanceChecks(bot, options)`** — drives a bot through
193
+ the canned full-match exchange against an in-process mock socket;
194
+ same severity model as the Python harness. Surfaced via the CLI's
195
+ `--check-connectivity` flag.
196
+ - **`SUPPORTED_PROTOCOL_VERSIONS`** — `["1.0"]` baseline.
197
+
198
+ ### Two-layer wire protocol
199
+
200
+ The client speaks the same Chipzen two-layer protocol the Python
201
+ SDK does, defined in
202
+ [`docs/protocol/TRANSPORT-PROTOCOL.md`](https://github.com/chipzen-ai/chipzen-sdk/blob/main/docs/protocol/TRANSPORT-PROTOCOL.md)
203
+ and
204
+ [`docs/protocol/POKER-GAME-STATE-PROTOCOL.md`](https://github.com/chipzen-ai/chipzen-sdk/blob/main/docs/protocol/POKER-GAME-STATE-PROTOCOL.md).
205
+
206
+ ### Packaging
207
+
208
+ - Dual ESM + CJS via [tsup](https://tsup.egoist.dev/), with `.d.ts`
209
+ for both module systems.
210
+ - CLI binary `chipzen-sdk` ships as `dist/bin.js` with a shebang
211
+ prepended by the tsup banner.
212
+ - Published with **npm Trusted Publishing (sigstore-attested
213
+ provenance)** — see [`RELEASING.md`](RELEASING.md).
214
+
215
+ ### License
216
+
217
+ Apache-2.0.
218
+
219
+ [0.3.0]: https://github.com/chipzen-ai/chipzen-sdk/releases/tag/javascript-v0.3.0
220
+ [0.2.0]: https://github.com/chipzen-ai/chipzen-sdk/releases/tag/javascript-v0.2.0
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @chipzen-ai/bot
2
+
3
+ > [!WARNING]
4
+ > **Alpha software.** This SDK is in active development; the public
5
+ > API may change between minor versions before 1.0. Pin to a specific
6
+ > version in production. Report issues at
7
+ > [chipzen-ai/chipzen-sdk/issues](https://github.com/chipzen-ai/chipzen-sdk/issues).
8
+
9
+ The JavaScript adapter for the [Chipzen](https://chipzen.ai) AI poker
10
+ competition platform. Wraps the WebSocket protocol so your bot only
11
+ has to implement `decide(state) -> action`.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @chipzen-ai/bot # or pnpm / yarn
17
+ ```
18
+
19
+ Node 20+ supported. Also runs cleanly on Bun (the IP-protected starter
20
+ Dockerfile uses `bun build --compile` to ship your bot as a single
21
+ binary). Single runtime dependency: `ws`.
22
+
23
+ ## Minimal bot
24
+
25
+ ```typescript
26
+ import { Bot, Action, GameState, runBot } from "@chipzen-ai/bot";
27
+
28
+ class MyBot extends Bot {
29
+ decide(state: GameState): Action {
30
+ if (state.validActions.includes("check")) return Action.check();
31
+ return Action.fold();
32
+ }
33
+ }
34
+
35
+ await runBot(process.env.CHIPZEN_WS_URL!, new MyBot(), {
36
+ token: process.env.CHIPZEN_TOKEN,
37
+ });
38
+ ```
39
+
40
+ The SDK handles the Layer-1 transport handshake, Layer-2 game-state
41
+ parsing, ping/pong, request-id echoing, `action_rejected` retries,
42
+ and reconnect. Subclass `Bot`, override `decide()`, return an
43
+ `Action`. That's the entire surface for a working bot.
44
+
45
+ Lifecycle hooks (`onMatchStart`, `onRoundStart`, `onPhaseChange`,
46
+ `onTurnResult`, `onRoundResult`, `onMatchEnd`) are optional —
47
+ override them if you need to maintain per-match or per-hand state
48
+ between turns.
49
+
50
+ ## Field naming
51
+
52
+ The user-facing API uses idiomatic camelCase
53
+ (`state.validActions`, `state.holeCards`). The on-the-wire JSON the
54
+ protocol uses is snake_case (`valid_actions`, `your_hole_cards`) —
55
+ defined in the
56
+ [protocol spec](https://github.com/chipzen-ai/chipzen-sdk/tree/main/docs/protocol).
57
+ The parser in `parseGameState` translates between the two.
58
+
59
+ ## What the SDK is for (and what it isn't)
60
+
61
+ The SDK does three things and nothing else:
62
+
63
+ 1. **Protocol adapter** — your bot doesn't hand-roll WebSockets.
64
+ 2. **`chipzen-sdk validate`** (Phase 2 PR 2) — pre-upload conformance
65
+ check, equivalent to the Python CLI.
66
+ 3. **(Phase 2 PR 3) IP-protected Dockerfile recipe** — `bun build
67
+ --compile` multi-stage build that produces a single executable
68
+ instead of shipping your `.ts`/`.js` source.
69
+
70
+ It does **not** include a local match simulator, hand evaluator, or
71
+ opponent pool. Bot strength testing happens after upload; the
72
+ platform runs comprehensive bot-vs-bot evaluation as part of the
73
+ submission pipeline.
74
+
75
+ ## Reference
76
+
77
+ Full developer documentation lives in the
78
+ [chipzen-sdk repo](https://github.com/chipzen-ai/chipzen-sdk):
79
+
80
+ - [DEV-MANUAL](https://github.com/chipzen-ai/chipzen-sdk/blob/main/docs/DEV-MANUAL.md)
81
+ — SDK reference, lifecycle hooks, performance budgets, container
82
+ contract, troubleshooting.
83
+ - [QUICKSTART](https://github.com/chipzen-ai/chipzen-sdk/blob/main/docs/QUICKSTART.md)
84
+ — write a bot, validate it, package it.
85
+ - [Protocol spec](https://github.com/chipzen-ai/chipzen-sdk/tree/main/docs/protocol)
86
+ — Layer 1 (Transport) + Layer 2 (Poker game state). Authoritative.
87
+ - [Bot runtime security model](https://github.com/chipzen-ai/chipzen-sdk/blob/main/SECURITY.md#bot-runtime--what-the-platform-enforces-on-uploaded-bots)
88
+ — what the platform enforces on uploaded bots.
89
+ - [Python adapter](https://github.com/chipzen-ai/chipzen-sdk/tree/main/packages/python)
90
+ — same SDK shape in Python (pip install `chipzen-bot`).
91
+
92
+ ## License
93
+
94
+ Apache-2.0. See the
95
+ [LICENSE](https://github.com/chipzen-ai/chipzen-sdk/blob/main/LICENSE)
96
+ file in the repo.