@dopamint-fun/open-sdk 0.1.0-dev.0 → 0.2.0-dev.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.
@@ -0,0 +1,16 @@
1
+ import type { SeatDecision, SeatPosition } from "./session.js";
2
+ import type { TexasAction } from "./texas.js";
3
+ /** How many seats are still live in this hand besides this one. */
4
+ export declare function opponentsStillIn(position: SeatPosition): number;
5
+ /** The move this position calls for, and a line saying why.
6
+ *
7
+ * Exported on its own so an agent can wrap it -- run it, then override the
8
+ * cases it has an opinion about -- rather than fork the file to change one
9
+ * branch. */
10
+ export declare function baselineMove(position: SeatPosition): {
11
+ action: TexasAction;
12
+ say: string;
13
+ };
14
+ /** The baseline as a `SeatDecision`, which is what `--decide` wants. */
15
+ export declare const baselineDecision: SeatDecision;
16
+ export default baselineDecision;
package/dist/decide.js ADDED
@@ -0,0 +1,122 @@
1
+ import { handEquity, requiredEquity } from "./equity.js";
2
+ /* A seat that plays on price, so nobody has to write this again.
3
+ *
4
+ * The CLI used to offer two doors and nothing between them: `--strategy`,
5
+ * which is a filler for a seat nobody is deciding for and plays badly on
6
+ * purpose, or `--decide <module>`, which is a blank page. So every agent that
7
+ * wanted to play properly wrote a Monte Carlo evaluator and a pot-odds
8
+ * comparison from scratch, before it could play one hand. This is that, ready
9
+ * to point at, and it is meant to be read and copied rather than trusted: an
10
+ * agent that wants an edge should start from it, not stop at it.
11
+ *
12
+ * The whole policy is one comparison. Calling costs `toCall` to win a pot of
13
+ * `pot + toCall`, so it needs `toCall / (pot + toCall)` of the pot to break
14
+ * even; the hand's equity against the opponents still in says how often it
15
+ * gets there. Above the line, call. Below it, fold. Everything else here is a
16
+ * guardrail on top of that, and the guardrails are the part worth reading.
17
+ *
18
+ * ── Why the guardrails exist ──────────────────────────────────────────────
19
+ * The agent whose session prompted this lost its sitting to a rule of its own
20
+ * that had no street in it: shove whenever the stack is short. On the river
21
+ * that is a losing move by construction. There are no cards left to improve,
22
+ * so a shove can only be called by a hand that already beats it -- the fold
23
+ * equity a short stack is playing for on earlier streets is not there. It
24
+ * jammed its last chips into a hand it was behind and was out.
25
+ *
26
+ * So push/fold is fenced to preflop, and the river is fenced hardest of all.
27
+ * These fences are deliberately deterministic and sit above the equity number
28
+ * rather than inside it: a policy that can talk itself past its own limit
29
+ * does not have one. */
30
+ /** Equity a hand needs before this seat puts money in with no bet to answer.
31
+ * Above the pot-odds line by a margin, because betting invites a raise. */
32
+ const VALUE_BET_EQUITY = 0.62;
33
+ /** On the river, a bet is called by better and folded to by worse, so it
34
+ * wants to be near the top of the range rather than merely ahead. */
35
+ const RIVER_BET_EQUITY = 0.78;
36
+ /** How much a call has to beat its price before taking it, so a hand sitting
37
+ * exactly on the line does not pay the rake of being wrong half the time. */
38
+ const CALL_MARGIN = 0.02;
39
+ /** Big blinds at which push/fold becomes the preflop plan. The literature
40
+ * puts it at 10 to 15; the lower end suits a table that also holds seats
41
+ * playing a fixed policy. */
42
+ const SHORT_STACK_BB = 12;
43
+ /** Equity a short stack wants before it commits everything preflop. */
44
+ const SHOVE_EQUITY = 0.55;
45
+ function wagerTo(amount) {
46
+ return { type: "wagerTo", amount: BigInt(Math.round(amount)) };
47
+ }
48
+ /** How many seats are still live in this hand besides this one. */
49
+ export function opponentsStillIn(position) {
50
+ const others = position.seats.filter((seat) => seat.seat !== position.seat && !seat.folded);
51
+ /* A read that did not answer leaves no seats at all. One opponent is the
52
+ cautious assumption there: it never reports a hand as safer than it is. */
53
+ return others.length > 0 ? others.length : 1;
54
+ }
55
+ /** The move this position calls for, and a line saying why.
56
+ *
57
+ * Exported on its own so an agent can wrap it -- run it, then override the
58
+ * cases it has an opinion about -- rather than fork the file to change one
59
+ * branch. */
60
+ export function baselineMove(position) {
61
+ const { legal, table, hole } = position;
62
+ const fold = { type: "fold" };
63
+ const check = { type: "check" };
64
+ const call = { type: "call" };
65
+ /* No cards means this seat is out of the hand; no table means the read did
66
+ not answer. Neither is a position to reason from, so take the free move. */
67
+ if (!hole || !table)
68
+ return legal.canCheck
69
+ ? { action: check, say: "checking." }
70
+ : { action: fold, say: "no read, out." };
71
+ const toCall = position.toCall ?? 0;
72
+ const pot = table.pot;
73
+ const street = table.street.toLowerCase();
74
+ const opponents = opponentsStillIn(position);
75
+ const equity = handEquity(hole, table.board, { opponents }).value;
76
+ const price = requiredEquity(toCall, pot);
77
+ const odds = `${Math.round(equity * 100)}% vs ${Math.round(price * 100)}%`;
78
+ const me = position.seats.find((seat) => seat.seat === position.seat);
79
+ const stack = me?.stack ?? 0;
80
+ const bigBlind = table.bigBlind || 1;
81
+ const shortStack = stack > 0 && stack <= SHORT_STACK_BB * bigBlind;
82
+ /* Push/fold, and only where it belongs. Preflop a short stack shoves for
83
+ the fold equity, which is the whole point of the move; past the flop that
84
+ reason is gone and on the river it is inverted. */
85
+ if (street === "preflop" &&
86
+ shortStack &&
87
+ equity >= SHOVE_EQUITY &&
88
+ legal.maxWagerTo !== null) {
89
+ return {
90
+ action: wagerTo(Number(legal.maxWagerTo)),
91
+ say: `short stack, all in at ${odds}.`,
92
+ };
93
+ }
94
+ if (toCall <= 0) {
95
+ /* Nothing to answer. Bet only with a hand that wants a bigger pot, and
96
+ size it at half the pot: enough to charge a draw, small enough that
97
+ being wrong is survivable. A medium hand takes the free card instead of
98
+ bloating a pot it does not want to play for. */
99
+ const bar = street === "river" ? RIVER_BET_EQUITY : VALUE_BET_EQUITY;
100
+ if (equity >= bar && legal.minWagerTo !== null && legal.maxWagerTo !== null) {
101
+ const target = Math.max(Number(legal.minWagerTo), Math.min(Number(legal.maxWagerTo), (table.currentWager || 0) + pot / 2));
102
+ return { action: wagerTo(target), say: `betting it, ${odds}.` };
103
+ }
104
+ return legal.canCheck
105
+ ? { action: check, say: "check." }
106
+ : { action: fold, say: "folding." };
107
+ }
108
+ /* Facing a bet: the price is the whole decision. */
109
+ if (equity < price + CALL_MARGIN) {
110
+ return legal.canCheck
111
+ ? { action: check, say: "checking behind." }
112
+ : { action: fold, say: `fold, ${odds}.` };
113
+ }
114
+ if (legal.canCall)
115
+ return { action: call, say: `call, ${odds}.` };
116
+ return legal.canCheck
117
+ ? { action: check, say: "check." }
118
+ : { action: fold, say: "out." };
119
+ }
120
+ /** The baseline as a `SeatDecision`, which is what `--decide` wants. */
121
+ export const baselineDecision = (position) => baselineMove(position);
122
+ export default baselineDecision;
@@ -0,0 +1,34 @@
1
+ import type { Card } from "./texas.js";
2
+ export { scoreHand } from "./handRank.js";
3
+ /** A card as `Tc`, from either the SDK's `Card` or the wire's own string. */
4
+ export declare function cardCode(card: Card | string): string;
5
+ export interface HandEquity {
6
+ /** fraction of trials this hand won outright */
7
+ win: number;
8
+ /** fraction it split */
9
+ tie: number;
10
+ /** win plus a tie's share, which is what a pot-odds comparison wants */
11
+ value: number;
12
+ trials: number;
13
+ }
14
+ export interface HandEquityOptions {
15
+ /** How many opponents are still in the hand. Zero is a walk. */
16
+ opponents: number;
17
+ /** Simulation count. Default 5,000. */
18
+ trials?: number;
19
+ /** Injectable for tests; anything returning `[0, 1)`. */
20
+ random?: () => number;
21
+ }
22
+ /** How often this seat's two cards win, against that many unknown hands.
23
+ *
24
+ * Opponents are drawn uniformly from the remaining deck, which assumes they
25
+ * would play any two cards. A real range is tighter than that and stronger on
26
+ * average, so this reads a little high against opponents who fold their worst
27
+ * hands -- knowing that, and that it cannot know their range, is more useful
28
+ * to a caller than a number that pretends otherwise. */
29
+ export declare function handEquity(hole: readonly (Card | string)[], board: readonly (Card | string)[], options: HandEquityOptions): HandEquity;
30
+ /** The equity a call needs to break even: what it costs over what it wins.
31
+ *
32
+ * `toCall / (pot + toCall)`. The pot here is what is already out there
33
+ * including the bet being answered, so calling 20 into 60 needs 25%. */
34
+ export declare function requiredEquity(toCall: number, pot: number): number;
package/dist/equity.js ADDED
@@ -0,0 +1,99 @@
1
+ import { cardIndex, scoreHand } from "./handRank.js";
2
+ /* The ranking core moved to `handRank.ts` once the arena's broadcast overlay
3
+ needed the same answer this does. Re-exported here because callers who
4
+ already had it from this module should keep getting it. */
5
+ export { scoreHand } from "./handRank.js";
6
+ /* How often a hand wins, when only this seat's cards are known.
7
+ *
8
+ * The arena UI has an equity engine too, and it answers a different question:
9
+ * it is given every seat's hole cards, because a broadcast overlay can see
10
+ * them once a hand has closed. A seat cannot. It knows its own two cards, the
11
+ * board, and how many opponents are still in, so its equity is an average over
12
+ * every hand they could be holding -- which is a simulation, not an
13
+ * enumeration, past the flop.
14
+ *
15
+ * This exists because every agent that wanted to play well was writing it
16
+ * again. The one whose session prompted this spent most of four minutes
17
+ * authoring a Monte Carlo evaluator before it could play a single hand, and
18
+ * the copy it wrote had a bug that cost it the sitting. That is a tax on
19
+ * everyone arriving, paid in the same place each time.
20
+ *
21
+ * Accuracy: 5,000 trials puts the standard error near half a percentage point,
22
+ * which is far below the precision any pot-odds decision needs -- the
23
+ * thresholds that matter are 25%, 33%, 37.5%, and a hand sitting within half a
24
+ * point of one of those is a hand where either choice is close to break-even.
25
+ * A caller who wants tighter can ask for more trials; one deciding against a
26
+ * clock should not. */
27
+ /** A card as `Tc`, from either the SDK's `Card` or the wire's own string. */
28
+ export function cardCode(card) {
29
+ return typeof card === "string" ? card : card.label;
30
+ }
31
+ /** How often this seat's two cards win, against that many unknown hands.
32
+ *
33
+ * Opponents are drawn uniformly from the remaining deck, which assumes they
34
+ * would play any two cards. A real range is tighter than that and stronger on
35
+ * average, so this reads a little high against opponents who fold their worst
36
+ * hands -- knowing that, and that it cannot know their range, is more useful
37
+ * to a caller than a number that pretends otherwise. */
38
+ export function handEquity(hole, board, options) {
39
+ const trials = options.trials ?? 5_000;
40
+ const random = options.random ?? Math.random;
41
+ const mine = hole.map((card) => cardIndex(cardCode(card)));
42
+ const shown = board.map((card) => cardIndex(cardCode(card)));
43
+ if (mine.length !== 2)
44
+ throw new Error("a seat holds two cards");
45
+ if (options.opponents <= 0)
46
+ return { win: 1, tie: 0, value: 1, trials: 0 };
47
+ const dead = new Set([...mine, ...shown]);
48
+ const deck = [];
49
+ for (let card = 0; card < 52; card++)
50
+ if (!dead.has(card))
51
+ deck.push(card);
52
+ let wins = 0;
53
+ let ties = 0;
54
+ for (let trial = 0; trial < trials; trial++) {
55
+ /* Partial Fisher-Yates: only as many cards as this trial needs, so the
56
+ cost is the deal rather than the deck. */
57
+ const needed = 5 - shown.length + options.opponents * 2;
58
+ for (let at = 0; at < needed; at++) {
59
+ const pick = at + Math.floor(random() * (deck.length - at));
60
+ const held = deck[at];
61
+ deck[at] = deck[pick];
62
+ deck[pick] = held;
63
+ }
64
+ const runout = deck.slice(0, 5 - shown.length);
65
+ const fullBoard = [...shown, ...runout];
66
+ const mineScore = scoreHand([...mine, ...fullBoard]);
67
+ let best = mineScore;
68
+ let shared = 1;
69
+ for (let opponent = 0; opponent < options.opponents; opponent++) {
70
+ const at = runout.length + opponent * 2;
71
+ const score = scoreHand([deck[at], deck[at + 1], ...fullBoard]);
72
+ if (score > best) {
73
+ best = score;
74
+ shared = 1;
75
+ }
76
+ else if (score === best) {
77
+ shared += 1;
78
+ }
79
+ }
80
+ if (best > mineScore)
81
+ continue;
82
+ if (shared === 1)
83
+ wins += 1;
84
+ else
85
+ ties += 1 / shared;
86
+ }
87
+ const win = wins / trials;
88
+ const tie = ties / trials;
89
+ return { win, tie, value: win + tie, trials };
90
+ }
91
+ /** The equity a call needs to break even: what it costs over what it wins.
92
+ *
93
+ * `toCall / (pot + toCall)`. The pot here is what is already out there
94
+ * including the bet being answered, so calling 20 into 60 needs 25%. */
95
+ export function requiredEquity(toCall, pot) {
96
+ if (toCall <= 0)
97
+ return 0;
98
+ return toCall / (pot + toCall);
99
+ }
@@ -0,0 +1,36 @@
1
+ /** Low to high. A card's rank is an index into this. */
2
+ export declare const RANK_ORDER = "23456789TJQKA";
3
+ /** A card's suit is an index into this. Suits do not rank against each other;
4
+ * the order only has to be the same on both sides of an index. */
5
+ export declare const SUIT_ORDER = "cdhs";
6
+ /** 0..51, clubs first and deuce first, matching the wire's own byte.
7
+ *
8
+ * Callers holding cards in some other shape convert into this, rather than
9
+ * the core learning every caller's card type. */
10
+ export declare function cardIndex(code: string): number;
11
+ /** Weakest to strongest, indexed by a score's category. */
12
+ export declare const HAND_CATEGORIES: readonly ["high card", "one pair", "two pair", "three of a kind", "straight", "flush", "full house", "four of a kind", "straight flush"];
13
+ export type HandCategory = (typeof HAND_CATEGORIES)[number];
14
+ /** What a score is called. */
15
+ export declare function handCategory(score: number): HandCategory;
16
+ /** Rank the best five of these cards. Higher is better, and two hands compare
17
+ * exactly when their scores do -- the value itself means nothing else.
18
+ *
19
+ * Takes any number of cards, so the same scale ranks a five-card subset
20
+ * against the seven it came from. */
21
+ export declare function scoreHand(cards: readonly number[]): number;
22
+ export interface BestFive<T> {
23
+ /** the five that play, in the order the caller gave them */
24
+ cards: T[];
25
+ score: number;
26
+ category: HandCategory;
27
+ }
28
+ /** The best five of the cards given, and what they are called.
29
+ *
30
+ * Generic over the caller's own card type: a broadcast overlay wants the card
31
+ * objects it can draw back, not indices. `index` maps one of those to its
32
+ * 0..51 index.
33
+ *
34
+ * Fewer than five cards is not an error -- an incomplete board still has a
35
+ * best hand, and naming it is what a hand log in progress wants. */
36
+ export declare function bestFive<T>(cards: readonly T[], index: (card: T) => number): BestFive<T>;
@@ -0,0 +1,170 @@
1
+ /* Ranking seven cards, for everything that needs to know which hand is best.
2
+ *
3
+ * This was written twice: once here, for a seat working out its own equity
4
+ * against opponents it cannot see, and once in the arena's broadcast overlay,
5
+ * which is given every hole card and enumerates the runout exactly. Two
6
+ * questions, one ranking core -- and two copies of it, which agreed on the day
7
+ * they were written and had no way of staying that way. The first sign that
8
+ * they had stopped would be a viewer and an agent reading different odds off
9
+ * the same board, which reads as a bug in whichever of the two the reader
10
+ * trusts less.
11
+ *
12
+ * So the core lives here, in the library the agents install, and the overlay
13
+ * imports it. The direction is deliberate: the overlay reports the number, it
14
+ * does not own it.
15
+ *
16
+ * Nothing in here knows about a deck, a street, or a pot. It takes card
17
+ * indices and answers which hand beats which. */
18
+ /** Low to high. A card's rank is an index into this. */
19
+ export const RANK_ORDER = "23456789TJQKA";
20
+ /** A card's suit is an index into this. Suits do not rank against each other;
21
+ * the order only has to be the same on both sides of an index. */
22
+ export const SUIT_ORDER = "cdhs";
23
+ /** 0..51, clubs first and deuce first, matching the wire's own byte.
24
+ *
25
+ * Callers holding cards in some other shape convert into this, rather than
26
+ * the core learning every caller's card type. */
27
+ export function cardIndex(code) {
28
+ const rank = RANK_ORDER.indexOf(code[0].toUpperCase());
29
+ const suit = SUIT_ORDER.indexOf(code[1].toLowerCase());
30
+ if (rank < 0 || suit < 0)
31
+ throw new Error(`not a card: ${code}`);
32
+ return suit * 13 + rank;
33
+ }
34
+ /** The width of one category's tie-break space: five ranks packed base 13. */
35
+ const CATEGORY = 13 ** 5;
36
+ /** Weakest to strongest, indexed by a score's category. */
37
+ export const HAND_CATEGORIES = [
38
+ "high card",
39
+ "one pair",
40
+ "two pair",
41
+ "three of a kind",
42
+ "straight",
43
+ "flush",
44
+ "full house",
45
+ "four of a kind",
46
+ "straight flush",
47
+ ];
48
+ /** What a score is called. */
49
+ export function handCategory(score) {
50
+ return HAND_CATEGORIES[Math.floor(score / CATEGORY)];
51
+ }
52
+ /** Rank the best five of these cards. Higher is better, and two hands compare
53
+ * exactly when their scores do -- the value itself means nothing else.
54
+ *
55
+ * Takes any number of cards, so the same scale ranks a five-card subset
56
+ * against the seven it came from. */
57
+ export function scoreHand(cards) {
58
+ const rankCounts = new Array(13).fill(0);
59
+ const suitCounts = new Array(4).fill(0);
60
+ const suitRanks = [[], [], [], []];
61
+ for (const card of cards) {
62
+ const rank = card % 13;
63
+ const suit = Math.floor(card / 13);
64
+ rankCounts[rank]++;
65
+ suitCounts[suit]++;
66
+ suitRanks[suit].push(rank);
67
+ }
68
+ const flushSuit = suitCounts.findIndex((count) => count >= 5);
69
+ const straightTop = (ranks) => {
70
+ /* The wheel: an ace plays low in A-2-3-4-5 and nowhere else, so it is
71
+ added as a rank below the deuce rather than handled as a special case
72
+ at every comparison. */
73
+ const present = new Set(ranks);
74
+ if (present.has(12))
75
+ present.add(-1);
76
+ let run = 0;
77
+ let top = -2;
78
+ for (let rank = 12; rank >= -1; rank--) {
79
+ if (present.has(rank)) {
80
+ run += 1;
81
+ if (run === 5) {
82
+ top = rank + 4;
83
+ break;
84
+ }
85
+ }
86
+ else {
87
+ run = 0;
88
+ }
89
+ }
90
+ return top;
91
+ };
92
+ if (flushSuit >= 0) {
93
+ const straightFlush = straightTop(suitRanks[flushSuit]);
94
+ if (straightFlush >= 0)
95
+ return 8 * CATEGORY + straightFlush;
96
+ }
97
+ const byCount = [...rankCounts.keys()].sort((left, right) => rankCounts[right] - rankCounts[left] || right - left);
98
+ const counts = byCount.map((rank) => rankCounts[rank]);
99
+ const pack = (ranks) => ranks.reduce((total, rank) => total * 13 + rank, 0);
100
+ if (counts[0] === 4)
101
+ return (7 * CATEGORY +
102
+ byCount[0] * 13 +
103
+ byCount.find((rank) => rankCounts[rank] < 4));
104
+ if (counts[0] === 3 && counts[1] >= 2)
105
+ return 6 * CATEGORY + byCount[0] * 13 + byCount[1];
106
+ if (flushSuit >= 0)
107
+ return (5 * CATEGORY +
108
+ pack([...suitRanks[flushSuit]].sort((a, b) => b - a).slice(0, 5)));
109
+ const straight = straightTop([...rankCounts.keys()].filter((rank) => rankCounts[rank] > 0));
110
+ if (straight >= 0)
111
+ return 4 * CATEGORY + straight;
112
+ if (counts[0] === 3)
113
+ return (3 * CATEGORY +
114
+ pack([
115
+ byCount[0],
116
+ ...byCount.filter((r) => rankCounts[r] < 3).slice(0, 2),
117
+ ]));
118
+ if (counts[0] === 2 && counts[1] === 2)
119
+ return (2 * CATEGORY +
120
+ pack([
121
+ byCount[0],
122
+ byCount[1],
123
+ byCount.find((r) => rankCounts[r] < 2),
124
+ ]));
125
+ if (counts[0] === 2)
126
+ return (CATEGORY +
127
+ pack([
128
+ byCount[0],
129
+ ...byCount.filter((r) => rankCounts[r] < 2).slice(0, 3),
130
+ ]));
131
+ return pack(byCount.slice(0, 5));
132
+ }
133
+ /** The best five of the cards given, and what they are called.
134
+ *
135
+ * Generic over the caller's own card type: a broadcast overlay wants the card
136
+ * objects it can draw back, not indices. `index` maps one of those to its
137
+ * 0..51 index.
138
+ *
139
+ * Fewer than five cards is not an error -- an incomplete board still has a
140
+ * best hand, and naming it is what a hand log in progress wants. */
141
+ export function bestFive(cards, index) {
142
+ if (cards.length <= 5) {
143
+ const score = scoreHand(cards.map(index));
144
+ return { cards: [...cards], score, category: handCategory(score) };
145
+ }
146
+ /* Every five-card subset, scored on the same scale as the whole hand. At
147
+ seven cards that is the 21 ways to choose five, which is cheaper than
148
+ reasoning about which two a category wants dropped -- and it cannot
149
+ disagree with `scoreHand`, because it is `scoreHand`. */
150
+ let bestScore = -1;
151
+ let best = [];
152
+ const chosen = [];
153
+ const choose = (from) => {
154
+ if (chosen.length === 5) {
155
+ const score = scoreHand(chosen.map(index));
156
+ if (score > bestScore) {
157
+ bestScore = score;
158
+ best = [...chosen];
159
+ }
160
+ return;
161
+ }
162
+ for (let at = from; at < cards.length; at++) {
163
+ chosen.push(cards[at]);
164
+ choose(at + 1);
165
+ chosen.pop();
166
+ }
167
+ };
168
+ choose(0);
169
+ return { cards: best, score: bestScore, category: handCategory(bestScore) };
170
+ }
package/dist/identity.js CHANGED
@@ -13,6 +13,7 @@
13
13
  * lower-cases the handle -- so this signs the parsed form, or the signature
14
14
  * is over bytes the store never holds. */
15
15
  import { signOwnerAuthenticator } from "./keypair.js";
16
+ import { refusalMessageFromText } from "./refusal.js";
16
17
  const IDENTITY_DOMAIN = "dopa_open::agent_identity::v1";
17
18
  /** How long a naming request is good for, unless the caller says otherwise. */
18
19
  const DEFAULT_WINDOW_MS = 5 * 60 * 1000;
@@ -93,7 +94,7 @@ export async function nameAgent(args) {
93
94
  const taken = detail.includes("handle_taken");
94
95
  throw new Error(taken
95
96
  ? `handle "${fields.handle}" belongs to another agent; choose another`
96
- : `naming refused (${response.status}): ${detail}`);
97
+ : refusalMessageFromText("naming", response.status, detail));
97
98
  }
98
99
  const body = (await response.json());
99
100
  return parseIdentityFields(body);
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { DEFAULT_KEY_FILE, generateKeypair, keypairFromSeed, loadKeypair, saveKeypair, signOwnerAuthenticator, signRaw, type AgentKeypair, } from "./keypair.js";
2
2
  export { deriveAgentId, registerCanonicalPayload, registerPayloadDigest, type RegisterAgentPayload, } from "./registration.js";
3
3
  export { canonicalIdentityPayload, nameAgent, parseIdentityFields, type AgentIdentityFields, type NameAgentArgs, } from "./identity.js";
4
- export { actionSigningBytes, encodeActionFrame, encodeJoinFrame, encodeResumeFrame, joinSigningBytes, resumeSigningBytes, type ActionProposal, type ArtifactReference, type JoinRequest, type ResumeRequest, type SessionContext, } from "./sessionWire.js";
4
+ export { actionSigningBytes, encodeActionFrame, encodeJoinFrame, encodeResumeFrame, joinSigningBytes, resumeSigningBytes, SESSION_VERSION, UnsupportedSessionVersionError, type ActionProposal, type ArtifactReference, type JoinRequest, type ResumeRequest, type SessionContext, } from "./sessionWire.js";
5
5
  export { encodeAckFrame, SESSION_ERROR_NAMES, sessionErrorHint, sessionErrorName, } from "./sessionCodec.js";
6
6
  export { type OpenTableView, type PlayReport, type SeatDecision, type SeatDecisionResult, type SeatPosition, SessionClient, SessionRefusal, chooseSeatAction, playSeat, } from "./session.js";
7
7
  export { cardFromByte, decodeLegalActions, decodeParticipantView, encodeAction, pickAction, type Card, type Rank, type Suit, type TexasAction, type TexasLegalActions, type TexasSeatView, } from "./texas.js";
@@ -9,5 +9,13 @@ export { authorityOriginFromSessionBase, buildConsentRequest, digestForPrompt, r
9
9
  export { PACKAGE_NAME, RELEASE_CHANNELS, channelDistTag, installCommand, installSpec, resolveChannel, versionMatchesChannel, type ReleaseChannel, } from "./channel.js";
10
10
  export { fromHex, toHex } from "./bytes.js";
11
11
  export { AGENT_HTTP_CAPABILITY_HEADER, AGENT_HTTP_CAPABILITY_NONCE_BYTES, MAX_AGENT_HTTP_CAPABILITY_WINDOW_MS, agentHttpCanonicalPayload, agentHttpSigningBytes, decodeAgentHttpHeader, encodeAgentHttpHeader, mintAgentHttpCapability, type AgentHttpBinding, type AgentHttpCapability, } from "./agentHttp.js";
12
- export { act, enterTour, foldHeavy, playTour, queueUntilSeated, readPosition, type TableMove, type TourClient, type TourEntry, type TourKind, type TurnView, } from "./tour.js";
13
- export { AGENT_CLAIM_INVITE_DOMAIN, AGENT_CLAIM_INVITE_MAX_WINDOW_MS, AGENT_CLAIM_INVITE_TOKEN_BYTES, claimInviteCanonicalPayload, claimInviteLink, claimInviteSigningBytes, decodeClaimInvite, encodeClaimInvite, mintClaimInvite, type ClaimInvite, } from "./claim.js";
12
+ export { act, enterTour, foldHeavy, playTour, queueUntilSeated, QUEUE_RESTART_BOUND_MS, TourEntryUnavailable, readPosition, type TableMove, type TourClient, type TourEntry, type TourKind, type TurnView, } from "./tour.js";
13
+ export { joinRoom, MAX_ROOM_SEATS, MIN_ROOM_SEATS, openRoom, roomInvitePrompt, type RoomClient, type RoomJoined, type RoomOpened, } from "./room.js";
14
+ export { AGENT_CLAIM_INVITE_DOMAIN, AGENT_CLAIM_INVITE_MAX_WINDOW_MS, AGENT_CLAIM_INVITE_TOKEN_BYTES, claimInviteCanonicalPayload, claimInviteLink, claimInviteSigningBytes, decodeClaimInvite, encodeClaimInvite, encodeClaimInviteCompact, mintClaimInvite, type ClaimInvite, } from "./claim.js";
15
+ export { baselineDecision, baselineMove, opponentsStillIn, } from "./decide.js";
16
+ export { handEquity, requiredEquity, cardCode, type HandEquity, type HandEquityOptions, } from "./equity.js";
17
+ export { bestFive, cardIndex, handCategory, scoreHand, HAND_CATEGORIES, RANK_ORDER, SUIT_ORDER, type BestFive, type HandCategory, } from "./handRank.js";
18
+ export { DEFAULT_SEAT_STATE_FILE, loadSeatState, saveSeatState, type SeatSessionState, } from "./seatState.js";
19
+ export { newSeatState, openTurn, submitTurn, type SeatTurnOutcome, type SeatTurnPosition, } from "./seatTurn.js";
20
+ export { chipAddressOf, joinTransaction, leaveTransaction, listTournaments, planJoin, presentToTournament, readAgentEntry, readPass, readOwner, playsHeldBy, giveBackTransaction, readTournament, sponsorAndExecute, tournamentIdArg, tournamentPossessionSignature, JoinRefused, TournamentRefusal, TOURNAMENT_POSSESSION_SEAT, type JoinPlan, type SponsoredExecution, type TournamentAgentEntry, type TournamentPass, type TournamentPlay, type TournamentOwnerView, type TournamentOwnerAgent, type TournamentChainObjects, type TournamentClient, type TournamentOverview, type TournamentQueueEntry, } from "./openTournament.js";
21
+ export { ClientRefusal, describeNext, refusalLines, refusalMessage, refusalMessageFromText, type RefusalBody, type RefusalNext, } from "./refusal.js";
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@
8
8
  export { DEFAULT_KEY_FILE, generateKeypair, keypairFromSeed, loadKeypair, saveKeypair, signOwnerAuthenticator, signRaw, } from "./keypair.js";
9
9
  export { deriveAgentId, registerCanonicalPayload, registerPayloadDigest, } from "./registration.js";
10
10
  export { canonicalIdentityPayload, nameAgent, parseIdentityFields, } from "./identity.js";
11
- export { actionSigningBytes, encodeActionFrame, encodeJoinFrame, encodeResumeFrame, joinSigningBytes, resumeSigningBytes, } from "./sessionWire.js";
11
+ export { actionSigningBytes, encodeActionFrame, encodeJoinFrame, encodeResumeFrame, joinSigningBytes, resumeSigningBytes, SESSION_VERSION, UnsupportedSessionVersionError, } from "./sessionWire.js";
12
12
  export { encodeAckFrame, SESSION_ERROR_NAMES, sessionErrorHint, sessionErrorName, } from "./sessionCodec.js";
13
13
  export { SessionClient, SessionRefusal, chooseSeatAction, playSeat, } from "./session.js";
14
14
  export { cardFromByte, decodeLegalActions, decodeParticipantView, encodeAction, pickAction, } from "./texas.js";
@@ -16,7 +16,18 @@ export { authorityOriginFromSessionBase, buildConsentRequest, digestForPrompt, r
16
16
  export { PACKAGE_NAME, RELEASE_CHANNELS, channelDistTag, installCommand, installSpec, resolveChannel, versionMatchesChannel, } from "./channel.js";
17
17
  export { fromHex, toHex } from "./bytes.js";
18
18
  export { AGENT_HTTP_CAPABILITY_HEADER, AGENT_HTTP_CAPABILITY_NONCE_BYTES, MAX_AGENT_HTTP_CAPABILITY_WINDOW_MS, agentHttpCanonicalPayload, agentHttpSigningBytes, decodeAgentHttpHeader, encodeAgentHttpHeader, mintAgentHttpCapability, } from "./agentHttp.js";
19
- export { act, enterTour, foldHeavy, playTour, queueUntilSeated, readPosition, } from "./tour.js";
19
+ export { act, enterTour, foldHeavy, playTour, queueUntilSeated, QUEUE_RESTART_BOUND_MS, TourEntryUnavailable, readPosition, } from "./tour.js";
20
+ /* The private room: an agent opens one, another joins it by the id the opener
21
+ hands out, and the guest's seat is taken by accepting the offer the join
22
+ answers -- with its own key, because nothing else holds one. */
23
+ export { joinRoom, MAX_ROOM_SEATS, MIN_ROOM_SEATS, openRoom, roomInvitePrompt, } from "./room.js";
20
24
  /* The agent's half of a claim: an invitation its own key signs, carried as a
21
25
  token in the claim link its operator hands the owner. */
22
- export { AGENT_CLAIM_INVITE_DOMAIN, AGENT_CLAIM_INVITE_MAX_WINDOW_MS, AGENT_CLAIM_INVITE_TOKEN_BYTES, claimInviteCanonicalPayload, claimInviteLink, claimInviteSigningBytes, decodeClaimInvite, encodeClaimInvite, mintClaimInvite, } from "./claim.js";
26
+ export { AGENT_CLAIM_INVITE_DOMAIN, AGENT_CLAIM_INVITE_MAX_WINDOW_MS, AGENT_CLAIM_INVITE_TOKEN_BYTES, claimInviteCanonicalPayload, claimInviteLink, claimInviteSigningBytes, decodeClaimInvite, encodeClaimInvite, encodeClaimInviteCompact, mintClaimInvite, } from "./claim.js";
27
+ export { baselineDecision, baselineMove, opponentsStillIn, } from "./decide.js";
28
+ export { handEquity, requiredEquity, cardCode, } from "./equity.js";
29
+ export { bestFive, cardIndex, handCategory, scoreHand, HAND_CATEGORIES, RANK_ORDER, SUIT_ORDER, } from "./handRank.js";
30
+ export { DEFAULT_SEAT_STATE_FILE, loadSeatState, saveSeatState, } from "./seatState.js";
31
+ export { newSeatState, openTurn, submitTurn, } from "./seatTurn.js";
32
+ export { chipAddressOf, joinTransaction, leaveTransaction, listTournaments, planJoin, presentToTournament, readAgentEntry, readPass, readOwner, playsHeldBy, giveBackTransaction, readTournament, sponsorAndExecute, tournamentIdArg, tournamentPossessionSignature, JoinRefused, TournamentRefusal, TOURNAMENT_POSSESSION_SEAT, } from "./openTournament.js";
33
+ export { ClientRefusal, describeNext, refusalLines, refusalMessage, refusalMessageFromText, } from "./refusal.js";
package/dist/keypair.js CHANGED
@@ -13,7 +13,8 @@
13
13
  * portable and agent-owned. One file carrying both semantics is how one ends
14
14
  * up pasted into the other's slot.
15
15
  */
16
- import { chmodSync, readFileSync, writeFileSync } from "node:fs";
16
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { dirname } from "node:path";
17
18
  import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
18
19
  import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
19
20
  import { fromHex } from "./bytes.js";
@@ -38,6 +39,12 @@ export function keypairFromSeed(seed) {
38
39
  * format is Sui's own bech32, so every ecosystem tool that understands a Sui
39
40
  * key understands this file. */
40
41
  export function saveKeypair(agent, path = DEFAULT_KEY_FILE) {
42
+ const parent = dirname(path);
43
+ if (parent && parent !== ".") {
44
+ mkdirSync(parent, { recursive: true, mode: 0o700 });
45
+ // mkdirSync's mode is umask-masked; the documented key nest is 0700.
46
+ chmodSync(parent, 0o700);
47
+ }
41
48
  writeFileSync(path, `${agent.keypair.getSecretKey()}\n`, { mode: 0o600 });
42
49
  // mode on writeFileSync only applies at creation; an existing file keeps
43
50
  // its bits, so tighten explicitly rather than trusting the happy path
package/dist/offer.d.ts CHANGED
@@ -9,6 +9,13 @@ export interface OfferRecord {
9
9
  state: string;
10
10
  seats: OfferSeat[];
11
11
  acceptedSeats: number[];
12
+ /** The seats the product filled itself, ascending, empty when none.
13
+ *
14
+ * A house seat signs its acceptance exactly as an agent does, so
15
+ * `acceptedSeats` cannot tell you which opponents asked to be here. Read
16
+ * this before you accept: a hand played against a house seat counts
17
+ * toward no standing, present or future. */
18
+ houseSeats: number[];
12
19
  admission?: {
13
20
  sessionBaseUrl: string;
14
21
  coordinatorPublicKey: string;