@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 +220 -0
- package/README.md +96 -0
- package/dist/bin.js +2545 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.cjs +2310 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +746 -0
- package/dist/index.d.ts +746 -0
- package/dist/index.js +2244 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/run_external.ts","../src/bot.ts","../src/config.ts","../src/retry.ts","../src/connect.ts","../src/external.ts","../src/client.ts","../src/models.ts","../package.json","../src/version.ts","../src/scaffold.ts","../src/validate.ts","../src/conformance.ts","../src/bin.ts"],"sourcesContent":["/**\n * CLI dispatcher for `chipzen-sdk` (init / validate).\n *\n * Mirrors the Python CLI's two-command surface. Uses Node's built-in\n * `parseArgs` so we don't pull in commander/yargs as a dep.\n */\n\nimport { parseArgs } from \"node:util\";\nimport path from \"node:path\";\n\nimport { runExternalCli } from \"./run_external.js\";\nimport { scaffoldBot } from \"./scaffold.js\";\nimport {\n type ValidationResult,\n validateBot,\n DEFAULT_MAX_UPLOAD_BYTES,\n DEFAULT_TIMEOUT_WARN_MS,\n} from \"./validate.js\";\n\nconst COMMANDS = {\n init: \"Scaffold a new bot project from a starter template\",\n validate: \"Run pre-upload checks: size, syntax, imports, smoke test, timeout\",\n \"run-external\":\n \"Run a bot on the external-API remote-play path (lobby -> matched -> play)\",\n} as const;\n\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<void> {\n if (argv.length === 0 || argv[0] === \"--help\" || argv[0] === \"-h\") {\n printHelp();\n process.exit(argv.length === 0 ? 1 : 0);\n }\n\n const [command, ...rest] = argv;\n switch (command) {\n case \"init\":\n await initCli(rest);\n return;\n case \"validate\":\n await validateCli(rest);\n return;\n case \"run-external\":\n await runExternalCli(rest);\n return;\n default:\n console.error(`Unknown command: ${command}`);\n console.error(\"\");\n printHelp();\n process.exit(1);\n }\n}\n\nfunction printHelp(): void {\n console.log(\"Chipzen Poker Bot SDK\");\n console.log(\"\");\n console.log(\"Usage: chipzen-sdk <command> [options]\");\n console.log(\"\");\n console.log(\"Commands:\");\n for (const [name, desc] of Object.entries(COMMANDS)) {\n console.log(` ${name.padEnd(14)} ${desc}`);\n }\n console.log(\"\");\n console.log(\"Run 'chipzen-sdk <command> --help' for details on a specific command.\");\n}\n\n// ---------------------------------------------------------------------------\n// init\n// ---------------------------------------------------------------------------\n\nasync function initCli(args: string[]): Promise<void> {\n if (args[0] === \"--help\" || args[0] === \"-h\") {\n console.log(\"Usage: chipzen-sdk init <name> [--dir <parent>]\");\n console.log(\"\");\n console.log(\"Scaffold a new Chipzen bot project under <parent>/<name>.\");\n console.log(\"Default parent is the current directory.\");\n return;\n }\n\n const { values, positionals } = parseArgs({\n args,\n options: { dir: { type: \"string\" } },\n allowPositionals: true,\n });\n\n const name = positionals[0];\n if (!name) {\n console.error(\"error: chipzen-sdk init requires a <name> positional argument\");\n process.exit(2);\n }\n\n try {\n const created = await scaffoldBot(name, { parentDir: values.dir });\n console.log(`Created bot project: ${created}`);\n console.log(\"\");\n console.log(\"Next steps:\");\n console.log(` cd ${name}`);\n console.log(\" npm install\");\n console.log(\" # Edit bot.js to implement your strategy\");\n console.log(\" chipzen-sdk validate .\");\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// validate\n// ---------------------------------------------------------------------------\n\nasync function validateCli(args: string[]): Promise<void> {\n if (args[0] === \"--help\" || args[0] === \"-h\") {\n console.log(\"Usage: chipzen-sdk validate <path> [options]\");\n console.log(\"\");\n console.log(\"Pre-upload validation. Checks performed:\");\n console.log(\" size Directory size within upload limits\");\n console.log(\" file_structure Entry point file (bot.js / bot.mjs / bot.cjs / main.js)\");\n console.log(\" syntax Valid JavaScript via `node --check`\");\n console.log(\" imports No blocked sandbox modules; warn on fs/fs-promises\");\n console.log(\" bot_class Class extending Bot or ChipzenBot exists\");\n console.log(\" decide_method Bot class implements decide()\");\n console.log(\" smoke_test Bot can be instantiated; decide() returns an Action\");\n console.log(\" timeout decide() completes within time limits\");\n console.log(\"\");\n console.log(\"With --check-connectivity, four protocol scenarios also run:\");\n console.log(\" connectivity_full_match Drive 1 canned match end-to-end\");\n console.log(\" multi_turn_request_id_echo Drive 3 turn_requests across phases\");\n console.log(\" and verify request_id echo on each\");\n console.log(\" action_rejected_recovery Verify safe-fallback retry on rejection\");\n console.log(\" retry_storm_bounded Verify reactive response to 3 back-to-back\");\n console.log(\" action_rejected messages\");\n console.log(\"\");\n console.log(\"The validator is a courtesy linter -- the authoritative gate is\");\n console.log(\"server-side seccomp + cap-drop on the bot container.\");\n console.log(\"\");\n console.log(\"Options:\");\n console.log(\" --entry-point <name> Override entry-point filename\");\n console.log(\" --max-size-mb <int> Max upload size in MB (default: \" +\n `${DEFAULT_MAX_UPLOAD_BYTES / (1024 * 1024)})`);\n console.log(\" --timeout-warn-ms <int> Warn-threshold for decide() in ms (default: \" +\n `${DEFAULT_TIMEOUT_WARN_MS})`);\n console.log(\" --check-connectivity Run the 4 protocol-conformance scenarios\");\n console.log(\" --no-color Disable colored output\");\n return;\n }\n\n const { values, positionals } = parseArgs({\n args,\n options: {\n \"entry-point\": { type: \"string\" },\n \"max-size-mb\": { type: \"string\" },\n \"timeout-warn-ms\": { type: \"string\" },\n \"check-connectivity\": { type: \"boolean\" },\n \"no-color\": { type: \"boolean\" },\n },\n allowPositionals: true,\n });\n\n const target = positionals[0];\n if (!target) {\n console.error(\"error: chipzen-sdk validate requires a <path> positional argument\");\n process.exit(2);\n }\n\n const maxBytes = values[\"max-size-mb\"]\n ? parseInt(values[\"max-size-mb\"], 10) * 1024 * 1024\n : DEFAULT_MAX_UPLOAD_BYTES;\n const timeoutWarn = values[\"timeout-warn-ms\"]\n ? parseInt(values[\"timeout-warn-ms\"], 10)\n : DEFAULT_TIMEOUT_WARN_MS;\n\n const results = await validateBot(path.resolve(target), {\n entryPoint: values[\"entry-point\"],\n maxUploadBytes: maxBytes,\n timeoutWarnMs: timeoutWarn,\n checkConnectivity: values[\"check-connectivity\"] ?? false,\n });\n\n printResults(results, !values[\"no-color\"]);\n\n if (results.some((r) => r.severity === \"fail\")) process.exit(1);\n}\n\nfunction printResults(results: ValidationResult[], color: boolean): void {\n const supportsColor = color && process.stdout.isTTY;\n const GREEN = supportsColor ? \"\\x1b[92m\" : \"\";\n const YELLOW = supportsColor ? \"\\x1b[93m\" : \"\";\n const RED = supportsColor ? \"\\x1b[91m\" : \"\";\n const RESET = supportsColor ? \"\\x1b[0m\" : \"\";\n\n console.log(\"\");\n console.log(\"Chipzen Bot Validation\");\n console.log(\"=\".repeat(50));\n for (const r of results) {\n const icon =\n r.severity === \"pass\"\n ? `${GREEN}PASS${RESET}`\n : r.severity === \"warn\"\n ? `${YELLOW}WARN${RESET}`\n : `${RED}FAIL${RESET}`;\n console.log(` [${icon}] ${r.name}: ${r.message}`);\n }\n\n console.log(\"\");\n const fails = results.filter((r) => r.severity === \"fail\").length;\n if (fails > 0) {\n console.log(`${RED}${fails} check${fails === 1 ? \"\" : \"s\"} failed.${RESET}`);\n } else {\n console.log(`${GREEN}All checks passed! Your bot is ready to upload.${RESET}`);\n }\n}\n","/**\n * `chipzen-sdk run-external` CLI wrapper for external-API bots.\n *\n * Mirrors the Python SDK's `chipzen.run_external` (External-API Issue 25,\n * chipzen-ai/chipzen-sdk#44). Wraps the boilerplate of \"load config,\n * connect, run bot\" so a dev's main loop is just:\n *\n * chipzen-sdk run-external my-bot.js\n *\n * The wrapper:\n *\n * 1. Loads token + url + bot_id from a discovered `chipzen.toml`.\n * 2. Resolves the env-aware lobby URL via `connectToChipzen` when no\n * explicit `url` is set in `chipzen.toml`.\n * 3. Dynamically imports the user's bot module from a filesystem path.\n * 4. Discovers the `Bot` subclass exported by that file (single subclass\n * auto-selected; multiple require `--bot-class <name>`).\n * 5. Hands off to `runExternalBot` with the resolved url + token + policy.\n *\n * Precedence (highest first):\n *\n * - **token**: `--token` > `[external_api].token`.\n * - **url**: `[external_api].url` > env-derived URL from bot_id + env.\n * - **env**: `--env` > `$CHIPZEN_ENV` > `prod`.\n * - **bot_id**: `--bot-id` > `[external_api].bot_id`. Required only when no\n * explicit URL is configured.\n */\n\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { Bot } from \"./bot.js\";\nimport {\n type ChipzenConfig,\n ChipzenConfigError,\n loadChipzenConfig,\n} from \"./config.js\";\nimport { connectToChipzen, type EnvName, ENV_NAMES } from \"./connect.js\";\nimport { DEFAULT_RETRY_POLICY, type RetryPolicy } from \"./retry.js\";\nimport { runExternalBot } from \"./external.js\";\n\n/** Parsed `run-external` arguments. */\nexport interface RunExternalArgs {\n botFile: string;\n env: EnvName | null;\n token: string | null;\n botId: string | null;\n botClass: string | null;\n maxMatches: number | null;\n safeMode: boolean;\n}\n\n/**\n * Parse `run-external` argv (excluding the `run-external` token itself).\n *\n * Flags mirror the Python CLI: `--env`, `--token`, `--bot-id`,\n * `--bot-class`, `--max-matches`, `--no-safe-mode`. Throws on an unknown\n * flag, a missing value, or a bad `--env` choice so the caller can map it\n * to a clean non-zero exit.\n */\nexport function parseRunExternalArgs(args: string[]): RunExternalArgs {\n const out: RunExternalArgs = {\n botFile: \"\",\n env: null,\n token: null,\n botId: null,\n botClass: null,\n maxMatches: null,\n safeMode: true,\n };\n const positionals: string[] = [];\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n const takeValue = (name: string): string => {\n const value = args[++i];\n if (value === undefined) throw new Error(`${name} requires a value`);\n return value;\n };\n switch (arg) {\n case \"--env\": {\n const value = takeValue(\"--env\");\n if (!(ENV_NAMES as readonly string[]).includes(value)) {\n throw new Error(\n `--env must be one of ${ENV_NAMES.join(\", \")} (got ${JSON.stringify(value)})`,\n );\n }\n out.env = value as EnvName;\n break;\n }\n case \"--token\":\n out.token = takeValue(\"--token\");\n break;\n case \"--bot-id\":\n out.botId = takeValue(\"--bot-id\");\n break;\n case \"--bot-class\":\n out.botClass = takeValue(\"--bot-class\");\n break;\n case \"--max-matches\": {\n const value = takeValue(\"--max-matches\");\n const n = Number.parseInt(value, 10);\n if (!Number.isFinite(n)) {\n throw new Error(`--max-matches must be an integer (got ${JSON.stringify(value)})`);\n }\n out.maxMatches = n;\n break;\n }\n case \"--no-safe-mode\":\n out.safeMode = false;\n break;\n default:\n if (arg.startsWith(\"-\")) {\n throw new Error(`Unknown option: ${arg}`);\n }\n positionals.push(arg);\n break;\n }\n }\n\n const botFile = positionals[0];\n if (!botFile) {\n throw new Error(\"run-external requires a <bot-file> positional argument\");\n }\n out.botFile = botFile;\n return out;\n}\n\n/**\n * Dynamically import a bot module from a filesystem path.\n *\n * Uses a `file://` URL so the import works regardless of cwd. Supports\n * `.js` / `.mjs` / `.cjs` modules (whatever Node's loader resolves).\n *\n * @throws Error if the file cannot be loaded (missing, syntax error,\n * missing dependency). The message names the file.\n */\nexport async function loadBotModule(botFile: string): Promise<Record<string, unknown>> {\n const abs = path.resolve(botFile);\n try {\n // Cache-bust so repeated loads of the same path (tests) re-evaluate.\n const url = `${pathToFileURL(abs).href}?t=${Date.now()}`;\n return (await import(url)) as Record<string, unknown>;\n } catch (err) {\n throw new Error(`Failed to load ${botFile}: ${(err as Error).message}`);\n }\n}\n\n/** A Bot subclass constructor (zero-arg construction). */\nexport type BotConstructor = new () => Bot;\n\n/**\n * Return all `Bot` subclass constructors exported by `module`.\n *\n * Scans the module's exported values for classes whose prototype chain\n * includes {@link Bot} (and that aren't `Bot` itself). Both `export class`\n * and `export default class` are considered.\n */\nexport function findBotSubclasses(module: Record<string, unknown>): BotConstructor[] {\n const found: BotConstructor[] = [];\n const seen = new Set<unknown>();\n for (const value of Object.values(module)) {\n if (typeof value !== \"function\") continue;\n if (value === Bot) continue;\n if (seen.has(value)) continue;\n // A subclass of Bot: Bot is in its prototype chain.\n if (value.prototype instanceof Bot) {\n seen.add(value);\n found.push(value as BotConstructor);\n }\n }\n return found;\n}\n\n/**\n * Pick a single Bot subclass from the discovered list.\n *\n * @throws Error if no candidates exist, the explicit name doesn't match,\n * or multiple candidates exist without an explicit pick. The message\n * lists the options.\n */\nexport function selectBotClass(\n candidates: BotConstructor[],\n botClassName: string | null,\n botFile: string,\n): BotConstructor {\n if (botClassName !== null) {\n const matches = candidates.filter((c) => c.name === botClassName);\n if (matches.length === 0) {\n const available = candidates.map((c) => c.name).join(\", \") || \"<none>\";\n throw new Error(\n `No Bot subclass named ${JSON.stringify(botClassName)} in ${botFile}. ` +\n `Available subclasses: ${available}.`,\n );\n }\n return matches[0]!;\n }\n\n if (candidates.length === 0) {\n throw new Error(\n `No Bot subclass found in ${botFile}. Export a class that extends ` +\n `Bot (e.g. export class MyBot extends Bot { ... }).`,\n );\n }\n\n if (candidates.length > 1) {\n const names = candidates.map((c) => c.name).join(\", \");\n throw new Error(\n `Multiple Bot subclasses found in ${botFile}: ${names}. ` +\n `Pick one with --bot-class <name>.`,\n );\n }\n\n return candidates[0]!;\n}\n\n/**\n * Resolve `(url, token, retryPolicy, config)` for `runExternalBot`.\n *\n * 1. A verbatim `[external_api].url` in config wins outright; token comes\n * from `--token` > config token.\n * 2. Otherwise the URL is env-derived: need a `bot_id` (`--bot-id` >\n * config) and call `connectToChipzen` to build the lobby URL.\n *\n * @throws Error if neither a config URL nor a bot_id is available.\n */\nexport function resolveConnection(opts: {\n config: ChipzenConfig | null;\n env: EnvName | null;\n token: string | null;\n botId: string | null;\n retryPolicy?: RetryPolicy;\n}): { url: string; token: string | null; retryPolicy: RetryPolicy; config: ChipzenConfig | null } {\n const policy = opts.retryPolicy ?? DEFAULT_RETRY_POLICY;\n\n // Branch 1: verbatim URL in config wins outright. No bot_id resolution\n // and no env mapping needed.\n const configUrl = opts.config ? opts.config.url : null;\n if (configUrl !== null) {\n const token = opts.token !== null ? opts.token : opts.config ? opts.config.token : null;\n return { url: configUrl, token, retryPolicy: policy, config: opts.config };\n }\n\n const botId = opts.botId ?? (opts.config ? opts.config.botId : null);\n if (!botId) {\n throw new Error(\n \"No lobby URL is configured. Either:\\n\" +\n \" - Pass --bot-id <id> on the command line, or\\n\" +\n \" - Set [external_api].bot_id in chipzen.toml, or\\n\" +\n \" - Set [external_api].url in chipzen.toml for a verbatim URL.\",\n );\n }\n\n const conn = connectToChipzen(botId, opts.env, {\n retryPolicy: policy,\n config: opts.config,\n });\n const token = opts.token !== null ? opts.token : conn.token;\n return { url: conn.url, token, retryPolicy: conn.retryPolicy, config: conn.config };\n}\n\n/** Print `run-external` help text. */\nexport function printRunExternalHelp(): void {\n console.log(\"Usage: chipzen-sdk run-external <bot-file> [options]\");\n console.log(\"\");\n console.log(\"Run a Chipzen external-API bot from a JavaScript file. Loads config\");\n console.log(\"from chipzen.toml, resolves the env-aware lobby URL, and plays via\");\n console.log(\"the SDK's runExternalBot() entry point.\");\n console.log(\"\");\n console.log(\"Options:\");\n console.log(\" --env <prod|staging|local> Target environment. Defaults to $CHIPZEN_ENV, else prod.\");\n console.log(\" --token <cz_extbot_...> External-API token. Overrides [external_api].token.\");\n console.log(\" --bot-id <uuid> External-API bot UUID. Overrides [external_api].bot_id.\");\n console.log(\" --bot-class <name> Bot subclass to run when the file exports more than one.\");\n console.log(\" --max-matches <int> Stop after this many matches. Default: run until lobby closes.\");\n console.log(\" --no-safe-mode Let a decide() error crash the process (exit non-zero).\");\n console.log(\"\");\n console.log(\"Examples:\");\n console.log(\" chipzen-sdk run-external my-bot.js\");\n console.log(\" chipzen-sdk run-external my-bot.js --env staging\");\n console.log(\" CHIPZEN_ENV=staging chipzen-sdk run-external my-bot.js\");\n console.log(\" chipzen-sdk run-external my-bot.js --token cz_extbot_xyz --bot-id abc123\");\n console.log(\" chipzen-sdk run-external my-bot.js --bot-class TightAggressive\");\n}\n\n/**\n * CLI entry point for `chipzen-sdk run-external`.\n *\n * Exits the process via `process.exit` on error (code 2 for setup errors,\n * code 1 for a bot-run failure) so a harness / CI sees the failure.\n */\nexport async function runExternalCli(args: string[]): Promise<void> {\n if (args[0] === \"--help\" || args[0] === \"-h\") {\n printRunExternalHelp();\n return;\n }\n\n let parsed: RunExternalArgs;\n try {\n parsed = parseRunExternalArgs(args);\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exit(2);\n }\n\n // Discover chipzen.toml once up front.\n let config: ChipzenConfig | null;\n try {\n config = loadChipzenConfig();\n } catch (err) {\n if (err instanceof ChipzenConfigError) {\n console.error(`error: invalid chipzen.toml: ${err.message}`);\n process.exit(2);\n }\n throw err;\n }\n\n // Resolve url + token + retry policy.\n let resolved;\n try {\n resolved = resolveConnection({\n config,\n env: parsed.env,\n token: parsed.token,\n botId: parsed.botId,\n });\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exit(2);\n }\n\n // Dynamically import the user's bot module + pick a Bot subclass.\n let module: Record<string, unknown>;\n try {\n module = await loadBotModule(parsed.botFile);\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exit(2);\n }\n\n let BotClass: BotConstructor;\n try {\n BotClass = selectBotClass(findBotSubclasses(module), parsed.botClass, parsed.botFile);\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exit(2);\n }\n\n let botInstance: Bot;\n try {\n botInstance = new BotClass();\n } catch (err) {\n console.error(`error: failed to instantiate ${BotClass.name}: ${(err as Error).message}`);\n process.exit(2);\n }\n\n console.error(`Connecting ${BotClass.name} -> ${resolved.url}`);\n\n try {\n await runExternalBot(botInstance, {\n url: resolved.url,\n token: resolved.token,\n retryPolicy: resolved.retryPolicy,\n config: resolved.config,\n safeMode: parsed.safeMode,\n maxMatches: parsed.maxMatches,\n });\n } catch (err) {\n // Includes BotDecisionError from --no-safe-mode: a bot bug should exit\n // non-zero so a harness / CI sees the failure.\n console.error(`error: bot run failed: ${(err as Error).message}`);\n process.exit(1);\n }\n}\n","/**\n * `Bot` abstract base class.\n *\n * Subclass and override `decide()`. Lifecycle hooks\n * (`onMatchStart` / `onRoundStart` / `onPhaseChange` / `onTurnResult` /\n * `onRoundResult` / `onMatchEnd`) are optional — defaults are no-ops.\n * Override the ones you need; the SDK's session loop will call them\n * at the right point.\n */\n\nimport type { Action, GameState } from \"./models.js\";\n\nexport abstract class Bot {\n /**\n * The only required override. Called every time the server sends a\n * `turn_request` for your seat. Return one of:\n * `Action.fold()`, `Action.check()`, `Action.call()`,\n * `Action.raiseTo(amount)`, `Action.allIn()`.\n *\n * The returned action's wire-form `action` string MUST be in\n * `state.validActions`; raise amounts MUST satisfy\n * `state.minRaise <= amount <= state.maxRaise`. If the bot returns\n * an illegal action, the SDK's safe-fallback substitutes `check`\n * (or `fold` if check is illegal) and the platform sends a\n * `bot_error` event to the human's UI.\n */\n abstract decide(state: GameState): Action;\n\n /** Called once when the `match_start` message arrives. */\n onMatchStart(_matchInfo: Record<string, unknown>): void {\n /* default: no-op */\n }\n\n /**\n * Called at the start of every hand with the raw `round_start` message.\n *\n * Override if you need the Layer 1 envelope (`round_id`,\n * `round_number`) or the full Layer 2 `state` payload. For most bots,\n * `onHandStart` is the simpler hook.\n */\n onRoundStart(_message: Record<string, unknown>): void {\n /* default: no-op */\n }\n\n /**\n * Called when the flop, turn, or river is dealt. Useful for triggering\n * postflop planning *between* your turns rather than inside `decide`.\n */\n onPhaseChange(_message: Record<string, unknown>): void {\n /* default: no-op */\n }\n\n /**\n * Called after every participant's action is broadcast — yours and\n * every opponent's. Use for opponent modeling, timing analysis, or\n * stack tracking.\n *\n * This hook runs *before* the next `turn_request` is dispatched, and\n * runs serially. Slow work here eats into your decide budget — see\n * the DEV-MANUAL §6 for the \"queue drain\" failure mode.\n */\n onTurnResult(_message: Record<string, unknown>): void {\n /* default: no-op */\n }\n\n /** Called when a hand ends (`round_result` message). */\n onRoundResult(_message: Record<string, unknown>): void {\n /* default: no-op */\n }\n\n /** Called once when the match ends. */\n onMatchEnd(_results: Record<string, unknown>): void {\n /* default: no-op */\n }\n\n /**\n * Called after each `turn_action` is sent, with the wall-clock time\n * `decide()` took in milliseconds.\n *\n * Default is a no-op. Override to track decision latency — useful for\n * spotting when your bot is drifting toward the platform's turn-timeout\n * budget. See chipzen-ai/chipzen-sdk#46.\n */\n onDecisionLatency(_latencyMs: number): void {\n /* default: no-op */\n }\n}\n","/**\n * `chipzen.toml` discovery and parsing for the SDK.\n *\n * Devs running an external-API bot should be able to drop their long-lived\n * API token into a config file once and forget about it, instead of\n * hard-coding `token=\"cz_extbot_...\"` into source. This module implements\n * the discovery + parsing half of that convention; `runExternalBot` (and\n * the `chipzen-sdk run-external` CLI) consume the result and prefer\n * explicit kwargs over config-file values.\n *\n * Mirrors the Python SDK's `chipzen.config` (External-API Issue 23,\n * chipzen-ai/chipzen-sdk#42).\n *\n * Discovery\n * ---------\n *\n * Search order, first match wins:\n *\n * 1. `./chipzen.toml` (current working directory)\n * 2. `~/.chipzen/chipzen.toml` (user-home config)\n * 3. `/etc/chipzen/chipzen.toml` (system config, POSIX only — silently\n * skipped on Windows where `/etc` does not exist)\n *\n * If no file is found, `loadChipzenConfig` returns `null` and the caller\n * falls back to whatever explicit arguments were passed. A clear error is\n * only raised when a file IS found but is malformed or missing the\n * expected section.\n *\n * File format\n * -----------\n *\n * [external_api]\n * token = \"cz_extbot_<32-char-base62-random>\"\n * url = \"wss://chipzen.ai/ws/external/bot/<bot_id>\" # optional\n * bot_id = \"<bot-uuid>\" # optional\n *\n * All three fields are optional and must be quoted strings. We parse the\n * single `[external_api]` table with a minimal inline reader rather than\n * pulling in a TOML dependency — keeping the package's single runtime dep\n * (`ws`).\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport const CONFIG_FILENAME = \"chipzen.toml\";\nexport const SECTION_NAME = \"external_api\";\n\n/**\n * Parsed contents of a `chipzen.toml` file. All `[external_api]` fields\n * are optional; absence yields `null`.\n */\nexport interface ChipzenConfig {\n /** Filesystem path the config was loaded from (for error messages). */\n readonly path: string;\n /** Value of `[external_api] token` if present, else `null`. */\n readonly token: string | null;\n /** Value of `[external_api] url` if present, else `null`. */\n readonly url: string | null;\n /** Value of `[external_api] bot_id` if present, else `null`. */\n readonly botId: string | null;\n}\n\n/**\n * Raised when a `chipzen.toml` is found but cannot be used (malformed,\n * missing the `[external_api]` section, or a field is the wrong type).\n *\n * A \"found but unusable\" file is always a hard error — silent fallback\n * would mask typos that would otherwise be obvious.\n */\nexport class ChipzenConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ChipzenConfigError\";\n }\n}\n\n/**\n * Return the ordered list of candidate config-file locations.\n *\n * 1. `./chipzen.toml` in the current working directory.\n * 2. `~/.chipzen/chipzen.toml` (user home).\n * 3. `/etc/chipzen/chipzen.toml` — POSIX only; omitted on Windows where\n * `/etc` is not a meaningful path (the home-dir entry is enough for\n * the typical Windows dev workflow).\n */\nexport function searchPaths(): string[] {\n const paths = [\n path.join(process.cwd(), CONFIG_FILENAME),\n path.join(os.homedir(), \".chipzen\", CONFIG_FILENAME),\n ];\n if (process.platform !== \"win32\") {\n paths.push(path.join(\"/etc/chipzen\", CONFIG_FILENAME));\n }\n return paths;\n}\n\n/**\n * Return the first existing `chipzen.toml` on the search path, or `null`.\n *\n * @param paths Override the default search order (mostly for tests). When\n * omitted, uses {@link searchPaths}.\n */\nexport function discoverConfigPath(paths?: string[]): string | null {\n const candidates = paths ?? searchPaths();\n for (const candidate of candidates) {\n try {\n if (fs.statSync(candidate).isFile()) {\n return candidate;\n }\n } catch {\n // Not found / not statable — treat as \"not present\" and continue.\n continue;\n }\n }\n return null;\n}\n\n/**\n * Minimal parser for the `[external_api]` table of a `chipzen.toml`.\n *\n * Deliberately tiny — it understands exactly what the SDK needs: a single\n * `[external_api]` table with `key = \"string\"` entries. This keeps the\n * package's runtime dependency surface at one (`ws`) instead of pulling\n * in a full TOML library for three string fields.\n *\n * Recognized syntax inside the section:\n *\n * - `key = \"value\"` or `key = 'value'` — a quoted string assignment.\n * - `# comment` lines and blank lines — ignored.\n * - Other top-level `[sections]` — ignored (the dev may keep one config\n * file for several tools).\n *\n * Anything that doesn't fit (an unquoted/typed value for a recognized\n * key, an array-of-tables `[[external_api]]`, a syntactically broken\n * assignment) raises {@link ChipzenConfigError}, matching the Python\n * reader's \"found-but-malformed is a hard error\" contract.\n *\n * @throws ChipzenConfigError on a malformed file or missing section.\n */\nfunction parseExternalApiSection(\n raw: string,\n filePath: string,\n): { token: string | null; url: string | null; botId: string | null } {\n const lines = raw.split(/\\r?\\n/);\n let inSection = false;\n let sawSection = false;\n const values: Record<string, string> = {};\n\n for (let i = 0; i < lines.length; i++) {\n const rawLine = lines[i] ?? \"\";\n const line = stripComment(rawLine).trim();\n if (line === \"\") continue;\n\n // Section header?\n if (line.startsWith(\"[\")) {\n if (line.startsWith(\"[[\")) {\n const inner = line.slice(2, line.indexOf(\"]]\"));\n if (inner.trim() === SECTION_NAME) {\n throw new ChipzenConfigError(\n `${filePath}: [${SECTION_NAME}] must be a table (key=value pairs), ` +\n `got an array-of-tables ([[${SECTION_NAME}]]).`,\n );\n }\n inSection = false;\n continue;\n }\n const closeIdx = line.indexOf(\"]\");\n if (closeIdx < 0) {\n throw new ChipzenConfigError(\n `${filePath}: malformed section header on line ${i + 1}: ${JSON.stringify(rawLine)}.`,\n );\n }\n const name = line.slice(1, closeIdx).trim();\n inSection = name === SECTION_NAME;\n if (inSection) sawSection = true;\n continue;\n }\n\n if (!inSection) continue;\n\n // key = value assignment.\n const eq = line.indexOf(\"=\");\n if (eq < 0) {\n throw new ChipzenConfigError(\n `${filePath}: Failed to parse line ${i + 1} in [${SECTION_NAME}]: ` +\n `${JSON.stringify(rawLine)} (expected key = \"value\").`,\n );\n }\n const key = line.slice(0, eq).trim();\n const valueText = line.slice(eq + 1).trim();\n if (key === \"\") {\n throw new ChipzenConfigError(\n `${filePath}: Failed to parse line ${i + 1} in [${SECTION_NAME}]: empty key.`,\n );\n }\n\n // Only the three recognized keys are type-checked; unknown keys are\n // forward-compat ignored (mirrors the Python reader). But a malformed\n // value for a RECOGNIZED key is a hard error.\n if (key === \"token\" || key === \"url\" || key === \"bot_id\") {\n const parsed = parseQuotedString(valueText);\n if (parsed === null) {\n const display = key === \"bot_id\" ? \"bot_id\" : key;\n throw new ChipzenConfigError(\n `${filePath}: [${SECTION_NAME}].${display} must be a string, ` +\n `got ${JSON.stringify(valueText)}.`,\n );\n }\n values[key] = parsed;\n }\n // Unknown keys: ignored for forward-compat.\n }\n\n if (!sawSection) {\n throw new ChipzenConfigError(\n `${filePath} has no [${SECTION_NAME}] section. Add one with at least:\\n` +\n `\\n [${SECTION_NAME}]\\n token = \"cz_extbot_...\"\\n`,\n );\n }\n\n return {\n token: values.token ?? null,\n url: values.url ?? null,\n botId: values.bot_id ?? null,\n };\n}\n\n/** Strip a trailing `# comment` that is outside a quoted string. */\nfunction stripComment(line: string): string {\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (ch === \"#\" && !inSingle && !inDouble) return line.slice(0, i);\n }\n return line;\n}\n\n/**\n * Parse a quoted-string TOML value. Returns the unquoted content, or\n * `null` if the text is not a single well-formed quoted string (an\n * unquoted number/bool/bare word, or an unterminated quote).\n */\nfunction parseQuotedString(text: string): string | null {\n if (text.length < 2) return null;\n const quote = text[0];\n if (quote !== '\"' && quote !== \"'\") return null;\n if (text[text.length - 1] !== quote) return null;\n const inner = text.slice(1, -1);\n // Reject an embedded un-escaped closing quote, which would mean the\n // value isn't a single string (e.g. `\"a\" \"b\"`).\n if (quote === \"'\") {\n // Literal strings: no escapes, so any inner single-quote is illegal.\n if (inner.includes(\"'\")) return null;\n return inner;\n }\n // Basic strings: allow `\\\"` escapes; reject a bare un-escaped `\"`.\n let out = \"\";\n for (let i = 0; i < inner.length; i++) {\n const ch = inner[i];\n if (ch === \"\\\\\") {\n const next = inner[i + 1];\n if (next === '\"' || next === \"\\\\\") {\n out += next;\n i++;\n continue;\n }\n if (next === \"n\") {\n out += \"\\n\";\n i++;\n continue;\n }\n if (next === \"t\") {\n out += \"\\t\";\n i++;\n continue;\n }\n out += ch;\n continue;\n }\n if (ch === '\"') return null;\n out += ch;\n }\n return out;\n}\n\n/**\n * Discover and parse a `chipzen.toml` from the search path.\n *\n * @param paths Override the default search order. When omitted, uses\n * cwd → `~/.chipzen/` → `/etc/chipzen/` (POSIX only).\n * @returns A {@link ChipzenConfig} if a file was found and parsed; `null`\n * if no file exists on the search path. The \"no file\" case is NOT an\n * error — the SDK falls back to explicit kwargs in that case.\n * @throws ChipzenConfigError if a file is found but is malformed, lacks\n * the `[external_api]` section, or has a wrong-typed token / url / bot_id.\n */\nexport function loadChipzenConfig(paths?: string[]): ChipzenConfig | null {\n const filePath = discoverConfigPath(paths);\n if (filePath === null) return null;\n\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, \"utf-8\");\n } catch (err) {\n throw new ChipzenConfigError(`Failed to read ${filePath}: ${(err as Error).message}`);\n }\n\n const { token, url, botId } = parseExternalApiSection(raw, filePath);\n return { path: filePath, token, url, botId };\n}\n\n/**\n * Return the token to use, honoring the precedence rules.\n *\n * 1. If `explicitToken` is non-`undefined`/non-`null`, return it. Even an\n * empty string wins — the dev was explicit.\n * 2. If `explicitTicket` is set, return `null` (ticket-auth; no token).\n * 3. Otherwise, if `config` carries a token, return it.\n * 4. Otherwise, return `null`.\n */\nexport function resolveToken(opts: {\n explicitToken?: string | null;\n explicitTicket?: string | null;\n config?: ChipzenConfig | null;\n}): string | null {\n if (opts.explicitToken !== undefined && opts.explicitToken !== null) {\n return opts.explicitToken;\n }\n if (opts.explicitTicket !== undefined && opts.explicitTicket !== null) {\n return null;\n }\n if (opts.config && opts.config.token !== null) {\n return opts.config.token;\n }\n return null;\n}\n\n/**\n * Return the URL override to use, honoring the precedence rules.\n *\n * 1. If `explicitUrl` is set, return it.\n * 2. Otherwise, if `config` carries a `url`, return it.\n * 3. Otherwise, return `null`.\n */\nexport function resolveUrl(opts: {\n explicitUrl?: string | null;\n config?: ChipzenConfig | null;\n}): string | null {\n if (opts.explicitUrl !== undefined && opts.explicitUrl !== null) {\n return opts.explicitUrl;\n }\n if (opts.config && opts.config.url !== null) {\n return opts.config.url;\n }\n return null;\n}\n","/**\n * Retry / backoff policy for the WebSocket client.\n *\n * When the connection to the Chipzen server drops (TCP reset, heartbeat\n * miss, transient network failure, etc.) the SDK reconnects within the\n * server's reconnect grace window. The pacing of those reconnect attempts\n * is configurable via {@link RetryPolicy}, accepted by both `runBot` and\n * `runExternalBot`.\n *\n * The default policy mirrors the Python SDK's spec (External-API Issue 26):\n * 5 attempts, 500ms initial backoff, doubling each attempt, capped at\n * 30 seconds. The defaults are sensible for the typical home-network\n * deployment; devs on noisy connections may want a longer backoff or\n * more attempts.\n *\n * Note: this policy controls **only** how reconnect attempts are paced.\n * The 30-second server-side grace window itself is unchanged; if the\n * reconnects burn through the window the session is considered lost and\n * the server terminates the match-side state.\n */\n\n/** Backoff knobs accepted by {@link RetryPolicy}. All fields are optional. */\nexport interface RetryPolicyOptions {\n /**\n * Maximum number of reconnection attempts after a connection drop or\n * heartbeat miss. Must be `>= 0`. `0` disables reconnection entirely\n * (the first connect failure raises). Default `5`.\n */\n maxReconnectAttempts?: number;\n /**\n * Delay before the **first** reconnect attempt, in milliseconds. Must\n * be `>= 0`. Default `500`.\n */\n initialBackoffMs?: number;\n /**\n * Upper bound for any single backoff delay, in milliseconds. Must be\n * `>= initialBackoffMs`. Default `30000` (matches the server-side grace\n * window so a single backoff never exceeds the window itself).\n */\n maxBackoffMs?: number;\n /**\n * Exponential factor applied between attempts. `2.0` doubles the delay\n * each attempt. Must be `>= 1.0`; `1.0` produces constant backoff.\n * Default `2.0`.\n */\n backoffMultiplier?: number;\n}\n\n/**\n * Backoff knobs applied to reconnect attempts.\n *\n * Backoff progression for attempt `n` (1-indexed) is:\n *\n * min(initialBackoffMs * backoffMultiplier ** (n - 1), maxBackoffMs)\n *\n * Examples (defaults):\n *\n * attempt 1: 500 ms\n * attempt 2: 1000 ms\n * attempt 3: 2000 ms\n * attempt 4: 4000 ms\n * attempt 5: 8000 ms\n * attempt 6: 16000 ms (would be next, but capped by attempts=5)\n */\nexport class RetryPolicy {\n readonly maxReconnectAttempts: number;\n readonly initialBackoffMs: number;\n readonly maxBackoffMs: number;\n readonly backoffMultiplier: number;\n\n constructor(options: RetryPolicyOptions = {}) {\n const maxReconnectAttempts = options.maxReconnectAttempts ?? 5;\n const initialBackoffMs = options.initialBackoffMs ?? 500;\n const maxBackoffMs = options.maxBackoffMs ?? 30_000;\n const backoffMultiplier = options.backoffMultiplier ?? 2.0;\n\n if (maxReconnectAttempts < 0) {\n throw new Error(`maxReconnectAttempts must be >= 0, got ${maxReconnectAttempts}`);\n }\n if (initialBackoffMs < 0) {\n throw new Error(`initialBackoffMs must be >= 0, got ${initialBackoffMs}`);\n }\n if (maxBackoffMs < initialBackoffMs) {\n throw new Error(\n `maxBackoffMs must be >= initialBackoffMs (${maxBackoffMs} < ${initialBackoffMs})`,\n );\n }\n if (backoffMultiplier < 1.0) {\n throw new Error(`backoffMultiplier must be >= 1.0, got ${backoffMultiplier}`);\n }\n\n this.maxReconnectAttempts = maxReconnectAttempts;\n this.initialBackoffMs = initialBackoffMs;\n this.maxBackoffMs = maxBackoffMs;\n this.backoffMultiplier = backoffMultiplier;\n }\n\n /**\n * Return the delay (in ms) to wait **before** the given attempt.\n *\n * @param attempt 1-indexed attempt number. `attempt=1` is the first\n * reconnect after a drop, `attempt=2` the second, etc.\n * @returns The delay in milliseconds, capped at `maxBackoffMs`.\n * @throws Error if `attempt < 1`.\n */\n backoffMs(attempt: number): number {\n if (attempt < 1) {\n throw new Error(`attempt must be >= 1, got ${attempt}`);\n }\n // Compute initial * multiplier ** (attempt - 1) then clamp + floor.\n // Clamping before the floor keeps the cap exact even when the float\n // product overflows the cap by a fraction.\n const raw = this.initialBackoffMs * this.backoffMultiplier ** (attempt - 1);\n return Math.floor(Math.min(raw, this.maxBackoffMs));\n }\n}\n\n/**\n * The default {@link RetryPolicy} used when `runBot` / `runExternalBot`\n * is called without an explicit `retryPolicy` argument.\n */\nexport const DEFAULT_RETRY_POLICY = new RetryPolicy();\n","/**\n * Environment-aware connection helper for the external-API lobby.\n *\n * Devs running an external-API bot against Chipzen shouldn't need to\n * remember the exact lobby WebSocket URL per environment. This module\n * exposes {@link connectToChipzen} — a small one-liner that returns a\n * fully-populated {@link ConnectionConfig} containing the resolved `url`,\n * `token`, and `retryPolicy`, ready to hand off to `runExternalBot`.\n *\n * Mirrors the Python SDK's `chipzen.connect` (External-API Issue 24,\n * chipzen-ai/chipzen-sdk#43).\n *\n * Environment → URL mapping:\n *\n * prod -> wss://chipzen.ai/ws/external/bot/{botId}\n * staging -> wss://staging.chipzen.ai/ws/external/bot/{botId}\n * local -> ws://localhost:8001/ws/external/bot/{botId}\n *\n * Precedence (highest first) for the final WebSocket URL:\n *\n * 1. `[external_api].url` from a discovered `chipzen.toml` — a config-file\n * URL ALWAYS wins (most explicit, user-managed override).\n * 2. An **explicitly passed** `env` argument.\n * 3. The `CHIPZEN_ENV` environment variable, if set to a recognized value.\n * 4. The default of `\"prod\"`.\n */\n\nimport {\n type ChipzenConfig,\n loadChipzenConfig,\n resolveToken,\n resolveUrl,\n} from \"./config.js\";\nimport { DEFAULT_RETRY_POLICY, RetryPolicy } from \"./retry.js\";\n\n/** Recognized target environments. */\nexport type EnvName = \"prod\" | \"staging\" | \"local\";\n\n/** The canonical env names, in order, for error messages + validation. */\nexport const ENV_NAMES: readonly EnvName[] = [\"prod\", \"staging\", \"local\"] as const;\n\n/**\n * Name of the environment variable consulted when `env` is not explicitly\n * passed.\n */\nexport const ENV_VAR_NAME = \"CHIPZEN_ENV\";\n\n/** Canonical lobby-URL templates per env (`{botId}` substituted at resolve time). */\nconst ENV_URL_TEMPLATES: Record<EnvName, string> = {\n prod: \"wss://chipzen.ai/ws/external/bot/{botId}\",\n staging: \"wss://staging.chipzen.ai/ws/external/bot/{botId}\",\n local: \"ws://localhost:8001/ws/external/bot/{botId}\",\n};\n\n/**\n * Fully-resolved connection parameters ready for `runExternalBot`.\n * Returned by {@link connectToChipzen}.\n */\nexport interface ConnectionConfig {\n /** WebSocket URL the bot should connect to. */\n readonly url: string;\n /** Long-lived API token from a discovered `chipzen.toml`, or `null`. */\n readonly token: string | null;\n /** {@link RetryPolicy} controlling reconnect pacing. */\n readonly retryPolicy: RetryPolicy;\n /**\n * The resolved environment name, or `null` if the URL was supplied\n * verbatim via a config file (no env mapping applied). Mostly for logs.\n */\n readonly env: EnvName | null;\n /**\n * The {@link ChipzenConfig} discovered during resolution (if any), or\n * `null`. Exposed so callers can pass it through to `runExternalBot`\n * and avoid a second filesystem stat.\n */\n readonly config: ChipzenConfig | null;\n}\n\nfunction isEnvName(value: string): value is EnvName {\n return (ENV_NAMES as readonly string[]).includes(value);\n}\n\n/**\n * Pick the env name to use, applying explicit-arg-then-env-var rules.\n *\n * @param explicitEnv The `env` argument (`undefined`/`null` = \"not specified\").\n * @param envVarValue The current value of `$CHIPZEN_ENV` (or `undefined`).\n * @throws Error if an explicit `env` or the env-var value isn't recognized.\n */\nexport function resolveEnvName(\n explicitEnv: EnvName | null | undefined,\n envVarValue: string | undefined,\n): EnvName {\n if (explicitEnv !== undefined && explicitEnv !== null) {\n if (!isEnvName(explicitEnv)) {\n throw new Error(`Unknown env ${JSON.stringify(explicitEnv)}. Valid values: ${ENV_NAMES.join(\", \")}.`);\n }\n return explicitEnv;\n }\n\n // Empty string is treated as \"not set\" so an accidental `CHIPZEN_ENV=`\n // falls through to the default rather than tripping the error.\n if (envVarValue) {\n if (!isEnvName(envVarValue)) {\n throw new Error(\n `${ENV_VAR_NAME}=${JSON.stringify(envVarValue)} is not a recognized ` +\n `environment. Valid values: ${ENV_NAMES.join(\", \")}.`,\n );\n }\n return envVarValue;\n }\n\n return \"prod\";\n}\n\n/** Format the canonical lobby URL for a given env + bot id. */\nexport function urlForEnv(env: EnvName, botId: string): string {\n return ENV_URL_TEMPLATES[env].replace(\"{botId}\", botId);\n}\n\n/** Options for {@link connectToChipzen}. */\nexport interface ConnectToChipzenOptions {\n /** Override the reconnect-pacing policy. Defaults to {@link DEFAULT_RETRY_POLICY}. */\n retryPolicy?: RetryPolicy;\n /** Pre-loaded config to avoid a second filesystem stat. `undefined` triggers discovery. */\n config?: ChipzenConfig | null;\n}\n\n/**\n * Resolve a {@link ConnectionConfig} for the external-API lobby.\n *\n * Maps `env` to a canonical lobby URL and combines it with whatever\n * config-file token / URL / retry policy the dev has set up.\n *\n * @param botId External-API bot UUID. Required, non-empty.\n * @param env Target environment. `undefined`/`null` means \"look at\n * `$CHIPZEN_ENV` first, then fall back to `prod`\".\n * @param options Optional retry policy + pre-loaded config.\n * @throws Error if `botId` is empty, `env` is unrecognized, or\n * `$CHIPZEN_ENV` is set to an unrecognized value.\n * @throws ChipzenConfigError if a discovered `chipzen.toml` is malformed.\n */\nexport function connectToChipzen(\n botId: string,\n env?: EnvName | null,\n options: ConnectToChipzenOptions = {},\n): ConnectionConfig {\n if (!botId || typeof botId !== \"string\") {\n throw new Error(\n \"connectToChipzen() requires a non-empty botId string. Pass the \" +\n \"external-API bot UUID issued by the Chipzen platform, e.g. \" +\n \"connectToChipzen('abc123', 'prod').\",\n );\n }\n\n // Resolve + validate the env name BEFORE config-file URL resolution so a\n // typo in an explicit `env` / `$CHIPZEN_ENV` always surfaces even when a\n // config file overrides the env-derived URL.\n const resolvedEnv = resolveEnvName(env, process.env[ENV_VAR_NAME]);\n const envDerivedUrl = urlForEnv(resolvedEnv, botId);\n\n const config = options.config !== undefined ? options.config : loadChipzenConfig();\n\n const configUrl = resolveUrl({ explicitUrl: null, config });\n let finalUrl: string;\n let envForReturn: EnvName | null;\n if (configUrl === null) {\n finalUrl = envDerivedUrl;\n envForReturn = resolvedEnv;\n } else {\n finalUrl = configUrl;\n // Config file supplied a verbatim URL; the env name is not meaningful.\n envForReturn = null;\n }\n\n const token = resolveToken({ explicitToken: null, config });\n const retryPolicy = options.retryPolicy ?? DEFAULT_RETRY_POLICY;\n\n return {\n url: finalUrl,\n token,\n retryPolicy,\n env: envForReturn,\n config: config ?? null,\n };\n}\n","/**\n * External-API remote-play entry point: {@link runExternalBot}.\n *\n * Where `runBot` connects a bot to a *known* match URL (the\n * containerized/upload path — the platform's executor hands the container\n * its `/ws/match/{matchId}/{participantId}` URL), this module implements\n * the **external-API remote-play** path: a developer runs their bot on\n * their own machine, authenticates with a long-lived `cz_extbot_` token,\n * and the platform matches and dispatches them like any other competitor.\n *\n * The flow:\n *\n * lobby WS /ws/external/bot/{botId} (token in authenticate frame)\n * -> \"matched\" notify (carries matchId + gatewayWsUrl)\n * -> per-match gateway WS /ws/external/match/{mid}/{pid}\n * (token in Sec-WebSocket-Protocol header)\n * -> two-layer bot handshake + game loop to match_end\n *\n * The **match data plane is identical** to the containerized path, so the\n * game loop here reuses `_runSession` from `client.ts` verbatim — the only\n * external-API-specific code is the lobby connection and the per-match\n * gateway handshake. A developer writes ONE `Bot` subclass and it works on\n * both paths.\n *\n * The lobby is held open for the bot's whole session and each `matched`\n * plays in its own task (Promise), so the 15-second lobby heartbeat is\n * answered even while a multi-minute match is in flight. This is what lets\n * a single connection serve a whole tournament (the bot is \"checked in\"\n * via lobby presence and matched once per round).\n *\n * Mirrors the Python SDK's `chipzen.external`, including the reconnect\n * fix: a dropped gateway socket RECONNECTS and resumes; match-task\n * ownership is hoisted to the top level so a lobby reconnect doesn't kill\n * in-flight matches; teardown drains-then-cancels, never orphaning a task.\n */\n\nimport WebSocket from \"ws\";\n\nimport type { Bot } from \"./bot.js\";\nimport {\n _NodeWebSocketReader,\n _runSession,\n type AsyncMessageReader,\n BotDecisionError,\n} from \"./client.js\";\nimport {\n type ChipzenConfig,\n loadChipzenConfig,\n resolveToken,\n} from \"./config.js\";\nimport { connectToChipzen, type EnvName } from \"./connect.js\";\nimport { DEFAULT_RETRY_POLICY, RetryPolicy } from \"./retry.js\";\nimport { VERSION } from \"./version.js\";\n\n/**\n * Sentinel subprotocol that marks the `cz_extbot_` token in the\n * `Sec-WebSocket-Protocol` header (CZ issue 2932 moved the token off the\n * query string, where it leaked into proxy access logs). Must match the\n * value the platform's api gateway expects.\n */\nexport const BOT_TOKEN_SUBPROTOCOL = \"chipzen-bot-token\";\n\n/**\n * How long the lobby loop blocks on a single `reader.next()` before waking\n * to re-check the stop signal. Short enough that a stop is honored\n * promptly; long enough that the loop isn't a busy-wait. Mutable so tests\n * can shrink it.\n */\nexport let LOBBY_RECV_TIMEOUT_MS = 2000;\n\n/** Test hook: override {@link LOBBY_RECV_TIMEOUT_MS}. */\nexport function _setLobbyRecvTimeoutMs(ms: number): void {\n LOBBY_RECV_TIMEOUT_MS = ms;\n}\n\n/**\n * On teardown, how long to let still-in-flight matches finish before\n * cancelling them, so nothing is left orphaned.\n */\nexport const MATCH_DRAIN_GRACE_MS = 5000;\n\n/**\n * Build the `Sec-WebSocket-Protocol` offer that carries the bot token.\n *\n * Returns `[sentinel, token]` — the sentinel marks \"the next value is my\n * bot token\". The api gateway extracts the token from this header (so it\n * never appears in any access log / URL) and echoes the sentinel back on\n * accept.\n */\nexport function botTokenSubprotocols(token: string): [string, string] {\n return [BOT_TOKEN_SUBPROTOCOL, token];\n}\n\nfunction normaliseBase(url: string): string {\n // Strip path/query/fragment, keeping scheme + authority only. Tolerate a\n // bare `host:port` without a scheme (defaults to wss).\n try {\n const u = new URL(url);\n return `${u.protocol}//${u.host}`;\n } catch {\n // Not a parseable absolute URL; treat the whole thing as host[:port].\n const host = url.replace(/\\/+$/, \"\");\n return `wss://${host}`;\n }\n}\n\n/**\n * Resolve the `matched.gateway_ws_url` path against the lobby origin.\n *\n * The `matched` notification carries `gateway_ws_url` as a *path*\n * (`/ws/external/match/{mid}/{pid}`). The `cz_extbot_` token is NOT on the\n * query string — it travels in the `Sec-WebSocket-Protocol` header. A\n * future server that returns a full URL is passed through unchanged.\n */\nexport function resolveGatewayUrl(lobbyUrl: string, gatewayWsPath: string): string {\n if (gatewayWsPath.startsWith(\"ws://\") || gatewayWsPath.startsWith(\"wss://\")) {\n // Absolute URL from the server: honor it only if it stays on the same\n // origin as the lobby and doesn't downgrade wss -> ws. Otherwise the bot\n // token could be sent to an attacker-/misconfig-supplied host (or in\n // cleartext). A relative path is re-anchored to the lobby origin below, so\n // it's inherently same-origin.\n const lobby = new URL(normaliseBase(lobbyUrl));\n const gateway = new URL(gatewayWsPath);\n const downgrade = lobby.protocol === \"wss:\" && gateway.protocol !== \"wss:\";\n if (gateway.host !== lobby.host || downgrade) {\n throw new Error(\n `refusing gateway URL ${gatewayWsPath}: cross-origin or insecure relative to ` +\n `lobby ${lobby.protocol}//${lobby.host} (the bot token must not be sent to a ` +\n `different host or in cleartext)`,\n );\n }\n return gatewayWsPath;\n }\n return `${normaliseBase(lobbyUrl)}${gatewayWsPath}`;\n}\n\n/** Parse a WS frame into an object (`{}` on non-object / bad JSON). */\nfunction loads(raw: string): Record<string, unknown> {\n try {\n const msg = JSON.parse(raw) as unknown;\n return msg !== null && typeof msg === \"object\" && !Array.isArray(msg)\n ? (msg as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n\n/** A factory that produces a fresh {@link Bot} per match (or returns the same one). */\nexport type BotFactory = () => Bot;\n\n/**\n * Normalize the `bot` argument into a per-match instance factory.\n *\n * Accepts either:\n *\n * - a `Bot` **instance** — reused for every match (correct for the common\n * case of sequential tournament matches), or\n * - a **callable** returning a fresh `Bot` (a factory function) — a new\n * instance per match, the right choice for overlapping matches with\n * per-match mutable state.\n *\n * (TS class constructors are also callable; passing `MyBot` works, but the\n * idiomatic JS form is `() => new MyBot()`.)\n */\nexport function asFactory(bot: Bot | BotFactory): BotFactory {\n // A Bot instance has a `decide` method; a factory function does not (it\n // returns one when called). Disambiguate on `decide`.\n if (bot && typeof (bot as Bot).decide === \"function\") {\n const instance = bot as Bot;\n return () => instance;\n }\n if (typeof bot === \"function\") {\n return bot as BotFactory;\n }\n throw new TypeError(\n \"runExternalBot(bot=...) must be a Bot instance or a callable returning one, \" +\n `got ${typeof bot}.`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Transport — injectable so tests can drive the lobby/gateway without a\n// real network (mirrors the Python tests' monkeypatch of websockets.connect).\n// ---------------------------------------------------------------------------\n\n/** A connected WS-shaped object the lobby loop / `_runSession` drive. */\nexport interface ExternalConnection {\n /** Send a string frame. */\n send(data: string): void | Promise<void>;\n /** Pull-based message reader; resolves `null` when the socket closes. */\n reader: AsyncMessageReader;\n /** Close the underlying socket. */\n close(): void;\n}\n\n/** Options handed to a {@link Transport} on connect. */\nexport interface TransportConnectOptions {\n /** WS `User-Agent` header value. */\n userAgent: string;\n /**\n * Sec-WebSocket-Protocol offer (gateway leg carries `[sentinel, token]`).\n * Omitted for the lobby leg.\n */\n subprotocols?: string[];\n}\n\n/** Opens WS connections for the external path. Injectable for tests. */\nexport type Transport = (\n url: string,\n options: TransportConnectOptions,\n) => Promise<ExternalConnection>;\n\nconst MAX_WS_PAYLOAD = 2 ** 24; // 16 MiB — large round_result / deck_reveal frames.\n\n/** Default transport: a real `ws.WebSocket`. */\nconst defaultTransport: Transport = async (url, options) => {\n const ws = new WebSocket(url, options.subprotocols ?? [], {\n headers: { \"User-Agent\": options.userAgent },\n maxPayload: MAX_WS_PAYLOAD,\n });\n await new Promise<void>((resolve, reject) => {\n const onOpen = (): void => {\n ws.removeListener(\"error\", onError);\n resolve();\n };\n const onError = (err: Error): void => {\n ws.removeListener(\"open\", onOpen);\n reject(err);\n };\n ws.once(\"open\", onOpen);\n ws.once(\"error\", onError);\n });\n return {\n send: (data: string) => ws.send(data),\n reader: new _NodeWebSocketReader(ws),\n close: () => {\n if (ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) {\n ws.close();\n }\n },\n };\n};\n\n/**\n * Backoff sleep. Indirected through a module-level binding so tests can\n * replace it with an instant, recording stub (mirrors the Python tests'\n * monkeypatch of `asyncio.sleep`).\n */\nlet sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\n/** Test hook: replace the backoff sleep (e.g. to record delays instantly). */\nexport function _setSleep(fn: (ms: number) => Promise<void>): void {\n sleep = fn;\n}\n\n/** Test hook: restore the default real backoff sleep. */\nexport function _resetSleep(): void {\n sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n}\n\n/** Read the next lobby frame, or `undefined` if the timeout elapsed first. */\nasync function recvWithTimeout(\n reader: AsyncMessageReader,\n timeoutMs: number,\n): Promise<string | null | undefined> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), timeoutMs);\n });\n try {\n return await Promise.race([reader.next(), timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/** Per-match result recorded by the session. */\nexport interface MatchResult {\n matchId: string | null;\n end: Record<string, unknown> | null;\n}\n\n/**\n * A match-play Promise with a `done` flag so the lobby loop can prune\n * settled tasks from the shared list across reconnects (mirrors Python's\n * `match_tasks[:] = [t for t in match_tasks if not t.done()]`).\n */\ninterface MatchTask {\n promise: Promise<void>;\n done: boolean;\n}\n\ninterface PlayMatchParams {\n gatewayUrl: string;\n matchId: string;\n token: string;\n bot: Bot;\n policy: RetryPolicy;\n clientName: string;\n clientVersion: string;\n safeMode: boolean;\n userAgent: string;\n transport: Transport;\n}\n\n/**\n * Play one match end-to-end over the per-match gateway WS, reconnecting\n * across a mid-match drop.\n *\n * Opens the gateway WS (token in the `Sec-WebSocket-Protocol` header) and\n * hands off to `_runSession` for the two-layer handshake + game loop —\n * the exact data-plane code the containerized path runs.\n *\n * If the socket drops before `match_end`, reconnect (bounded by `policy`)\n * and let the platform's reconnect-resume re-deliver the pending turn:\n * `_runSession` already consumes the server `reconnected` frame and\n * replays its `pending_request`, and the same `bot` instance carries its\n * state across the gap.\n *\n * Returns the `match_end` payload, or `null` if the match could not be\n * completed within the reconnect budget.\n *\n * Throws {@link BotDecisionError} (safeMode=false) without retrying — a\n * deterministic bot bug is terminal.\n */\nasync function playOneMatch(params: PlayMatchParams): Promise<Record<string, unknown> | null> {\n const { gatewayUrl, matchId, token, bot, policy, transport } = params;\n let attempt = 0;\n for (;;) {\n let conn: ExternalConnection | null = null;\n let reason: string;\n try {\n conn = await transport(gatewayUrl, {\n userAgent: params.userAgent,\n subprotocols: botTokenSubprotocols(token),\n });\n // The inner leg's token is the gateway's internal JWT (authoritative);\n // the executor ignores the value we send, but the authenticate frame\n // MUST be first. _runSession sends an empty token when token+ticket\n // are both null.\n const end = await _runSession(\n conn,\n bot,\n {\n matchId,\n token: null,\n ticket: null,\n clientName: params.clientName,\n clientVersion: params.clientVersion,\n safeMode: params.safeMode,\n },\n conn.reader,\n );\n if (end !== null) {\n return end; // clean match_end — done\n }\n // _runSession returned null: the socket closed without a match_end\n // (a drop). Try to resume.\n reason = \"closed without match_end\";\n } catch (err) {\n if (err instanceof BotDecisionError) {\n throw err; // deterministic bot bug — terminal, never reconnect-retry\n }\n reason = err instanceof Error ? err.message || err.constructor.name : String(err);\n } finally {\n if (conn) conn.close();\n }\n\n attempt += 1;\n if (attempt > policy.maxReconnectAttempts) {\n // Reconnect budget exhausted; abandon the match rather than hang.\n return null;\n }\n await sleep(policy.backoffMs(attempt));\n }\n}\n\ninterface LobbyOnceParams {\n lobbyUrl: string;\n token: string;\n factory: BotFactory;\n results: MatchResult[];\n matchTasks: MatchTask[];\n completed: { value: number };\n policy: RetryPolicy;\n clientName: string;\n clientVersion: string;\n safeMode: boolean;\n userAgent: string;\n maxMatches: number | null;\n stop: { value: boolean };\n fatal: Error[];\n transport: Transport;\n}\n\n/**\n * Hold ONE lobby connection and dispatch every `matched` it delivers.\n *\n * Returns a status string:\n *\n * - `\"stopped\"` — the stop signal fired, or `maxMatches` was reached.\n * - `\"evicted\"` — the lobby evicted us (a newer connection replaced this).\n * - `\"closed\"` — the lobby connection closed unexpectedly (may reconnect).\n *\n * Each `matched` is played in its own task, appended to `matchTasks`\n * (which the CALLER owns) so the lobby heartbeat is answered during\n * matches AND in-flight matches survive a lobby reconnect — a match runs\n * on its own gateway socket, independent of the lobby.\n */\nasync function runLobbyOnce(p: LobbyOnceParams): Promise<\"stopped\" | \"evicted\" | \"closed\"> {\n const onMatchSettled = (\n end: Record<string, unknown> | null | undefined,\n err: unknown,\n ): void => {\n if (err !== undefined && err !== null) {\n if (err instanceof BotDecisionError) {\n // safeMode=false: a deterministic bot bug. Surface it (stop the\n // session and re-raise from runExternalBot).\n p.fatal.push(err);\n p.stop.value = true;\n return;\n }\n // Record, never crash the lobby.\n p.results.push({ matchId: null, end: null });\n } else {\n p.completed.value += 1;\n p.results.push({\n matchId: (end?.match_id as string | undefined) ?? null,\n end: end ?? null,\n });\n }\n if (p.maxMatches !== null && p.completed.value >= p.maxMatches) {\n p.stop.value = true;\n }\n };\n\n const conn = await p.transport(p.lobbyUrl, { userAgent: p.userAgent });\n try {\n await conn.send(JSON.stringify({ type: \"authenticate\", token: p.token }));\n while (!p.stop.value) {\n const raw = await recvWithTimeout(conn.reader, LOBBY_RECV_TIMEOUT_MS);\n if (raw === undefined) {\n continue; // periodic wake to re-check the stop signal\n }\n if (raw === null) {\n // Socket closed.\n return \"closed\";\n }\n\n const msg = loads(raw);\n const mtype = msg.type;\n if (mtype === \"ping\") {\n await conn.send(JSON.stringify({ type: \"pong\" }));\n } else if (mtype === \"hello\") {\n // Lobby server hello — informational; the client does NOT reply.\n } else if (mtype === \"matched\") {\n let gatewayUrl: string;\n try {\n gatewayUrl = resolveGatewayUrl(p.lobbyUrl, msg.gateway_ws_url as string);\n } catch {\n // Untrusted gateway URL (cross-origin / downgrade); skip this match\n // rather than send the token there.\n continue;\n }\n const matchId = msg.match_id as string;\n const task: MatchTask = { promise: Promise.resolve(), done: false };\n task.promise = playOneMatch({\n gatewayUrl,\n matchId,\n token: p.token,\n bot: p.factory(),\n policy: p.policy,\n clientName: p.clientName,\n clientVersion: p.clientVersion,\n safeMode: p.safeMode,\n userAgent: p.userAgent,\n transport: p.transport,\n }).then(\n (end) => {\n task.done = true;\n onMatchSettled(end, undefined);\n },\n (err) => {\n task.done = true;\n onMatchSettled(undefined, err);\n },\n );\n p.matchTasks.push(task);\n } else if (mtype === \"evict\") {\n return \"evicted\";\n }\n // else: ignore unknown frame types (forward-compat).\n }\n return \"stopped\";\n } finally {\n conn.close();\n }\n}\n\n/**\n * Teardown: let still-in-flight matches finish for a short grace window,\n * then await everything so no task is orphaned.\n *\n * (JS Promises can't be cancelled the way asyncio tasks can; the match\n * tasks are already bounded by the reconnect budget, so the grace window\n * is the meaningful knob — after it, we simply await the rest to\n * completion rather than leaving them dangling.)\n */\nasync function drainMatches(\n matchTasks: MatchTask[],\n grace: number = MATCH_DRAIN_GRACE_MS,\n): Promise<void> {\n if (matchTasks.length === 0) return;\n const all = Promise.allSettled(matchTasks.map((t) => t.promise));\n let timer: ReturnType<typeof setTimeout> | undefined;\n const graceTimeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, grace);\n });\n await Promise.race([all.then(() => undefined), graceTimeout]);\n if (timer) clearTimeout(timer);\n // Await the rest so nothing is orphaned / unhandled-rejection-leaks.\n await all;\n}\n\n/** Options for {@link runExternalBot}. */\nexport interface RunExternalBotOptions {\n /** External-API bot UUID. Used to build the lobby URL when `url` is absent. */\n botId?: string;\n /** Target environment (`prod` / `staging` / `local`). `undefined` consults `$CHIPZEN_ENV`. */\n env?: EnvName | null;\n /** Explicit full lobby URL. Overrides `botId` / `env` derivation. */\n url?: string;\n /** Long-lived `cz_extbot_` API token. Falls back to `[external_api].token`. Required. */\n token?: string | null;\n /** Pre-loaded config, to avoid a second filesystem stat. `undefined` triggers discovery. */\n config?: ChipzenConfig | null;\n /** Reconnect-pacing policy. Defaults to {@link DEFAULT_RETRY_POLICY}. */\n retryPolicy?: RetryPolicy;\n /** Client software name sent in the per-match `hello`. */\n clientName?: string;\n /** Client software version. Defaults to the SDK version. */\n clientVersion?: string;\n /** When `true` (default), a `decide()` throw is folded; `false` propagates a {@link BotDecisionError}. */\n safeMode?: boolean;\n /** Stop after this many matches complete. `undefined` runs until the lobby closes / evict. */\n maxMatches?: number | null;\n /** Override the WS `User-Agent`. Defaults to `chipzen-sdk-js/<version>`. */\n userAgent?: string;\n /** Injectable transport (tests). Defaults to a real `ws.WebSocket`. */\n transport?: Transport;\n}\n\n/**\n * Run a bot on the Chipzen external-API remote-play path.\n *\n * Connects to the lobby, then plays every match the platform dispatches to\n * this bot (a single challenge, or every round of a tournament) until the\n * lobby closes, the bot is evicted, or `maxMatches` matches complete.\n *\n * @returns A list of per-match result objects (`{matchId, end}`), one per\n * match played this session.\n * @throws Error if no token can be resolved, or neither `url` nor a\n * `botId` is available to build the lobby URL.\n * @throws BotDecisionError if `safeMode` is `false` and `bot.decide()` throws.\n */\nexport async function runExternalBot(\n bot: Bot | BotFactory,\n options: RunExternalBotOptions = {},\n): Promise<MatchResult[]> {\n const config = options.config !== undefined ? options.config : loadChipzenConfig();\n const transport = options.transport ?? defaultTransport;\n const clientName = options.clientName ?? \"chipzen-sdk-js\";\n const clientVersion = options.clientVersion ?? VERSION;\n const safeMode = options.safeMode ?? true;\n const maxMatches = options.maxMatches ?? null;\n\n // --- Resolve lobby URL + token + retry policy --------------------------\n let lobbyUrl: string;\n let policy: RetryPolicy;\n let resolvedToken: string | null;\n if (options.url !== undefined && options.url !== null) {\n lobbyUrl = options.url;\n policy = options.retryPolicy ?? DEFAULT_RETRY_POLICY;\n resolvedToken = resolveToken({ explicitToken: options.token, config });\n } else {\n const resolvedBotId = options.botId ?? (config ? config.botId : null);\n if (!resolvedBotId) {\n throw new Error(\n \"runExternalBot() needs a lobby URL. Pass url=..., or botId=... \" +\n \"(or set [external_api].bot_id / url in chipzen.toml).\",\n );\n }\n const conn = connectToChipzen(resolvedBotId, options.env, {\n retryPolicy: options.retryPolicy,\n config,\n });\n lobbyUrl = conn.url;\n policy = conn.retryPolicy;\n // An explicit token kwarg still wins over the config-file token.\n resolvedToken =\n options.token !== undefined && options.token !== null ? options.token : conn.token;\n }\n\n if (!resolvedToken) {\n throw new Error(\n \"runExternalBot() requires an external-API token (cz_extbot_...). \" +\n \"Pass token=..., or set [external_api].token in chipzen.toml.\",\n );\n }\n\n const userAgent = options.userAgent ?? `chipzen-sdk-js/${clientVersion}`;\n const factory = asFactory(bot);\n const results: MatchResult[] = [];\n const stop = { value: false };\n const fatal: Error[] = [];\n // Owned HERE, not by a single lobby session, so in-flight matches survive\n // a lobby reconnect (a match plays on its own gateway socket).\n const matchTasks: MatchTask[] = [];\n const completed = { value: 0 };\n\n // --- Lobby session loop with reconnect/backoff -------------------------\n let consecutiveFailures = 0;\n let everConnected = false;\n let giveupExc: Error | null = null;\n\n const dropDoneTasks = (): void => {\n // Prune settled tasks so the list doesn't grow unbounded across\n // reconnects (mirrors Python's match_tasks[:] = [...not done]).\n for (let i = matchTasks.length - 1; i >= 0; i--) {\n if (matchTasks[i]!.done) matchTasks.splice(i, 1);\n }\n };\n\n while (!stop.value) {\n let status: \"stopped\" | \"evicted\" | \"closed\" | null = null;\n try {\n status = await runLobbyOnce({\n lobbyUrl,\n token: resolvedToken,\n factory,\n results,\n matchTasks,\n completed,\n policy,\n clientName,\n clientVersion,\n safeMode,\n userAgent,\n maxMatches,\n stop,\n fatal,\n transport,\n });\n everConnected = true;\n } catch (err) {\n // connect() itself failed — count it as a reconnect attempt.\n consecutiveFailures += 1;\n if (consecutiveFailures > policy.maxReconnectAttempts) {\n // Only a hard error if we NEVER reached the lobby (bad URL / token /\n // network). If we connected and played, give up quietly.\n if (!everConnected) {\n giveupExc = err instanceof Error ? err : new Error(String(err));\n }\n break;\n }\n await sleep(policy.backoffMs(consecutiveFailures));\n dropDoneTasks();\n continue;\n }\n\n // A live lobby session ran; reset the backoff counter.\n consecutiveFailures = 0;\n if (status === \"stopped\" || status === \"evicted\" || fatal.length > 0) {\n break;\n }\n // status === \"closed\": the lobby dropped. In-flight matches keep playing\n // on their own sockets; reconnect the lobby per the policy.\n consecutiveFailures += 1;\n if (consecutiveFailures > policy.maxReconnectAttempts) {\n break;\n }\n await sleep(policy.backoffMs(consecutiveFailures));\n dropDoneTasks();\n }\n\n // --- Teardown: never orphan an in-flight match task --------------------\n await drainMatches(matchTasks);\n if (fatal.length > 0) {\n // A bot.decide() error under safeMode=false — re-raise so the process\n // exits non-zero (matches runBot's behavior).\n throw fatal[0];\n }\n if (giveupExc !== null) {\n throw giveupExc;\n }\n return results;\n}\n","/**\n * WebSocket client for the Chipzen two-layer protocol.\n *\n * The user-facing surface is `runBot(url, bot, options)`. Internals\n * (session loop, mock-friendly helpers) are exported with `_` prefix\n * for the conformance harness and tests; they are not part of the\n * supported API.\n */\n\nimport WebSocket from \"ws\";\n\nimport type { Bot } from \"./bot.js\";\nimport { Action, parseGameState } from \"./models.js\";\nimport { DEFAULT_RETRY_POLICY, RetryPolicy } from \"./retry.js\";\nimport { VERSION } from \"./version.js\";\n\n// ---------------------------------------------------------------------------\n// Public surface\n// ---------------------------------------------------------------------------\n\n/**\n * Raised when `bot.decide()` errors and `safeMode` is `false`.\n *\n * Distinguished from transport/connection errors so the caller treats it\n * as terminal (a deterministic bot bug, not a transient disconnect) and\n * does NOT reconnect-retry it. See chipzen-ai/chipzen-sdk#52.\n */\nexport class BotDecisionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BotDecisionError\";\n }\n}\n\n/**\n * Optional knobs for `runBot`. All fields default to sensible values\n * matching the platform's expectations.\n */\nexport interface RunBotOptions {\n /** Bot API token. Required for the `/bot` endpoint; empty is fine for local dev. */\n token?: string | null;\n /** Single-use ticket alternative to `token` (competitive endpoints). */\n ticket?: string | null;\n /** Match UUID. Auto-extracted from the URL if not supplied. */\n matchId?: string;\n /** Client software name sent in the `hello` handshake. */\n clientName?: string;\n /** Client software version sent in the `hello` handshake. Defaults to the SDK version. */\n clientVersion?: string;\n /**\n * Reconnect attempt **cap**. When given, overrides the attempt count\n * from `retryPolicy` (the policy's backoff knobs still apply). When\n * omitted, the policy's `maxReconnectAttempts` is used.\n */\n maxRetries?: number;\n /**\n * {@link RetryPolicy} controlling reconnect attempts + exponential\n * backoff. Defaults to {@link DEFAULT_RETRY_POLICY}.\n */\n retryPolicy?: RetryPolicy;\n /**\n * When `true` (default), an exception thrown by `bot.decide()` is\n * folded (a safe fallback action is sent). Set `false` for dev/eval so\n * the first exception propagates as a {@link BotDecisionError} and the\n * process exits non-zero (chipzen-ai/chipzen-sdk#52).\n */\n safeMode?: boolean;\n /**\n * Override the WS `User-Agent` header. Defaults to\n * `chipzen-sdk-js/<version>` (a non-default UA also clears the\n * platform's Cloudflare bot-fight rule; chipzen-ai/chipzen-sdk#46).\n */\n userAgent?: string;\n}\n\n/** Protocol versions this client claims to support in the handshake. */\nexport const SUPPORTED_PROTOCOL_VERSIONS = [\"1.0\"] as const;\n\n/**\n * Connect a bot to the Chipzen server and play until the match ends.\n *\n * Resolves on `match_end` (with the `match_end` payload, ignored by most\n * callers). Rejects if the connection cannot be established (after the\n * reconnect budget is exhausted) or if `safeMode` is `false` and\n * `bot.decide()` throws (a terminal {@link BotDecisionError}).\n */\nexport async function runBot(\n url: string,\n bot: Bot,\n options: RunBotOptions = {},\n): Promise<Record<string, unknown> | null> {\n const matchId = options.matchId ?? _extractMatchId(url);\n const token = options.token ?? null;\n const ticket = options.ticket ?? null;\n const clientName = options.clientName ?? \"chipzen-sdk\";\n const clientVersion = options.clientVersion ?? VERSION;\n const safeMode = options.safeMode ?? true;\n const userAgent = options.userAgent ?? `chipzen-sdk-js/${clientVersion}`;\n const policy = options.retryPolicy ?? DEFAULT_RETRY_POLICY;\n // An explicit maxRetries caps the attempt count; the policy still\n // supplies the backoff progression.\n const maxAttempts = options.maxRetries ?? policy.maxReconnectAttempts;\n\n let retries = 0;\n for (;;) {\n let ws: WebSocket | null = null;\n try {\n ws = new WebSocket(url, { headers: { \"User-Agent\": userAgent } });\n await _waitForOpen(ws);\n retries = 0; // reset on successful connect\n return await _runSession(ws, bot, {\n matchId,\n token,\n ticket,\n clientName,\n clientVersion,\n safeMode,\n });\n } catch (err) {\n if (err instanceof BotDecisionError) {\n // A deterministic bot bug (safeMode=false) — terminal, not a\n // transient disconnect. Do not reconnect-retry.\n throw err;\n }\n retries++;\n if (retries > maxAttempts) {\n throw err;\n }\n const waitMs = policy.backoffMs(retries);\n await new Promise((r) => setTimeout(r, waitMs));\n } finally {\n if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) {\n ws.close();\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// _runSession — internal session loop, exported for the conformance harness\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal WebSocket-shaped interface the session loop needs. Real\n * `ws.WebSocket` instances satisfy it; the conformance mock also\n * implements it.\n */\nexport interface SessionWebSocket {\n send(data: string): void | Promise<void>;\n close(): void;\n}\n\ninterface SessionContext {\n matchId: string;\n token: string | null;\n ticket: string | null;\n clientName: string;\n clientVersion: string;\n /**\n * When `true` (default), a throw from `bot.decide()` is folded to a\n * safe fallback action. When `false`, it surfaces as a\n * {@link BotDecisionError} (terminal — never reconnect-retried).\n */\n safeMode?: boolean;\n}\n\n/**\n * Run `bot.decide()` with timing + safeMode handling. Returns the action\n * to send plus the wall-clock decision time in ms.\n *\n * Under safeMode a thrown error (or a non-Action return) is folded to a\n * safe fallback; otherwise a thrown error is re-raised as a\n * {@link BotDecisionError} so the caller treats it as terminal.\n */\nfunction _decide(\n bot: Bot,\n state: ReturnType<typeof parseGameState>,\n validActions: string[] | undefined,\n safeMode: boolean,\n): { action: Action; latencyMs: number } {\n const start = Date.now();\n let action: Action;\n try {\n action = bot.decide(state);\n if (!(action instanceof Action)) {\n // User code returned the wrong type. Under safeMode, fold; otherwise\n // this is a deterministic bug — surface it.\n if (!safeMode) {\n throw new BotDecisionError(\n `bot.decide() returned a non-Action value (${typeof action})`,\n );\n }\n action = _safeFallbackAction(validActions);\n }\n } catch (err) {\n if (err instanceof BotDecisionError) throw err;\n if (!safeMode) {\n throw new BotDecisionError(err instanceof Error ? err.message : String(err));\n }\n action = _safeFallbackAction(validActions);\n }\n return { action, latencyMs: Date.now() - start };\n}\n\n/**\n * Drive a single connected session: handshake + message loop until\n * `match_end`. Returns the `match_end` payload on a clean finish, or\n * `null` if the handshake failed / the socket closed without a\n * `match_end` (the caller decides whether to reconnect). Exported for\n * the conformance harness in `conformance.ts`.\n */\nexport async function _runSession(\n ws: WebSocket | SessionWebSocket,\n bot: Bot,\n ctx: SessionContext,\n /** Optional pre-built reader; the real client builds one from the ws. */\n readerOverride?: AsyncMessageReader,\n): Promise<Record<string, unknown> | null> {\n const reader = readerOverride ?? new _NodeWebSocketReader(ws as WebSocket);\n const safeMode = ctx.safeMode ?? true;\n\n // --- Layer 1 handshake ------------------------------------------------\n const auth: Record<string, unknown> = {\n type: \"authenticate\",\n match_id: ctx.matchId,\n client_name: ctx.clientName,\n client_version: ctx.clientVersion,\n };\n if (ctx.token) auth.token = ctx.token;\n if (ctx.ticket) auth.ticket = ctx.ticket;\n await sendJson(ws, auth);\n\n const helloRaw = await reader.next();\n if (!helloRaw) {\n throw new Error(\"connection closed before server hello\");\n }\n const hello = parseJson(helloRaw);\n if (hello.type !== \"hello\") {\n throw new Error(`expected server hello, got ${hello.type ?? \"<no type>\"}`);\n }\n\n await sendJson(ws, {\n type: \"hello\",\n match_id: ctx.matchId,\n supported_versions: [...SUPPORTED_PROTOCOL_VERSIONS],\n });\n\n // --- Message loop -----------------------------------------------------\n let lastSeq = 0;\n for (;;) {\n const raw = await reader.next();\n if (raw === null) {\n // Connection closed (peer or transport) without a match_end. Caller\n // decides whether to retry — we just exit the loop here.\n return null;\n }\n\n let msg: Record<string, unknown>;\n try {\n msg = parseJson(raw);\n } catch {\n // Malformed envelope — log + continue. Real production deployments\n // never emit invalid JSON; this is for adversarial-input robustness.\n continue;\n }\n\n if (typeof msg.seq === \"number\" && msg.seq <= lastSeq) {\n // Sequence regression — likely a retransmit. Skip silently.\n continue;\n }\n if (typeof msg.seq === \"number\") lastSeq = msg.seq;\n\n const type = msg.type as string | undefined;\n switch (type) {\n case \"ping\":\n await sendJson(ws, { type: \"pong\", match_id: ctx.matchId });\n break;\n\n case \"match_start\":\n bot.onMatchStart(msg);\n break;\n\n case \"round_start\":\n bot.onRoundStart(msg);\n break;\n\n case \"phase_change\":\n bot.onPhaseChange(msg);\n break;\n\n case \"turn_result\":\n bot.onTurnResult(msg);\n break;\n\n case \"round_result\":\n bot.onRoundResult(msg);\n break;\n\n case \"turn_request\": {\n const requestId = (msg.request_id as string | undefined) ?? \"\";\n const state = parseGameState(msg);\n // _decide handles timing + safeMode; under safeMode=false it\n // throws BotDecisionError, which propagates out of _runSession.\n const { action, latencyMs } = _decide(\n bot,\n state,\n msg.valid_actions as string[] | undefined,\n safeMode,\n );\n await sendJson(ws, {\n type: \"turn_action\",\n match_id: ctx.matchId,\n request_id: requestId,\n ...action.toWire(),\n });\n bot.onDecisionLatency(latencyMs);\n break;\n }\n\n case \"action_rejected\": {\n // Server rejected our action. Retry with a safe fallback,\n // echoing the original request_id.\n const requestId = (msg.request_id as string | undefined) ?? \"\";\n const validActions = (msg.valid_actions as string[] | undefined) ?? [\"fold\"];\n const fallback = _safeFallbackAction(validActions);\n await sendJson(ws, {\n type: \"turn_action\",\n match_id: ctx.matchId,\n request_id: requestId,\n ...fallback.toWire(),\n });\n break;\n }\n\n case \"reconnected\": {\n // The server is replaying state after we reconnected. If a\n // pending_request is included, dispatch it as if it were a\n // fresh turn_request.\n const pending = msg.pending_request as Record<string, unknown> | undefined;\n if (pending && pending.type === \"turn_request\") {\n // Replay the pending turn exactly like a fresh turn_request.\n const requestId = (pending.request_id as string | undefined) ?? \"\";\n const { action, latencyMs } = _decide(\n bot,\n parseGameState(pending),\n pending.valid_actions as string[] | undefined,\n safeMode,\n );\n await sendJson(ws, {\n type: \"turn_action\",\n match_id: ctx.matchId,\n request_id: requestId,\n ...action.toWire(),\n });\n bot.onDecisionLatency(latencyMs);\n }\n break;\n }\n\n case \"match_end\":\n bot.onMatchEnd((msg.results as Record<string, unknown>) ?? msg);\n return msg; // clean exit — hand the match_end payload to the caller\n\n case \"error\":\n // Non-fatal — log + continue (the real logging plumbing belongs\n // in the bot author's code; the SDK stays quiet).\n break;\n\n default:\n // Unknown message type — forward-compat: just ignore.\n break;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nexport interface AsyncMessageReader {\n next(): Promise<string | null>;\n}\n\n/**\n * Adapts a `ws.WebSocket` to a pull-based async iterator the session\n * loop can drive.\n *\n * Resolves with `null` when the underlying socket closes. Errors on\n * the socket are not surfaced to consumers — they cause `next()` to\n * return `null` and the caller's outer reconnect logic decides what to do.\n */\nexport class _NodeWebSocketReader implements AsyncMessageReader {\n private readonly queue: string[] = [];\n private readonly waiters: Array<(msg: string | null) => void> = [];\n private closed = false;\n\n constructor(ws: WebSocket) {\n ws.on(\"message\", (data) => {\n const msg = data.toString();\n const w = this.waiters.shift();\n if (w) w(msg);\n else this.queue.push(msg);\n });\n ws.on(\"close\", () => this._close());\n ws.on(\"error\", () => this._close());\n }\n\n async next(): Promise<string | null> {\n const queued = this.queue.shift();\n if (queued !== undefined) return queued;\n if (this.closed) return null;\n return new Promise((resolve) => this.waiters.push(resolve));\n }\n\n private _close(): void {\n if (this.closed) return;\n this.closed = true;\n while (this.waiters.length) {\n const w = this.waiters.shift();\n if (w) w(null);\n }\n }\n}\n\nexport function _waitForOpen(ws: WebSocket): Promise<void> {\n if (ws.readyState === WebSocket.OPEN) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const onOpen = (): void => {\n ws.removeListener(\"error\", onError);\n resolve();\n };\n const onError = (err: Error): void => {\n ws.removeListener(\"open\", onOpen);\n reject(err);\n };\n ws.once(\"open\", onOpen);\n ws.once(\"error\", onError);\n });\n}\n\nexport function _safeFallbackAction(validActions: string[] | undefined): Action {\n const valid = new Set(validActions ?? []);\n if (valid.has(\"check\")) return Action.check();\n return Action.fold();\n}\n\n/**\n * Pull `match_id` out of a Chipzen WebSocket URL. Path shape is\n * `.../ws/match/<match_id>/...`. Returns `\"\"` if the URL doesn't\n * match the expected pattern.\n */\nexport function _extractMatchId(url: string): string {\n // Match the segment between \"/ws/match/\" and the next \"/\" (or end of URL).\n // Permissive on the inner shape — server-side IDs may be UUIDs,\n // shortened hashes, or namespaced strings like `m_abc_123`.\n const m = /\\/ws\\/match\\/([^/?#]+)/.exec(url);\n return m ? (m[1] ?? \"\") : \"\";\n}\n\nasync function sendJson(\n ws: WebSocket | SessionWebSocket,\n msg: Record<string, unknown>,\n): Promise<void> {\n const payload = JSON.stringify(msg);\n const result = (ws as { send: (data: string) => unknown }).send(payload);\n if (result && typeof (result as Promise<unknown>).then === \"function\") {\n await (result as Promise<unknown>);\n }\n}\n\nfunction parseJson(raw: string): Record<string, unknown> {\n return JSON.parse(raw) as Record<string, unknown>;\n}\n","/**\n * Core data models — Action, GameState, Card.\n *\n * Field naming uses idiomatic camelCase for the SDK's user-facing\n * surface. The on-the-wire JSON the protocol uses is snake_case\n * (Layer 1 / Layer 2 spec); the parsers in this module translate.\n */\n\n// ---------------------------------------------------------------------------\n// Card\n// ---------------------------------------------------------------------------\n\n/**\n * A standard playing card. `rank` is one of `2`-`9`, `T`, `J`, `Q`,\n * `K`, `A`. `suit` is one of `h` (hearts), `d` (diamonds), `c`\n * (clubs), `s` (spades).\n */\nexport interface Card {\n readonly rank: string;\n readonly suit: string;\n}\n\nconst VALID_RANKS = new Set([\n \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"T\", \"J\", \"Q\", \"K\", \"A\",\n]);\nconst VALID_SUITS = new Set([\"h\", \"d\", \"c\", \"s\"]);\n\n/**\n * Parse a card from its 2-character wire representation, e.g. `\"Ah\"`.\n *\n * Throws `Error` on malformed input; the wire format is\n * always exactly 2 characters with a valid rank + suit.\n */\nexport function cardFromString(s: string): Card {\n if (typeof s !== \"string\" || s.length !== 2) {\n throw new Error(`Invalid card string: ${JSON.stringify(s)} (expected 2 chars)`);\n }\n const rank = s[0]!;\n const suit = s[1]!;\n if (!VALID_RANKS.has(rank)) {\n throw new Error(`Invalid card rank: ${JSON.stringify(rank)} in ${JSON.stringify(s)}`);\n }\n if (!VALID_SUITS.has(suit)) {\n throw new Error(`Invalid card suit: ${JSON.stringify(suit)} in ${JSON.stringify(s)}`);\n }\n return { rank, suit };\n}\n\n/** Render a card back to its 2-character wire form. */\nexport function cardToString(c: Card): string {\n return `${c.rank}${c.suit}`;\n}\n\n// ---------------------------------------------------------------------------\n// Action\n// ---------------------------------------------------------------------------\n\nexport type ActionKind = \"fold\" | \"check\" | \"call\" | \"raise\" | \"all_in\";\n\n/**\n * The action a bot returns from `decide()`.\n *\n * Construct via the static factories (`Action.fold()` etc.) — they\n * validate the action vs. amount invariants for you.\n */\nexport class Action {\n private constructor(\n public readonly action: ActionKind,\n public readonly amount?: number,\n ) {\n if (action === \"raise\") {\n if (amount === undefined || !Number.isFinite(amount) || amount < 0) {\n throw new Error(\n `Action.raiseTo requires a non-negative finite amount; got ${amount}`,\n );\n }\n } else if (amount !== undefined) {\n throw new Error(\n `Only raise actions take an amount; ${action} got amount=${amount}`,\n );\n }\n }\n\n static fold(): Action {\n return new Action(\"fold\");\n }\n\n static check(): Action {\n return new Action(\"check\");\n }\n\n static call(): Action {\n return new Action(\"call\");\n }\n\n static raiseTo(amount: number): Action {\n return new Action(\"raise\", amount);\n }\n\n static allIn(): Action {\n return new Action(\"all_in\");\n }\n\n /**\n * Serialize to the two-layer `turn_action` payload shape the server expects.\n *\n * Returns `{action, params}` where `params` carries the raise amount\n * for `raise` and is empty for everything else.\n */\n toWire(): { action: ActionKind; params: Record<string, unknown> } {\n const params: Record<string, unknown> = {};\n if (this.action === \"raise\" && this.amount !== undefined) {\n params.amount = this.amount;\n }\n return { action: this.action, params };\n }\n}\n\n// ---------------------------------------------------------------------------\n// GameState\n// ---------------------------------------------------------------------------\n\n/**\n * A single entry from `state.actionHistory`. Synthetic blind/ante\n * entries (`post_small_blind`, `post_big_blind`, `post_ante`) appear\n * here too — the server generates them; bots do not submit them.\n */\nexport interface ActionHistoryEntry {\n readonly seat: number;\n readonly action: string;\n readonly amount?: number;\n readonly isTimeout?: boolean;\n}\n\n/**\n * Built from the server's `turn_request` message. The parser in\n * `parseGameState` converts the wire-format snake_case to the\n * camelCase fields below.\n */\nexport interface GameState {\n readonly handNumber: number;\n readonly phase: \"preflop\" | \"flop\" | \"turn\" | \"river\";\n readonly holeCards: readonly Card[];\n readonly board: readonly Card[];\n readonly pot: number;\n readonly yourStack: number;\n readonly opponentStacks: readonly number[];\n readonly yourSeat: number;\n readonly dealerSeat: number;\n readonly toCall: number;\n readonly minRaise: number;\n readonly maxRaise: number;\n readonly validActions: readonly string[];\n readonly actionHistory: readonly ActionHistoryEntry[];\n readonly roundId: string;\n readonly requestId: string;\n}\n\ninterface RawTurnRequest {\n request_id?: string;\n round_id?: string;\n valid_actions?: string[];\n state?: Record<string, unknown> | null;\n}\n\n/**\n * Parse a `turn_request` envelope into a `GameState`.\n *\n * The wire shape is documented in\n * `docs/protocol/POKER-GAME-STATE-PROTOCOL.md`. All fields default\n * to safe values when absent — but a real server always sends them.\n */\nexport function parseGameState(message: RawTurnRequest): GameState {\n const state = (message?.state ?? {}) as Record<string, unknown>;\n const holeStrs = (state[\"your_hole_cards\"] as string[] | undefined) ?? [];\n const boardStrs = (state[\"board\"] as string[] | undefined) ?? [];\n const validActions =\n (message?.valid_actions as string[] | undefined) ??\n (state[\"valid_actions\"] as string[] | undefined) ??\n [];\n const actionHistoryRaw = (state[\"action_history\"] as RawHistoryEntry[] | undefined) ?? [];\n\n return {\n handNumber: numberOrZero(state[\"hand_number\"]),\n phase: (state[\"phase\"] as GameState[\"phase\"] | undefined) ?? \"preflop\",\n holeCards: holeStrs.map(cardFromString),\n board: boardStrs.map(cardFromString),\n pot: numberOrZero(state[\"pot\"]),\n yourStack: numberOrZero(state[\"your_stack\"]),\n opponentStacks: ((state[\"opponent_stacks\"] as number[] | undefined) ?? []).map(Number),\n yourSeat: numberOrZero(state[\"your_seat\"]),\n dealerSeat: numberOrZero(state[\"dealer_seat\"]),\n toCall: numberOrZero(state[\"to_call\"]),\n minRaise: numberOrZero(state[\"min_raise\"]),\n maxRaise: numberOrZero(state[\"max_raise\"]),\n validActions,\n actionHistory: actionHistoryRaw.map(parseHistoryEntry),\n roundId: (message?.round_id as string | undefined) ?? \"\",\n requestId: (message?.request_id as string | undefined) ?? \"\",\n };\n}\n\ninterface RawHistoryEntry {\n seat?: number;\n action?: string;\n amount?: number;\n is_timeout?: boolean;\n}\n\nfunction parseHistoryEntry(raw: RawHistoryEntry): ActionHistoryEntry {\n const entry: { seat: number; action: string; amount?: number; isTimeout?: boolean } = {\n seat: numberOrZero(raw.seat),\n action: raw.action ?? \"\",\n };\n if (typeof raw.amount === \"number\") entry.amount = raw.amount;\n if (typeof raw.is_timeout === \"boolean\") entry.isTimeout = raw.is_timeout;\n return entry;\n}\n\nfunction numberOrZero(v: unknown): number {\n return typeof v === \"number\" && Number.isFinite(v) ? v : 0;\n}\n","{\n \"name\": \"@chipzen-ai/bot\",\n \"version\": \"0.3.0\",\n \"description\": \"Build, test, and deploy poker bots for the Chipzen AI competition platform\",\n \"license\": \"Apache-2.0\",\n \"author\": {\n \"name\": \"Chipzen, Inc.\",\n \"email\": \"support@chipzen.ai\"\n },\n \"homepage\": \"https://chipzen.ai\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/chipzen-ai/chipzen-sdk.git\",\n \"directory\": \"packages/javascript\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/chipzen-ai/chipzen-sdk/issues\"\n },\n \"keywords\": [\n \"poker\",\n \"bot\",\n \"ai\",\n \"competition\",\n \"chipzen\",\n \"sdk\",\n \"websocket\"\n ],\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"bin\": {\n \"chipzen-sdk\": \"./dist/bin.js\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"CHANGELOG.md\"\n ],\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"dependencies\": {\n \"ws\": \"^8.18.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.14.0\",\n \"@types/ws\": \"^8.5.12\",\n \"tsup\": \"^8.3.0\",\n \"typescript\": \"^5.6.0\",\n \"vitest\": \"^2.1.0\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"tsc --noEmit\",\n \"prepack\": \"pnpm build\"\n }\n}\n","/**\n * Single source of truth for the SDK version string.\n *\n * Reads `version` from the package's own `package.json` so the value the\n * handshake reports always tracks the published package (chipzen-ai/chipzen-sdk#41)\n * — no hardcoded literal to drift out of sync on a release bump.\n *\n * The import is resolved against `../package.json` (one level up from\n * `src/`). `tsup` inlines the JSON at build time, so the bundled output\n * carries the literal value with no runtime filesystem read.\n */\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-imports\nimport pkg from \"../package.json\" with { type: \"json\" };\n\n/** The installed SDK version, e.g. `\"0.3.0\"`. */\nexport const VERSION: string = (pkg as { version: string }).version;\n","/**\n * `chipzen-sdk init <name>` — scaffold a new Chipzen bot project.\n *\n * Mirrors the Python `chipzen.scaffold` shape so cross-language\n * developers see the same outputs. The scaffolded project is plain\n * ESM JavaScript (no TypeScript dep required to run); convert to TS\n * if you want by renaming bot.js -> bot.ts and adding a tsconfig.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface ScaffoldOptions {\n /** Where to create the new project. Defaults to cwd. */\n parentDir?: string;\n}\n\nexport async function scaffoldBot(name: string, options: ScaffoldOptions = {}): Promise<string> {\n if (!isValidProjectName(name)) {\n throw new Error(\n `Invalid project name: ${JSON.stringify(name)}. ` +\n \"Use ASCII letters, digits, underscores, and dashes only.\",\n );\n }\n const parent = options.parentDir ?? process.cwd();\n const projectDir = path.join(parent, name);\n\n // Avoid clobbering an existing directory.\n try {\n await fs.access(projectDir);\n throw new Error(`Directory already exists: ${projectDir}`);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw err;\n }\n }\n\n await fs.mkdir(projectDir, { recursive: true });\n\n await fs.writeFile(path.join(projectDir, \"bot.js\"), BOT_TEMPLATE, \"utf-8\");\n await fs.writeFile(path.join(projectDir, \"package.json\"), packageJsonTemplate(name), \"utf-8\");\n await fs.writeFile(path.join(projectDir, \"Dockerfile\"), DOCKERFILE_TEMPLATE, \"utf-8\");\n await fs.writeFile(path.join(projectDir, \".dockerignore\"), DOCKERIGNORE, \"utf-8\");\n await fs.writeFile(path.join(projectDir, \".gitignore\"), GITIGNORE, \"utf-8\");\n await fs.writeFile(path.join(projectDir, \"README.md\"), readmeTemplate(name), \"utf-8\");\n\n return projectDir;\n}\n\nfunction isValidProjectName(name: string): boolean {\n // Same rule the Python scaffold uses — keep cross-language consistency.\n return /^[A-Za-z0-9_-]+$/.test(name);\n}\n\nconst BOT_TEMPLATE = `// Chipzen starter bot — replace decide() with your strategy.\n// The SDK handles WebSocket, handshake, ping/pong, retries, and reconnect.\n\nimport { Bot, Action, runBot } from \"@chipzen-ai/bot\";\n\nclass MyBot extends Bot {\n decide(state) {\n // Return one of: Action.fold(), Action.check(), Action.call(),\n // Action.raiseTo(amount), Action.allIn(). The chosen action's\n // wire-form must be in state.validActions; raises must satisfy\n // state.minRaise <= amount <= state.maxRaise.\n if (state.validActions.includes(\"check\")) return Action.check();\n return Action.fold();\n }\n}\n\nexport async function main() {\n const url = process.env.CHIPZEN_WS_URL ?? process.argv[2];\n if (!url) {\n console.error(\"error: CHIPZEN_WS_URL not set and no URL passed on the command line\");\n process.exit(1);\n }\n await runBot(url, new MyBot(), {\n token: process.env.CHIPZEN_TOKEN ?? null,\n ticket: process.env.CHIPZEN_TICKET ?? null,\n });\n}\n\n// Run main() when this file is the entry point — covers both\n// \\`node bot.js\\` (Node sets import.meta.url to a file:// URL matching\n// argv[1]) and \\`bun build --compile\\` binaries (Bun sets\n// import.meta.main on the entry module). Importing from a test file\n// makes both checks false so MyBot can be exercised in isolation.\nif (import.meta.main || import.meta.url === \\`file://\\${process.argv[1]}\\`) {\n await main();\n}\n\nexport { MyBot };\n`;\n\nfunction packageJsonTemplate(name: string): string {\n return JSON.stringify(\n {\n name,\n version: \"0.1.0\",\n private: true,\n type: \"module\",\n main: \"bot.js\",\n scripts: {\n start: \"node bot.js\",\n },\n dependencies: {\n \"@chipzen-ai/bot\": \"^0.2.0\",\n },\n engines: {\n node: \">=20\",\n },\n },\n null,\n 2,\n ) + \"\\n\";\n}\n\n// Kept identical to packages/javascript/starters/javascript/Dockerfile so a\n// scaffolded project and the canonical starter directory ship the same recipe.\n// A test enforces this invariant.\nconst DOCKERFILE_TEMPLATE = `# syntax=docker/dockerfile:1.7\n#\n# IP-protected Chipzen JavaScript bot image.\n#\n# Multi-stage build that bundles bot.js + the SDK into a single\n# statically-linked binary via \\`bun build --compile\\` in the builder\n# stage, then ships only that binary in the runtime stage. The runtime\n# image contains no readable .js source for your strategy code.\n#\n# See ../../IP-PROTECTION.md for what this protects (and what it doesn't).\n#\n# Build: docker build -t my-bot:test .\n# Export: docker save my-bot:test | gzip > my-bot.tar.gz\n#\n# Build context for this directory should be small (bot.js +\n# package.json + this file). The .dockerignore alongside this file\n# keeps node_modules, caches, and lockfile metadata out.\n\n# -----------------------------------------------------------------------------\n# Stage 1: Bun bundle + compile. The .js source lives only in this stage and\n# is discarded before the runtime stage starts.\n# -----------------------------------------------------------------------------\n# Base pinned by tag — Dependabot can rotate to digest pinning later. Tag:\n# oven/bun:1-debian (glibc-based; the runtime stage below also uses glibc\n# so the compiled binary's dynamic linker can find what it needs).\nFROM oven/bun:1-debian AS builder\n\nWORKDIR /build\n\n# Bring in the bot source + dependency manifest. Only these are copied —\n# keep the build context narrow so the .dockerignore is the only allowlist.\nCOPY package.json bot.js ./\n\n# Resolve @chipzen-ai/bot + ws so \\`bun build\\` can bundle them. We don't\n# need a lockfile committed; the registry pin in package.json is enough\n# for a fresh install at build time.\nRUN bun install --production\n\n# Compile bot.js -> /build/bot. \\`--compile\\` emits a single executable\n# that bundles the JS, all deps, and the Bun runtime statically.\n# \\`--minify\\` shrinks output; \\`--sourcemap=none\\` excludes maps so the\n# strategy isn't trivially readable from inside the binary.\nRUN bun build --compile --minify --sourcemap=none --target=bun-linux-x64 \\\\\n bot.js --outfile=/build/bot \\\\\n && rm bot.js \\\\\n && rm -rf node_modules\n\n# -----------------------------------------------------------------------------\n# Stage 2: Runtime. Only the compiled binary + ENTRYPOINT.\n# No .js source for the bot's strategy is present.\n# -----------------------------------------------------------------------------\n# Base pinned by tag — Dependabot can rotate to digest pinning later. Tag:\n# debian:12-slim (matches the glibc the Bun --compile output expects).\nFROM debian:12-slim\n\n# CA certs are needed for outbound TLS (the platform's WebSocket\n# endpoint is wss://). dumb-init reaps any subprocesses cleanly on\n# container exit; tiny but useful.\nRUN apt-get update \\\\\n && apt-get install -y --no-install-recommends ca-certificates dumb-init \\\\\n && rm -rf /var/lib/apt/lists/*\n\nWORKDIR /bot\n\n# Copy ONLY the compiled binary from the builder stage.\nCOPY --from=builder /build/bot /bot/bot\nRUN chmod +x /bot/bot\n\n# Run as non-root (defense in depth — the platform also applies seccomp\n# and cap-drop on top of this).\nRUN groupadd --system --gid 10001 bot \\\\\n && useradd --system --uid 10001 --gid bot --home-dir /bot --shell /usr/sbin/nologin bot \\\\\n && chown -R bot:bot /bot\nUSER 10001\n\nENTRYPOINT [\"dumb-init\", \"/bot/bot\"]\n`;\nexport const _DOCKERFILE_TEMPLATE_FOR_TEST = DOCKERFILE_TEMPLATE;\n\nconst DOCKERIGNORE = `node_modules/\n.git/\n.gitignore\n.env\n.env.*\n*.md\nREADME*\nLICENSE*\n*.log\ncoverage/\n.DS_Store\n`;\n\nconst GITIGNORE = `node_modules/\n*.log\n.DS_Store\n.env\n.env.*\ncoverage/\ndist/\n`;\n\nfunction readmeTemplate(name: string): string {\n return `# ${name}\n\nA poker bot for the [Chipzen](https://chipzen.ai) platform.\n\n## Quick start\n\n\\`\\`\\`bash\nnpm install\n\\`\\`\\`\n\nEdit \\`bot.js\\` to implement your strategy in the \\`decide()\\` method.\n\nValidate before uploading:\n\n\\`\\`\\`bash\nchipzen-sdk validate .\n\\`\\`\\`\n\nBuild and export the upload tarball:\n\n\\`\\`\\`bash\ndocker build -t ${name}:v1 .\ndocker save ${name}:v1 | gzip > ${name}.tar.gz\n\\`\\`\\`\n\nThen upload via the Chipzen platform UI.\n`;\n}\n","/**\n * `chipzen-sdk validate <path>` — pre-upload conformance checks.\n *\n * Mirrors the Python validator's check shape and severity model so a\n * `(severity, name, message)` tuple from either language renders the\n * same way in client tooling.\n */\n\nimport { execSync } from \"node:child_process\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { type GameState } from \"./models.js\";\nimport { runConformanceChecks } from \"./conformance.js\";\nimport type { Bot } from \"./bot.js\";\n\nconst VALID_ACTION_KINDS = new Set([\"fold\", \"check\", \"call\", \"raise\", \"all_in\"]);\n\n/**\n * Duck-type check for an `Action`. We can't use `instanceof Action`\n * because the bot under test imports `Action` from a separate module\n * graph (the published dist or a file:// URL) — those classes are\n * structurally identical to ours but reference-distinct, so\n * `instanceof` would always be false.\n */\nfunction isAction(v: unknown): boolean {\n if (!v || typeof v !== \"object\") return false;\n const a = v as { action?: unknown; amount?: unknown };\n if (typeof a.action !== \"string\" || !VALID_ACTION_KINDS.has(a.action)) return false;\n if (a.action === \"raise\") {\n return typeof a.amount === \"number\" && Number.isFinite(a.amount) && a.amount >= 0;\n }\n return a.amount === undefined;\n}\n\nexport type Severity = \"pass\" | \"warn\" | \"fail\";\n\nexport interface ValidationResult {\n severity: Severity;\n name: string;\n message: string;\n}\n\nexport interface ValidateOptions {\n /** Override entry point filename (default: auto-detect bot.js / bot.mjs / bot.cjs). */\n entryPoint?: string;\n /** Hard-fail upload size threshold, in bytes. Defaults to 500 MB (platform cap). */\n maxUploadBytes?: number;\n /** Warn if `decide()` takes longer than this (ms). */\n timeoutWarnMs?: number;\n /**\n * If true, run the protocol-conformance harness after the smoke test\n * passes — drives the bot through one full canned match (handshake +\n * 1 hand + match_end) against an in-process mock WebSocket.\n */\n checkConnectivity?: boolean;\n /** Per-conformance-scenario timeout (ms). Default 10s. */\n conformanceTimeoutMs?: number;\n}\n\nexport const DEFAULT_MAX_UPLOAD_BYTES = 500 * 1024 * 1024;\nexport const DEFAULT_TIMEOUT_WARN_MS = 100;\nexport const PLATFORM_TIMEOUT_MS = 500;\n\nconst ALLOWED_ENTRY_POINTS = [\"bot.js\", \"bot.mjs\", \"bot.cjs\", \"main.js\", \"main.mjs\"];\n\n/**\n * Modules whose import would either fail in the platform sandbox or\n * indicate a class of bot we don't allow. Mirrors the Python\n * BLOCKED_MODULES set; not exhaustive — caught here as a fast pre-flight.\n */\nconst BLOCKED_MODULES = new Set([\n \"node:child_process\",\n \"child_process\",\n \"node:cluster\",\n \"cluster\",\n \"node:dgram\",\n \"dgram\",\n \"node:dns\",\n \"dns\",\n \"node:net\",\n \"net\",\n \"node:tls\",\n \"tls\",\n \"node:repl\",\n \"repl\",\n \"node:vm\",\n \"vm\",\n \"node:worker_threads\",\n \"worker_threads\",\n]);\n\n/**\n * Modules that might be fine but warrant a warning so the bot author\n * knows they're at the edge of what the sandbox tolerates.\n */\nconst WARN_MODULES = new Set([\"node:fs\", \"fs\", \"node:fs/promises\"]);\n\n// ---------------------------------------------------------------------------\n// Public entry\n// ---------------------------------------------------------------------------\n\nexport async function validateBot(\n botPath: string,\n options: ValidateOptions = {},\n): Promise<ValidationResult[]> {\n const results: ValidationResult[] = [];\n const maxBytes = options.maxUploadBytes ?? DEFAULT_MAX_UPLOAD_BYTES;\n const timeoutWarn = options.timeoutWarnMs ?? DEFAULT_TIMEOUT_WARN_MS;\n\n let stat;\n try {\n stat = await fs.stat(botPath);\n } catch {\n results.push({\n severity: \"fail\",\n name: \"file_structure\",\n message: `Path not found: ${botPath}`,\n });\n return results;\n }\n if (!stat.isDirectory()) {\n results.push({\n severity: \"fail\",\n name: \"file_structure\",\n message: `Path is not a directory: ${botPath}`,\n });\n return results;\n }\n\n results.push(...(await checkDirectorySize(botPath, maxBytes)));\n results.push(\n ...(await checkDirectory(\n botPath,\n options.entryPoint,\n timeoutWarn,\n options.checkConnectivity ?? false,\n options.conformanceTimeoutMs ?? 10_000,\n )),\n );\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Per-check implementations\n// ---------------------------------------------------------------------------\n\nasync function checkDirectorySize(\n dir: string,\n maxBytes: number,\n): Promise<ValidationResult[]> {\n const total = await dirTotalBytes(dir);\n const mb = total / (1024 * 1024);\n const limitMb = maxBytes / (1024 * 1024);\n if (total > maxBytes) {\n return [\n {\n severity: \"fail\",\n name: \"size\",\n message: `Directory is ${mb.toFixed(1)} MB, exceeds ${limitMb.toFixed(0)} MB upload limit`,\n },\n ];\n }\n return [\n {\n severity: \"pass\",\n name: \"size\",\n message: `Size OK (${mb.toFixed(1)} MB uncompressed / ${limitMb.toFixed(0)} MB limit)`,\n },\n ];\n}\n\nasync function checkDirectory(\n dir: string,\n entryPointOverride: string | undefined,\n timeoutWarnMs: number,\n checkConnectivity: boolean,\n conformanceTimeoutMs: number,\n): Promise<ValidationResult[]> {\n const results: ValidationResult[] = [];\n\n const entry = await findEntryPoint(dir, entryPointOverride);\n if (!entry) {\n results.push({\n severity: \"fail\",\n name: \"file_structure\",\n message: `No entry point found. Expected one of: ${ALLOWED_ENTRY_POINTS.join(\", \")}`,\n });\n return results;\n }\n results.push({\n severity: \"pass\",\n name: \"file_structure\",\n message: `Entry point found: ${path.basename(entry)}`,\n });\n\n // syntax — defer to `node --check` so we get Node's actual parser.\n const syntaxResult = await checkSyntax(entry);\n results.push(syntaxResult);\n if (syntaxResult.severity === \"fail\") return results;\n\n // imports — regex scan for blocked / warned modules.\n const source = await fs.readFile(entry, \"utf-8\");\n results.push(...checkImports(source, path.basename(entry)));\n\n // bot_class — regex scan for `class X extends Bot`.\n const botClassResult = checkBotClass(source);\n results.push(botClassResult);\n if (botClassResult.severity === \"fail\") return results;\n const botClassName = botClassResult.message.replace(\"Found bot class: \", \"\");\n\n // decide_method — regex scan for `decide(` inside the class body.\n const decideResult = checkDecideMethod(source, botClassName);\n results.push(decideResult);\n if (decideResult.severity === \"fail\") return results;\n\n // smoke_test + timeout — dynamic import + invoke decide() once.\n results.push(\n ...(await smokeTest(entry, botClassName, timeoutWarnMs, checkConnectivity, conformanceTimeoutMs)),\n );\n\n return results;\n}\n\nasync function findEntryPoint(\n dir: string,\n override: string | undefined,\n): Promise<string | null> {\n const candidates = override ? [override] : ALLOWED_ENTRY_POINTS;\n for (const name of candidates) {\n const candidate = path.join(dir, name);\n try {\n const s = await fs.stat(candidate);\n if (s.isFile()) return candidate;\n } catch {\n // try next\n }\n }\n return null;\n}\n\nasync function checkSyntax(filePath: string): Promise<ValidationResult> {\n try {\n execSync(`node --check ${JSON.stringify(filePath)}`, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: 5000,\n });\n return { severity: \"pass\", name: \"syntax\", message: \"Valid JavaScript syntax\" };\n } catch (err) {\n const stderr = (err as { stderr?: Buffer }).stderr?.toString() ?? String(err);\n const firstLine = stderr.split(\"\\n\").find((l) => l.includes(\"SyntaxError\")) ?? stderr.split(\"\\n\")[0] ?? stderr;\n return {\n severity: \"fail\",\n name: \"syntax\",\n message: `Syntax error: ${firstLine.trim()}`,\n };\n }\n}\n\nfunction checkImports(source: string, filename: string): ValidationResult[] {\n const results: ValidationResult[] = [];\n // Match `import ... from \"X\"` and `import(\"X\")` and `require(\"X\")`\n const importRe = /(?:from|import|require)\\s*\\(?\\s*[\"']([^\"']+)[\"']/g;\n const found = new Set<string>();\n let m;\n while ((m = importRe.exec(source)) !== null) {\n if (m[1]) found.add(m[1]);\n }\n\n const blocked: string[] = [];\n const warned: string[] = [];\n for (const mod of found) {\n if (BLOCKED_MODULES.has(mod)) blocked.push(mod);\n else if (WARN_MODULES.has(mod)) warned.push(mod);\n }\n\n if (blocked.length) {\n results.push({\n severity: \"fail\",\n name: \"imports\",\n message: `Blocked imports detected in ${filename}: ${blocked.join(\", \")}`,\n });\n } else {\n results.push({\n severity: \"pass\",\n name: \"imports\",\n message: \"No blocked imports detected\",\n });\n }\n for (const w of warned) {\n results.push({\n severity: \"warn\",\n name: \"imports\",\n message: `Imports ${JSON.stringify(w)} — usable but the platform sandbox restricts what it can do`,\n });\n }\n return results;\n}\n\nfunction checkBotClass(source: string): ValidationResult {\n // Look for `class XYZ extends Bot` or `class XYZ extends ChipzenBot`.\n const m = /class\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s+extends\\s+(?:Bot|ChipzenBot)\\b/.exec(source);\n if (!m || !m[1]) {\n return {\n severity: \"fail\",\n name: \"bot_class\",\n message: \"No class extending Bot (or ChipzenBot) found in entry point\",\n };\n }\n return {\n severity: \"pass\",\n name: \"bot_class\",\n message: `Found bot class: ${m[1]}`,\n };\n}\n\nfunction checkDecideMethod(source: string, className: string): ValidationResult {\n // Find the class block, then look for `decide(` inside it.\n // Imperfect (regex-based; nested classes / minified code can fool it),\n // but matches the Python validator's level of scrutiny for an alpha-tier\n // pre-flight check. The smoke_test below catches the actual runtime case.\n const classBlock = extractClassBlock(source, className);\n if (!classBlock) {\n return {\n severity: \"fail\",\n name: \"decide_method\",\n message: `Could not isolate ${className}'s class body for decide() check`,\n };\n }\n if (!/\\bdecide\\s*\\(/.test(stripComments(classBlock))) {\n return {\n severity: \"fail\",\n name: \"decide_method\",\n message: `${className} does not implement decide()`,\n };\n }\n return {\n severity: \"pass\",\n name: \"decide_method\",\n message: `${className}.decide() implemented`,\n };\n}\n\nfunction extractClassBlock(source: string, className: string): string | null {\n const start = source.search(new RegExp(`class\\\\s+${className}\\\\s+extends\\\\s+(?:Bot|ChipzenBot)\\\\b`));\n if (start < 0) return null;\n // Find the opening brace, then scan forward counting braces.\n const braceIdx = source.indexOf(\"{\", start);\n if (braceIdx < 0) return null;\n let depth = 1;\n for (let i = braceIdx + 1; i < source.length; i++) {\n if (source[i] === \"{\") depth++;\n else if (source[i] === \"}\") {\n depth--;\n if (depth === 0) return source.slice(braceIdx + 1, i);\n }\n }\n return null;\n}\n\nasync function smokeTest(\n entry: string,\n className: string,\n timeoutWarnMs: number,\n checkConnectivity: boolean,\n conformanceTimeoutMs: number,\n): Promise<ValidationResult[]> {\n const results: ValidationResult[] = [];\n\n let mod: Record<string, unknown>;\n try {\n // file:// URL is required for ESM dynamic import of an absolute path.\n const url = new URL(`file://${path.resolve(entry)}`).toString();\n mod = (await import(url)) as Record<string, unknown>;\n } catch (err) {\n results.push({\n severity: \"fail\",\n name: \"smoke_test\",\n message: `Failed to import bot: ${(err as Error).message}`,\n });\n return results;\n }\n\n const Cls = mod[className];\n if (typeof Cls !== \"function\") {\n results.push({\n severity: \"fail\",\n name: \"smoke_test\",\n message: `Bot class ${className} not exported from entry point — add \\`export { ${className} }\\``,\n });\n return results;\n }\n\n let bot: { decide: (state: GameState) => unknown };\n try {\n bot = new (Cls as { new (): { decide: (state: GameState) => unknown } })();\n } catch (err) {\n results.push({\n severity: \"fail\",\n name: \"smoke_test\",\n message: `${className}() constructor threw: ${(err as Error).message}`,\n });\n return results;\n }\n\n const mockState = mockGameState();\n const start = performance.now();\n let action: unknown;\n try {\n action = bot.decide(mockState);\n } catch (err) {\n results.push({\n severity: \"fail\",\n name: \"smoke_test\",\n message: `${className}.decide() threw: ${(err as Error).message}`,\n });\n return results;\n }\n const elapsedMs = performance.now() - start;\n\n if (!isAction(action)) {\n results.push({\n severity: \"fail\",\n name: \"smoke_test\",\n message: `${className}.decide() returned ${describe(action)} — expected an Action`,\n });\n return results;\n }\n\n results.push({\n severity: \"pass\",\n name: \"smoke_test\",\n message: `decide() returned ${(action as { action: string }).action} successfully`,\n });\n\n // Timeout sanity check.\n if (elapsedMs > PLATFORM_TIMEOUT_MS) {\n results.push({\n severity: \"fail\",\n name: \"timeout\",\n message: `decide() took ${elapsedMs.toFixed(1)} ms — exceeds platform default ${PLATFORM_TIMEOUT_MS} ms`,\n });\n } else if (elapsedMs > timeoutWarnMs) {\n results.push({\n severity: \"warn\",\n name: \"timeout\",\n message: `decide() took ${elapsedMs.toFixed(1)} ms — over the warn threshold of ${timeoutWarnMs} ms`,\n });\n } else {\n results.push({\n severity: \"pass\",\n name: \"timeout\",\n message: `decide() completed in ${elapsedMs.toFixed(1)} ms`,\n });\n }\n\n if (checkConnectivity) {\n // Reuse the bot instance — same shape the user's production code does.\n const conformance = await runConformanceChecks(bot as unknown as Bot, {\n timeoutMs: conformanceTimeoutMs,\n });\n results.push(...conformance);\n }\n\n return results;\n}\n\nfunction mockGameState(): GameState {\n return {\n handNumber: 1,\n phase: \"preflop\",\n holeCards: [\n { rank: \"A\", suit: \"h\" },\n { rank: \"K\", suit: \"d\" },\n ],\n board: [],\n pot: 150,\n yourStack: 9900,\n opponentStacks: [9850],\n yourSeat: 0,\n dealerSeat: 0,\n toCall: 50,\n minRaise: 200,\n maxRaise: 9900,\n validActions: [\"fold\", \"call\", \"raise\"],\n actionHistory: [],\n roundId: \"\",\n requestId: \"\",\n };\n}\n\nfunction stripComments(source: string): string {\n // Strip /* ... */ block comments and // ... line comments. Doesn't try\n // to be string-literal-aware — false positives there are harmless for\n // the regex check that comes after.\n return source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\").replace(/\\/\\/[^\\n]*/g, \"\");\n}\n\nfunction describe(v: unknown): string {\n if (v === null) return \"null\";\n if (v === undefined) return \"undefined\";\n return `${typeof v} ${JSON.stringify(v)}`;\n}\n\nasync function dirTotalBytes(dir: string): Promise<number> {\n let total = 0;\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n // Skip node_modules — it's not part of the upload context.\n if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\n total += await dirTotalBytes(full);\n } else if (entry.isFile()) {\n const s = await fs.stat(full);\n total += s.size;\n }\n }\n return total;\n}\n","/**\n * Protocol-conformance harness used by `chipzen-sdk validate --check-connectivity`.\n *\n * Mirrors the Python harness in `packages/python/src/chipzen/conformance.py`\n * — same scenario shape, same severity model, same canned protocol\n * exchange (handshake + one full hand + match_end). A clean run means\n * the upload pipeline will accept the bot on protocol grounds. It does\n * NOT mean the bot is good.\n *\n * Implementation: drives `_runSession` from `client.ts` against an\n * in-process mock WebSocket rather than spinning up a real server. The\n * `ws` transport is well-tested upstream; what we verify here is the\n * bot's own protocol handling, which is the only part the user's code\n * influences.\n */\n\nimport { _runSession, type SessionWebSocket, type AsyncMessageReader } from \"./client.js\";\nimport { type Bot } from \"./bot.js\";\n\nexport type Severity = \"pass\" | \"warn\" | \"fail\";\n\n/** Single conformance scenario result. Same shape as `ValidationResult`. */\nexport interface ConformanceCheck {\n severity: Severity;\n name: string;\n message: string;\n}\n\nconst MATCH_ID = \"m_conformance_test\";\nconst VALID_ACTION_KINDS = new Set([\"fold\", \"check\", \"call\", \"raise\", \"all_in\"]);\n\n// ---------------------------------------------------------------------------\n// Mock WebSocket — replays a scripted message sequence\n// ---------------------------------------------------------------------------\n\n/**\n * Scripted reader: serves a fixed list of messages then signals close\n * with `null`. Implements the same interface `_NodeWebSocketReader`\n * does so `_runSession` can drive it directly.\n */\nclass _ScriptedReader implements AsyncMessageReader {\n private index = 0;\n\n constructor(private readonly messages: string[]) {}\n\n async next(): Promise<string | null> {\n if (this.index >= this.messages.length) return null;\n return this.messages[this.index++] ?? null;\n }\n}\n\n/**\n * Capturing socket: records every payload the session loop sends so we\n * can verify the protocol envelopes after the scenario runs.\n */\nclass _CapturingSocket implements SessionWebSocket {\n readonly sent: string[] = [];\n send(data: string): void {\n this.sent.push(data);\n }\n close(): void {\n // no-op — the scripted reader controls the scenario lifetime.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Canned protocol messages — one full hand\n// ---------------------------------------------------------------------------\n\nfunction serverHello(): Record<string, unknown> {\n return {\n type: \"hello\",\n match_id: MATCH_ID,\n seq: 1,\n server_ts: \"2026-04-13T14:30:05.123Z\",\n supported_versions: [\"1.0\"],\n selected_version: \"1.0\",\n game_type: \"nlhe_6max\",\n capabilities: [],\n };\n}\n\nfunction matchStart(): Record<string, unknown> {\n return {\n type: \"match_start\",\n match_id: MATCH_ID,\n seq: 2,\n server_ts: \"2026-04-13T14:30:06.000Z\",\n seats: [\n { seat: 0, participant_id: \"p0\", display_name: \"You\", is_self: true },\n { seat: 1, participant_id: \"p1\", display_name: \"Opp\", is_self: false },\n ],\n game_config: {\n variant: \"nlhe\",\n starting_stack: 1000,\n small_blind: 5,\n big_blind: 10,\n ante: 0,\n total_hands: 0,\n },\n turn_timeout_ms: 5000,\n };\n}\n\nfunction roundStart(): Record<string, unknown> {\n return {\n type: \"round_start\",\n match_id: MATCH_ID,\n seq: 3,\n server_ts: \"2026-04-13T14:30:07.000Z\",\n round_id: \"r_1\",\n round_number: 1,\n state: {\n hand_number: 1,\n dealer_seat: 0,\n your_hole_cards: [\"Ah\", \"Kd\"],\n stacks: [995, 990],\n deck_commitment: \"\",\n },\n };\n}\n\nfunction turnRequest(): Record<string, unknown> {\n return {\n type: \"turn_request\",\n match_id: MATCH_ID,\n seq: 4,\n server_ts: \"2026-04-13T14:30:07.500Z\",\n seat: 0,\n request_id: \"req_1\",\n timeout_ms: 5000,\n valid_actions: [\"fold\", \"call\", \"raise\"],\n state: {\n hand_number: 1,\n phase: \"preflop\",\n board: [],\n your_hole_cards: [\"Ah\", \"Kd\"],\n pot: 15,\n your_stack: 995,\n opponent_stacks: [990],\n to_call: 5,\n min_raise: 20,\n max_raise: 995,\n action_history: [],\n },\n };\n}\n\nfunction turnResult(): Record<string, unknown> {\n return {\n type: \"turn_result\",\n match_id: MATCH_ID,\n seq: 5,\n server_ts: \"2026-04-13T14:30:08.000Z\",\n is_timeout: false,\n details: { seat: 0, action: \"call\", amount: 5 },\n };\n}\n\nfunction roundResult(): Record<string, unknown> {\n return {\n type: \"round_result\",\n match_id: MATCH_ID,\n seq: 6,\n server_ts: \"2026-04-13T14:30:12.000Z\",\n round_id: \"r_1\",\n round_number: 1,\n result: {\n hand_number: 1,\n winner_seats: [0],\n pot: 40,\n payouts: [{ seat: 0, amount: 40 }],\n showdown: [],\n action_history: [],\n stacks: [1020, 980],\n deck_commitment: \"\",\n deck_reveal: null,\n },\n };\n}\n\nfunction matchEnd(seq = 7): Record<string, unknown> {\n return {\n type: \"match_end\",\n match_id: MATCH_ID,\n seq,\n server_ts: \"2026-04-13T14:35:00.000Z\",\n reason: \"complete\",\n results: [\n { seat: 0, participant_id: \"p0\", rank: 1, score: 1020 },\n { seat: 1, participant_id: \"p1\", rank: 2, score: 980 },\n ],\n };\n}\n\nfunction turnRequestN(seq: number, requestId: string): Record<string, unknown> {\n return { ...turnRequest(), seq, request_id: requestId };\n}\n\nfunction turnResultN(seq: number): Record<string, unknown> {\n return { ...turnResult(), seq };\n}\n\nfunction phaseChange(seq: number, phase: string, board: string[]): Record<string, unknown> {\n return {\n type: \"phase_change\",\n match_id: MATCH_ID,\n seq,\n server_ts: \"2026-04-13T14:30:09.000Z\",\n state: { phase, board },\n };\n}\n\nfunction roundResultN(seq: number): Record<string, unknown> {\n return { ...roundResult(), seq };\n}\n\n/**\n * Server-side rejection of a previously-sent `turn_action`. Drives the\n * SDK's safe-fallback retry path. The SDK should respond with a\n * `turn_action` echoing this same `request_id` and a safe action\n * (`check` or `fold`) within `remaining_ms`.\n */\nfunction actionRejected(\n seq: number,\n requestId = \"req_1\",\n remainingMs = 4000,\n): Record<string, unknown> {\n return {\n type: \"action_rejected\",\n match_id: MATCH_ID,\n seq,\n server_ts: \"2026-04-13T14:30:08.000Z\",\n request_id: requestId,\n reason: \"invalid_action\",\n message: \"action not in valid_actions\",\n remaining_ms: remainingMs,\n valid_actions: [\"check\", \"fold\"],\n };\n}\n\nfunction fullMatchScript(): string[] {\n return [\n serverHello(),\n matchStart(),\n roundStart(),\n turnRequest(),\n turnResult(),\n roundResult(),\n matchEnd(),\n ].map((m) => JSON.stringify(m));\n}\n\n/**\n * Three turn_requests across preflop/flop/turn — exercises request_id\n * echo on every turn. The original full-match script only checks the\n * first action; a bug where the second or later action drops or\n * mangles the `request_id` would pass the basic harness silently.\n */\nfunction multiTurnScript(): string[] {\n return [\n serverHello(),\n matchStart(),\n roundStart(),\n turnRequestN(4, \"req_1\"),\n turnResultN(5),\n phaseChange(6, \"flop\", [\"2s\", \"7d\", \"Tc\"]),\n turnRequestN(7, \"req_2\"),\n turnResultN(8),\n phaseChange(9, \"turn\", [\"2s\", \"7d\", \"Tc\", \"Kh\"]),\n turnRequestN(10, \"req_3\"),\n turnResultN(11),\n roundResultN(12),\n matchEnd(13),\n ].map((m) => JSON.stringify(m));\n}\n\n/**\n * One `turn_request` followed by `action_rejected` — exercises the\n * SDK's safe-fallback retry. The full-match script never delivers an\n * `action_rejected`, so the SDK's retry path goes untested in\n * conformance even though it's a routine production code path.\n */\nfunction actionRejectedScript(): string[] {\n return [\n serverHello(),\n matchStart(),\n roundStart(),\n turnRequestN(4, \"req_1\"),\n actionRejected(5, \"req_1\"),\n turnResultN(6),\n roundResultN(7),\n matchEnd(8),\n ].map((m) => JSON.stringify(m));\n}\n\n/**\n * One `turn_request` followed by THREE consecutive `action_rejected`\n * messages. Catches a class of failure where a buggy SDK might enter\n * an infinite response loop or hang waiting for a non-rejection\n * message that never arrives. The SDK should be purely reactive: one\n * `turn_action` per incoming message, then exit cleanly on\n * `match_end`.\n */\nfunction retryStormScript(): string[] {\n return [\n serverHello(),\n matchStart(),\n roundStart(),\n turnRequestN(4, \"req_1\"),\n actionRejected(5, \"req_1\"),\n actionRejected(6, \"req_1\"),\n actionRejected(7, \"req_1\"),\n turnResultN(8),\n roundResultN(9),\n matchEnd(10),\n ].map((m) => JSON.stringify(m));\n}\n\n// ---------------------------------------------------------------------------\n// Scenario evaluation\n// ---------------------------------------------------------------------------\n\ninterface ClassifyResult {\n ok: boolean;\n message: string;\n}\n\n/**\n * Validate a single payload the bot sent. Returns ok=true with a\n * non-fatal note for messages that aren't `turn_action`; returns\n * ok=false with a diagnostic for anything malformed.\n *\n * @param expectedRequestId The request_id the server sent for the turn\n * this action is responding to. The SDK MUST echo this value back so\n * the server can correlate, deduplicate, and route action_rejected\n * retries.\n */\nfunction classifyTurnAction(payload: string, expectedRequestId = \"req_1\"): ClassifyResult {\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(payload) as Record<string, unknown>;\n } catch (err) {\n return { ok: false, message: `sent payload was not valid JSON: ${(err as Error).message}` };\n }\n if (msg.type !== \"turn_action\") {\n return { ok: true, message: `non-action message (${JSON.stringify(msg.type)}) — ignored` };\n }\n if (msg.request_id !== expectedRequestId) {\n return {\n ok: false,\n message:\n `turn_action request_id ${JSON.stringify(msg.request_id)} did not echo the ` +\n `server's ${JSON.stringify(expectedRequestId)} — the server uses request_id for ` +\n `correlation, idempotency, and action_rejected retries`,\n };\n }\n // The wire shape is `{type, action, params}` (see Action.toWire). Older\n // bots may also nest action inside params, so check both spots.\n const params = (msg.params as Record<string, unknown> | undefined) ?? {};\n const action = (msg.action as string | undefined) ?? (params.action as string | undefined);\n if (!action || !VALID_ACTION_KINDS.has(action)) {\n return {\n ok: false,\n message: `turn_action action ${JSON.stringify(action)} is not in the legal set`,\n };\n }\n return { ok: true, message: `sent turn_action: action=${JSON.stringify(action)}` };\n}\n\n/** Filter the captured-send buffer down to parsed `turn_action` payloads. */\nfunction extractTurnActions(sent: string[]): Array<Record<string, unknown>> {\n const out: Array<Record<string, unknown>> = [];\n for (const payload of sent) {\n try {\n const parsed = JSON.parse(payload) as Record<string, unknown>;\n if (parsed.type === \"turn_action\") out.push(parsed);\n } catch {\n // ignore — non-JSON sends shouldn't happen in practice\n }\n }\n return out;\n}\n\ninterface DriveResult {\n socket: _CapturingSocket;\n error: Error | null;\n}\n\n/**\n * Drive `_runSession` against `script` with a per-scenario timeout.\n * Returns the captured socket plus any error caught (timeout or\n * runtime exception). Callers inspect `socket.sent` for what the bot\n * emitted.\n *\n * Note on hung bots: `setTimeout` only fires when the event loop is\n * idle. A bot whose `decide()` busy-loops or calls a synchronous\n * blocking primitive (e.g. `Atomics.wait` on a sync int) starves the\n * event loop and prevents the timeout from firing. The Python SDK has\n * a daemon-thread watchdog for this; the JavaScript SDK does not yet\n * because the equivalent (a Worker thread) is heavier-weight and\n * deferred to a follow-up. Bots that block the loop will hang the\n * harness until process termination.\n */\nasync function driveSession(\n bot: Bot,\n script: string[],\n timeoutMs: number,\n): Promise<DriveResult> {\n const reader = new _ScriptedReader(script);\n const socket = new _CapturingSocket();\n try {\n await withTimeout(\n _runSession(\n socket,\n bot,\n {\n matchId: MATCH_ID,\n token: \"conformance\",\n ticket: null,\n clientName: \"chipzen-sdk-conformance\",\n clientVersion: \"0.0.0\",\n },\n reader,\n ),\n timeoutMs,\n );\n return { socket, error: null };\n } catch (err) {\n return { socket, error: err as Error };\n }\n}\n\nasync function withTimeout<T>(p: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error(`timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n });\n try {\n return await Promise.race([p, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nasync function runFullMatchScenario(\n bot: Bot,\n timeoutMs: number,\n): Promise<ConformanceCheck> {\n const name = \"connectivity_full_match\";\n const { socket, error } = await driveSession(bot, fullMatchScript(), timeoutMs);\n\n if (error) {\n if (error.message.startsWith(\"timed out\")) {\n return {\n severity: \"fail\",\n name,\n message:\n `bot did not complete the canned full-match exchange within ${timeoutMs}ms — ` +\n `either decide() is too slow or the bot is hung waiting on something`,\n };\n }\n return {\n severity: \"fail\",\n name,\n message: `bot raised ${error.constructor.name} during the canned exchange: ${error.message}`,\n };\n }\n\n if (socket.sent.length === 0) {\n return {\n severity: \"fail\",\n name,\n message:\n \"bot did not send any messages during the canned exchange — at minimum the \" +\n \"client should have sent authenticate / hello / turn_action\",\n };\n }\n\n const turnActions = extractTurnActions(socket.sent);\n if (turnActions.length === 0) {\n return {\n severity: \"fail\",\n name,\n message:\n \"bot completed the exchange but never sent a turn_action — decide() may have \" +\n \"returned an unexpected value or the SDK's runner hit a fallback path\",\n };\n }\n\n const verdict = classifyTurnAction(JSON.stringify(turnActions[0]));\n if (!verdict.ok) {\n return { severity: \"fail\", name, message: verdict.message };\n }\n return {\n severity: \"pass\",\n name,\n message: `completed handshake + 1 hand + match_end; ${verdict.message}`,\n };\n}\n\n/**\n * Drive three turn_requests and verify request_id is echoed correctly\n * on each. The full-match scenario only checks the first action; a\n * bug where the second-or-later action drops or rewrites the\n * `request_id` would slip through.\n */\nasync function runMultiTurnScenario(\n bot: Bot,\n timeoutMs: number,\n): Promise<ConformanceCheck> {\n const name = \"multi_turn_request_id_echo\";\n const { socket, error } = await driveSession(bot, multiTurnScript(), timeoutMs);\n\n if (error) {\n if (error.message.startsWith(\"timed out\")) {\n return {\n severity: \"fail\",\n name,\n message: `bot did not complete the multi-turn exchange within ${timeoutMs}ms`,\n };\n }\n return {\n severity: \"fail\",\n name,\n message: `bot raised ${error.constructor.name} during the multi-turn exchange: ${error.message}`,\n };\n }\n\n const turnActions = extractTurnActions(socket.sent);\n const expectedIds = [\"req_1\", \"req_2\", \"req_3\"];\n\n if (turnActions.length < expectedIds.length) {\n return {\n severity: \"fail\",\n name,\n message:\n `expected ${expectedIds.length} turn_actions across preflop/flop/turn, ` +\n `saw only ${turnActions.length} — bot stopped responding partway through the hand`,\n };\n }\n\n for (let i = 0; i < expectedIds.length; i++) {\n const expectedId = expectedIds[i]!;\n const verdict = classifyTurnAction(JSON.stringify(turnActions[i]), expectedId);\n if (!verdict.ok) {\n return {\n severity: \"fail\",\n name,\n message: `turn ${i + 1} of 3 failed: ${verdict.message}`,\n };\n }\n }\n\n return {\n severity: \"pass\",\n name,\n message:\n `all ${expectedIds.length} turn_actions echoed request_id correctly across ` +\n `preflop/flop/turn`,\n };\n}\n\n/**\n * Drive a turn_request followed by action_rejected and verify the SDK\n * retries safely. On rejection the SDK should send a second\n * `turn_action` echoing the same `request_id` and using a safe action\n * (`check` or `fold`).\n */\nasync function runActionRejectedScenario(\n bot: Bot,\n timeoutMs: number,\n): Promise<ConformanceCheck> {\n const name = \"action_rejected_recovery\";\n const { socket, error } = await driveSession(bot, actionRejectedScript(), timeoutMs);\n\n if (error) {\n if (error.message.startsWith(\"timed out\")) {\n return {\n severity: \"fail\",\n name,\n message:\n `bot did not complete the action_rejected scenario within ${timeoutMs}ms — ` +\n `the SDK's safe-fallback retry path may be hung`,\n };\n }\n return {\n severity: \"fail\",\n name,\n message: `bot raised ${error.constructor.name} during action_rejected handling: ${error.message}`,\n };\n }\n\n const turnActions = extractTurnActions(socket.sent);\n if (turnActions.length < 2) {\n return {\n severity: \"fail\",\n name,\n message:\n `expected 2 turn_actions (initial + safe-fallback retry), saw ${turnActions.length}; ` +\n `the SDK did not respond to the action_rejected message`,\n };\n }\n\n const retry = turnActions[1]!;\n if (retry.request_id !== \"req_1\") {\n return {\n severity: \"fail\",\n name,\n message:\n `safe-fallback retry used request_id ${JSON.stringify(retry.request_id)} instead of ` +\n `the original \"req_1\" — server-side correlation will fail`,\n };\n }\n\n const retryParams = (retry.params as Record<string, unknown> | undefined) ?? {};\n const retryAction =\n (retry.action as string | undefined) ?? (retryParams.action as string | undefined);\n if (retryAction !== \"check\" && retryAction !== \"fold\") {\n return {\n severity: \"fail\",\n name,\n message:\n `safe-fallback retry sent action ${JSON.stringify(retryAction)}; ` +\n `expected \"check\" or \"fold\" (the only universally-safe actions when valid_actions is unknown)`,\n };\n }\n\n return {\n severity: \"pass\",\n name,\n message:\n `action_rejected handled cleanly: original action sent, retry sent ${JSON.stringify(retryAction)} ` +\n `with original request_id`,\n };\n}\n\n/**\n * Drive a turn_request followed by THREE action_rejected messages\n * back-to-back. Catches a class of failure where a buggy SDK might\n * hang after the first rejection or enter an infinite send loop. The\n * SDK should respond reactively (one safe-fallback per rejection) and\n * exit cleanly when match_end arrives.\n */\nasync function runRetryStormScenario(\n bot: Bot,\n timeoutMs: number,\n): Promise<ConformanceCheck> {\n const name = \"retry_storm_bounded\";\n const { socket, error } = await driveSession(bot, retryStormScript(), timeoutMs);\n\n if (error) {\n if (error.message.startsWith(\"timed out\")) {\n return {\n severity: \"fail\",\n name,\n message:\n `bot did not complete the retry-storm scenario within ${timeoutMs}ms — ` +\n `the SDK may be stuck in a retry loop`,\n };\n }\n return {\n severity: \"fail\",\n name,\n message: `bot raised ${error.constructor.name} during retry-storm handling: ${error.message}`,\n };\n }\n\n const turnActions = extractTurnActions(socket.sent);\n // Expected: 1 initial + 3 retries = 4 turn_actions total. The SDK is\n // reactive: each action_rejected provokes exactly one retry.\n const expectedCount = 4;\n if (turnActions.length !== expectedCount) {\n return {\n severity: turnActions.length < expectedCount ? \"fail\" : \"warn\",\n name,\n message:\n `expected ${expectedCount} turn_actions (1 initial + 3 retries) under retry storm, ` +\n `saw ${turnActions.length} — the SDK's retry behavior may be unbounded or may have ` +\n `stopped responding`,\n };\n }\n\n return {\n severity: \"pass\",\n name,\n message:\n `SDK responded to all 3 action_rejected messages with safe-fallback retries ` +\n `(${expectedCount} turn_actions total) and exited cleanly on match_end`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public entry point\n// ---------------------------------------------------------------------------\n\nexport interface RunConformanceOptions {\n /** Per-scenario timeout in milliseconds. Default 10s. */\n timeoutMs?: number;\n}\n\ntype Scenario = (bot: Bot, timeoutMs: number) => Promise<ConformanceCheck>;\n\nexport const SCENARIOS: ReadonlyArray<{ name: string; fn: Scenario }> = [\n { name: \"connectivity_full_match\", fn: runFullMatchScenario },\n { name: \"multi_turn_request_id_echo\", fn: runMultiTurnScenario },\n { name: \"action_rejected_recovery\", fn: runActionRejectedScenario },\n { name: \"retry_storm_bounded\", fn: runRetryStormScenario },\n];\n\n/**\n * Run every conformance scenario against `bot` and return per-check\n * results. The same bot instance is reused across scenarios — matches\n * the user's production usage shape.\n *\n * Note: the JavaScript harness does not currently include a hard\n * wall-clock watchdog. A bot whose `decide()` blocks the event loop\n * (busy-loop, sync `Atomics.wait`) will hang the harness because\n * `setTimeout` cannot fire while the loop is starved. The Python SDK\n * uses a daemon thread for this; the JS equivalent is a Worker and is\n * deferred to a follow-up.\n */\nexport async function runConformanceChecks(\n bot: Bot,\n options: RunConformanceOptions = {},\n): Promise<ConformanceCheck[]> {\n const timeoutMs = options.timeoutMs ?? 10_000;\n const results: ConformanceCheck[] = [];\n for (const { fn } of SCENARIOS) {\n results.push(await fn(bot, timeoutMs));\n }\n return results;\n}\n","/**\n * Bin shim for the `chipzen-sdk` CLI.\n *\n * Lives separately from cli.ts so the shebang the tsup banner adds at\n * build time doesn't end up in the library entry point that other\n * packages might import.\n */\n\nimport { main } from \"./cli.js\";\n\nmain().catch((err) => {\n console.error(err instanceof Error ? err.stack ?? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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","names":["path","path","WebSocket","WebSocket","path","token","fs","path","fs","path","VALID_ACTION_KINDS","fs","path","path"]}
|