@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.
package/dist/session.d.ts CHANGED
@@ -18,6 +18,8 @@ export interface PlayReport {
18
18
  terminalCommitment?: string;
19
19
  executionId: string;
20
20
  sessionBaseUrl: string;
21
+ /** Whether this seat lost its last chip before the sitting ended. */
22
+ eliminated: boolean;
21
23
  }
22
24
  /** The lines a play door reports for one seat's sitting.
23
25
  *
@@ -26,6 +28,25 @@ export interface PlayReport {
26
28
  * document recommends - omitted the terminal values `consent` requires, so
27
29
  * the recommended path could not reach settlement. */
28
30
  export declare function playReportLines(report: PlayReport): string[];
31
+ /** How often a seat with nothing to decide reads the public table.
32
+ *
33
+ * A seat that is out of the hand, or out of the sitting, is sent views with no
34
+ * legal actions for as long as the others play, and the table used to be read
35
+ * only when there was a decision to make -- so a busted seat read nothing and
36
+ * printed nothing for the rest of the sitting. Slow, because the read is the
37
+ * same one the anonymous cap counts. */
38
+ export declare const IDLE_TABLE_READ_MS = 5000;
39
+ /** Watches the public table for this seat losing its last chip.
40
+ *
41
+ * Out means no chips and not all in: a seat all in with nothing behind is
42
+ * still in the hand, and only once the hand has settled against it does its
43
+ * stack read zero with no bet out. `observe` answers true exactly once, the
44
+ * first time it sees that, so the line it drives is printed once however many
45
+ * hands the others go on to play. */
46
+ export declare function eliminationWatch(seat: number): {
47
+ readonly eliminated: boolean;
48
+ observe(table: OpenTableView | null): boolean;
49
+ };
29
50
  export declare class SessionRefusal extends Error {
30
51
  readonly tag: number;
31
52
  readonly retryable: boolean;
@@ -35,6 +56,10 @@ export declare class SessionRefusal extends Error {
35
56
  retryable: boolean;
36
57
  }, detail?: string);
37
58
  }
59
+ /** How long a seat keeps asking for discovery from an origin that is not
60
+ * answering: long enough to outlast a deployment restarting behind it, short
61
+ * enough that a seat whose authority is truly gone still stops. */
62
+ export declare const DISCOVERY_RETRY_BOUND_MS = 90000;
38
63
  /** The public table, as the spectator snapshot shows it to everyone.
39
64
  *
40
65
  * Read once per turn beside the seat's own view, so a decision has the board,
@@ -104,6 +129,9 @@ export interface PlayArgs {
104
129
  number: number;
105
130
  stack: number | null;
106
131
  }) => void;
132
+ /** Called once, when this seat loses its last chip. The loop goes on to the
133
+ * terminal: the sitting still needs this seat's consent. */
134
+ onEliminated?: () => void;
107
135
  /** Filler picker for a seat nobody is deciding for. See `decide`. */
108
136
  strategy?: "fold-heavy" | "all-in";
109
137
  /** The seat's own judgement, called once per turn. See `decide`. */
@@ -120,9 +148,13 @@ export interface SeatPosition {
120
148
  view: ViewSnapshot;
121
149
  /** Which seat this is, as the authority cut the view. */
122
150
  seat: number;
123
- /** The seat's own two cards; null once it is out of the hand. Read from
124
- * the view's tail, exactly see `decodeParticipantView`. */
125
- hole: [Card, Card] | null;
151
+ /** The seat's own two cards as `turn` prints them, rank then suit: `["Ah",
152
+ * "Kd"]`; null once it is out of the hand. One shape on both doors, so a
153
+ * decision written against one runs against the other. */
154
+ hole: [string, string] | null;
155
+ /** The same two cards decoded, for a decision that wants rank and suit as
156
+ * values. Read from the view's tail, exactly — see `decodeParticipantView`. */
157
+ holeCards: [Card, Card] | null;
126
158
  /** The execution this seat is playing in, `0x`-prefixed. */
127
159
  executionId: string;
128
160
  /** The public table at this turn, or null where the read did not answer. */
@@ -199,7 +231,34 @@ export declare class SessionClient {
199
231
  eventsAfter: bigint;
200
232
  private readonly fetchImpl;
201
233
  constructor(baseUrl: string, agent: AgentKeypair, seat: number, participantId: Uint8Array, executionId: Uint8Array, executionManifestDigest: Uint8Array, clientNonce: Uint8Array, fetchImpl?: typeof fetch);
234
+ /** How long discovery keeps asking an authority that is not answering.
235
+ *
236
+ * Public so a test can shorten it; nothing else should need to. */
237
+ discoveryRetry: {
238
+ boundMs: number;
239
+ pauseMs: number;
240
+ sleep: (ms: number) => Promise<void>;
241
+ };
202
242
  private url;
243
+ /** Requires the authority's published discovery to host the current session
244
+ * contract, before any challenge, signature, or session request.
245
+ *
246
+ * Discovery is a compatibility predicate over published facts, never an
247
+ * authenticity claim: the join handshake is still the admission decision.
248
+ * The body is read under the same bound the shared binding enforces, so a
249
+ * hostile pre-join answer cannot stream into this process. A document that
250
+ * names no compatible contract is final -- an authority that cannot host
251
+ * the session does not become able to by asking again.
252
+ *
253
+ * An origin that did not answer with a document is another matter, and is
254
+ * asked again within `discoveryRetry`. A seat reconnects at whatever moment
255
+ * its stream dropped, and a deployment restarting behind that origin
256
+ * answers 404, 502 or nothing for a few seconds. Taken as final, one such
257
+ * answer ended a seat mid-sitting while its table played on without it, and
258
+ * the clock folded every turn it had left. */
259
+ private requireCompatibleDiscovery;
260
+ private answeredDiscovery;
261
+ private readBoundedDiscovery;
203
262
  challenge(): Promise<Uint8Array>;
204
263
  private postWire;
205
264
  join(): Promise<{
@@ -217,7 +276,7 @@ export declare class SessionClient {
217
276
  wire: Uint8Array;
218
277
  }>;
219
278
  startSubmit(wire: Uint8Array): Promise<Response>;
220
- acknowledge(context: SessionContext, cursor: ResumeCursor): Promise<void>;
279
+ acknowledge(context: SessionContext, cursor: ResumeCursor, originToken: string): Promise<void>;
221
280
  resume(context: SessionContext, cursor: ResumeCursor): Promise<void>;
222
281
  answerSeatAuth(challenge: {
223
282
  context: SessionContext;
@@ -227,6 +286,48 @@ export declare class SessionClient {
227
286
  coordinatorPublicKey: Uint8Array;
228
287
  }, coordinatorKey: Uint8Array, timeAuthorityKey: Uint8Array, pending: PendingProposal): Promise<void>;
229
288
  }
289
+ /** Everything a seat needs before it can play, from the offer it was admitted
290
+ * through.
291
+ *
292
+ * Lifted out of `playSeat` so the loop and the two short commands (`turn`,
293
+ * `act`) cannot drift on what they check. Every field here is re-derived from
294
+ * the arena on each call, which is what lets a seat be rebuilt in a fresh
295
+ * process: the only things that cannot be re-derived are the client nonce and
296
+ * the resume cursor, and those are what the seat state file keeps. */
297
+ export interface SeatSession {
298
+ client: SessionClient;
299
+ product: string;
300
+ executionHex: string;
301
+ coordinatorKey: Uint8Array;
302
+ timeAuthorityKey: Uint8Array;
303
+ sessionBaseUrl: string;
304
+ executionId: string;
305
+ }
306
+ export interface SeatSessionArgs {
307
+ productUrl: string;
308
+ offerId: string;
309
+ seat: number;
310
+ agent: AgentKeypair;
311
+ agentId: Uint8Array;
312
+ coordinatorKey?: Uint8Array;
313
+ timeAuthorityKey?: Uint8Array;
314
+ /** Stable across processes: the session is keyed by it, and a fresh one
315
+ * would resume nothing. Omitted only on the very first join. */
316
+ clientNonce?: Uint8Array;
317
+ fetchImpl?: typeof fetch;
318
+ /** Bounds on reading the offer through a product restart; tests shorten it. */
319
+ restartRetry?: RestartRetry;
320
+ }
321
+ export interface RestartRetry {
322
+ boundMs: number;
323
+ pauseMs: number;
324
+ sleep?: (ms: number) => Promise<void>;
325
+ }
326
+ /** A GET asked again while the origin answers as a restarting one does -- no
327
+ * answer, 408, 429 or a 5xx -- until `bound` has passed. Any other answer is
328
+ * returned as it came, so a refusal stays the caller's to read. */
329
+ export declare function fetchWhileRestarting(fetchImpl: typeof fetch, url: string, retry?: RestartRetry): Promise<Response>;
330
+ export declare function openSeatSession(args: SeatSessionArgs): Promise<SeatSession>;
230
331
  export declare function playSeat(args: PlayArgs): Promise<PlayReport>;
231
332
  /** What the play loop does about a refusal, by the refusal's tag.
232
333
  *
@@ -246,6 +347,22 @@ export interface OpenAgentNaming {
246
347
  * Two surfaces printed the same card two ways, and every agent normalised
247
348
  * them itself. */
248
349
  export declare function normaliseCardCode(code: string): string;
350
+ /** What the terminal disclosure says one seat takes off the table, read off
351
+ * the spectator snapshot's status.
352
+ *
353
+ * Read rather than assumed: consent checks the seat's chips against it before
354
+ * it signs, and a number a client invented would be refused there. The
355
+ * disclosure is published a moment after the seat sees the terminal, so this
356
+ * waits for it, bounded well inside the consent window.
357
+ *
358
+ * Signed as the seat where a capability is given. A private room's snapshot
359
+ * answers only its seats and owning wallets, so an anonymous read of it never
360
+ * finds an entitlement at all, and a room whose seats read that way never
361
+ * settles. A playground or tournament sitting ignores the capability. */
362
+ export declare function readDisclosedEntitlement(fetchImpl: typeof fetch, product: string, executionId: string, seat: number, capability?: ReadCapability, options?: {
363
+ attempts?: number;
364
+ pauseMs?: number;
365
+ }): Promise<bigint | undefined>;
249
366
  /** The public table for one execution, as the spectator snapshot shows it.
250
367
  *
251
368
  * Best-effort: a read that does not answer is null, never a throw -- the
@@ -254,7 +371,43 @@ export declare function normaliseCardCode(code: string): string;
254
371
  *
255
372
  * `names` is filled as agents are met and read from after that, so who is
256
373
  * in a seat costs one custody read per agent per sitting. */
257
- export declare function readPublicTable(fetchImpl: typeof fetch, product: string, executionHex: string, names?: Map<string, OpenAgentNaming | null>): Promise<OpenTableView | null>;
374
+ /** What the product says has become of a sitting.
375
+ *
376
+ * `over` is the answer a seat needs: there are no more turns in it, whether
377
+ * because it played out, is being settled, or ended badly. `live` means the
378
+ * table is still running and a seat that cannot attach has a real problem.
379
+ * `unknown` is not a guess -- the product could not say, or could not be
380
+ * reached, and telling an agent its match is over on that basis would be
381
+ * worse than telling it nothing. */
382
+ export type OpenSittingStatus = {
383
+ state: "over";
384
+ detail: "completed" | "settling" | "failed";
385
+ } | {
386
+ state: "live";
387
+ } | {
388
+ state: "unknown";
389
+ };
390
+ /** Read a sitting's status off the public history surface.
391
+ *
392
+ * Its own read rather than part of `readPublicTable`, because that answers
393
+ * what is on the table right now and returns nothing at all once the table is
394
+ * gone -- which is precisely the moment this question is being asked. */
395
+ export declare function readSittingStatus(fetchImpl: typeof fetch, product: string, executionHex: string): Promise<OpenSittingStatus>;
396
+ /** Mints the agent capability for one read, bound to its method and target.
397
+ *
398
+ * A callback rather than a header, because a capability is single-use and
399
+ * signed over the exact request: one minted for the previous poll authorizes
400
+ * nothing here. Null is an answer -- a reader with no key reads anonymously,
401
+ * which is all a playground sitting ever needs. */
402
+ export type ReadCapability = (method: string, requestTarget: string) => Promise<string | null>;
403
+ /** The capability `readPublicTable` asks for, minted on this agent's own key.
404
+ *
405
+ * A private room's live view is owner-session-only unless the reader holds a
406
+ * seat at that room, and the seat says so with the same signature every other
407
+ * Product API call carries. A playground sitting ignores one, so a seat can
408
+ * send it without having to know which kind of table it sat down at. */
409
+ export declare function agentReadCapability(agent: AgentKeypair, agentId: Uint8Array): ReadCapability;
410
+ export declare function readPublicTable(fetchImpl: typeof fetch, product: string, executionHex: string, names?: Map<string, OpenAgentNaming | null>, capability?: ReadCapability): Promise<OpenTableView | null>;
258
411
  /** What has been said in one hand, oldest first. Best-effort, like the
259
412
  * table: an empty list where the read did not answer. */
260
413
  export declare function readTableTalk(fetchImpl: typeof fetch, product: string, executionHex: string, handIndex: number): Promise<OpenTableTalkLine[]>;