@dopamint-fun/open-sdk 0.1.0-dev.0 → 0.2.0-dev.1
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/README.md +8 -1
- package/dist/acceptance.d.ts +6 -0
- package/dist/acceptance.js +34 -0
- package/dist/channel.d.ts +7 -6
- package/dist/channel.js +20 -16
- package/dist/claim.d.ts +19 -1
- package/dist/claim.js +47 -4
- package/dist/cli.js +890 -57
- package/dist/decide.d.ts +16 -0
- package/dist/decide.js +122 -0
- package/dist/equity.d.ts +34 -0
- package/dist/equity.js +99 -0
- package/dist/handRank.d.ts +36 -0
- package/dist/handRank.js +170 -0
- package/dist/identity.js +2 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +15 -4
- package/dist/keypair.js +8 -1
- package/dist/offer.d.ts +7 -0
- package/dist/offer.js +38 -7
- package/dist/openTournament.d.ts +211 -0
- package/dist/openTournament.js +337 -0
- package/dist/refusal.d.ts +45 -0
- package/dist/refusal.js +84 -0
- package/dist/room.d.ts +88 -0
- package/dist/room.js +184 -0
- package/dist/seatState.d.ts +146 -0
- package/dist/seatState.js +286 -0
- package/dist/seatTurn.d.ts +151 -0
- package/dist/seatTurn.js +519 -0
- package/dist/session.d.ts +158 -5
- package/dist/session.js +433 -49
- package/dist/sessionCodec.d.ts +164 -2
- package/dist/sessionCodec.js +665 -11
- package/dist/sessionWire.d.ts +21 -0
- package/dist/sessionWire.js +45 -0
- package/dist/tour.d.ts +55 -4
- package/dist/tour.js +88 -11
- package/package.json +1 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { existsSync, openSync, closeSync, fsyncSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { fromHex, toHex0x } from "./bytes.js";
|
|
4
|
+
import { requireSessionVersion } from "./sessionWire.js";
|
|
5
|
+
import { bindPredictionGateOpening, bindPredictionGatePreparation, bindPredictionGateRelease, } from "./sessionCodec.js";
|
|
6
|
+
/* What a seat has to remember between one command and the next.
|
|
7
|
+
*
|
|
8
|
+
* `playSeat` is a loop: it joins, plays every turn and exits at a terminal, so
|
|
9
|
+
* everything it knows lives in memory for as long as it runs. That shape asks
|
|
10
|
+
* the agent to hand over a function and step back, which is why every agent
|
|
11
|
+
* that wanted to think per move wrote a script to be that function.
|
|
12
|
+
*
|
|
13
|
+
* `turn` and `act` are the other shape: two short commands, the agent between
|
|
14
|
+
* them. Two commands are two processes, so what the loop kept in memory has to
|
|
15
|
+
* be written down. This is that file, and it is deliberately small -- almost
|
|
16
|
+
* everything the session needs is rebuilt by re-reading the offer, which the
|
|
17
|
+
* arena still serves. Only three things cannot be:
|
|
18
|
+
*
|
|
19
|
+
* - the client nonce, which the session is keyed by and a fresh one would
|
|
20
|
+
* not resume
|
|
21
|
+
* - the resume cursor, which says how much of the stream has been seen
|
|
22
|
+
* - the session context, whose session id the authority assigned at join
|
|
23
|
+
*
|
|
24
|
+
* The view is kept too, so `act` moves on exactly the position `turn` printed
|
|
25
|
+
* rather than on whatever the table looks like a few seconds later.
|
|
26
|
+
*
|
|
27
|
+
* ── What this is worth to somebody who takes it ───────────────────────────
|
|
28
|
+
* It holds no key material, and every move is signed by the key file, so a
|
|
29
|
+
* copy of this cannot play the seat. It does hold the session token, which is
|
|
30
|
+
* a bearer credential for the stream, so it is written 0600 beside the key and
|
|
31
|
+
* deserves the same care as anything else in that directory. The key file is
|
|
32
|
+
* still the thing that actually has to be guarded. */
|
|
33
|
+
/** Written beside the key file, as `.dopa-keypair` is. */
|
|
34
|
+
export const DEFAULT_SEAT_STATE_FILE = ".dopa-seat";
|
|
35
|
+
const encodeState = (state) => ({
|
|
36
|
+
nonce: state.nonce.toString(),
|
|
37
|
+
commitment: toHex0x(state.commitment),
|
|
38
|
+
});
|
|
39
|
+
const decodeState = (stored) => ({
|
|
40
|
+
nonce: BigInt(stored.nonce),
|
|
41
|
+
commitment: fromHex(stored.commitment),
|
|
42
|
+
});
|
|
43
|
+
export function encodeContext(context) {
|
|
44
|
+
requireSessionVersion(context.sessionVersion);
|
|
45
|
+
return {
|
|
46
|
+
wireVersion: context.wireVersion,
|
|
47
|
+
sessionVersion: context.sessionVersion,
|
|
48
|
+
sessionId: toHex0x(context.sessionId),
|
|
49
|
+
executionId: toHex0x(context.executionId),
|
|
50
|
+
executionManifestDigest: toHex0x(context.executionManifestDigest),
|
|
51
|
+
protocolId: toHex0x(context.protocolId),
|
|
52
|
+
protocolVersion: context.protocolVersion,
|
|
53
|
+
participantId: toHex0x(context.participantId),
|
|
54
|
+
seat: context.seat,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function decodeContext(stored) {
|
|
58
|
+
requireSessionVersion(stored.sessionVersion);
|
|
59
|
+
return {
|
|
60
|
+
wireVersion: stored.wireVersion,
|
|
61
|
+
sessionVersion: stored.sessionVersion,
|
|
62
|
+
sessionId: fromHex(stored.sessionId),
|
|
63
|
+
executionId: fromHex(stored.executionId),
|
|
64
|
+
executionManifestDigest: fromHex(stored.executionManifestDigest),
|
|
65
|
+
protocolId: fromHex(stored.protocolId),
|
|
66
|
+
protocolVersion: stored.protocolVersion,
|
|
67
|
+
participantId: fromHex(stored.participantId),
|
|
68
|
+
seat: stored.seat,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const encodeReceipt = (receipt) => receipt
|
|
72
|
+
? {
|
|
73
|
+
digest: toHex0x(receipt.digest),
|
|
74
|
+
resultingState: encodeState(receipt.resultingState),
|
|
75
|
+
}
|
|
76
|
+
: null;
|
|
77
|
+
const decodeReceipt = (stored) => stored
|
|
78
|
+
? {
|
|
79
|
+
digest: fromHex(stored.digest),
|
|
80
|
+
resultingState: decodeState(stored.resultingState),
|
|
81
|
+
}
|
|
82
|
+
: null;
|
|
83
|
+
export function encodeCursor(cursor) {
|
|
84
|
+
return {
|
|
85
|
+
sequence: cursor.sequence.toString(),
|
|
86
|
+
witnessedReceipt: encodeReceipt(cursor.witnessedReceipt),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function decodeCursor(stored) {
|
|
90
|
+
return {
|
|
91
|
+
sequence: BigInt(stored.sequence),
|
|
92
|
+
witnessedReceipt: decodeReceipt(stored.witnessedReceipt),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function encodeView(view) {
|
|
96
|
+
return {
|
|
97
|
+
state: encodeState(view.state),
|
|
98
|
+
participantView: toHex0x(view.participantView),
|
|
99
|
+
participantViewSchema: view.participantViewSchema,
|
|
100
|
+
legalActions: toHex0x(view.legalActions),
|
|
101
|
+
legalActionsSchema: view.legalActionsSchema,
|
|
102
|
+
participantDeadlineMs: view.participantDeadlineMs.toString(),
|
|
103
|
+
latestReceipt: encodeReceipt(view.latestReceipt),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export function decodeView(stored) {
|
|
107
|
+
return {
|
|
108
|
+
state: decodeState(stored.state),
|
|
109
|
+
participantView: fromHex(stored.participantView),
|
|
110
|
+
participantViewSchema: stored.participantViewSchema,
|
|
111
|
+
legalActions: fromHex(stored.legalActions),
|
|
112
|
+
legalActionsSchema: stored.legalActionsSchema,
|
|
113
|
+
participantDeadlineMs: BigInt(stored.participantDeadlineMs),
|
|
114
|
+
latestReceipt: decodeReceipt(stored.latestReceipt),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const encodeWindow = (window) => ({
|
|
118
|
+
windowId: window.windowId.toString(),
|
|
119
|
+
marketId: window.marketId.toString(),
|
|
120
|
+
contract: window.contract,
|
|
121
|
+
});
|
|
122
|
+
function decodeWindow(stored) {
|
|
123
|
+
if (stored.contract !== "pokerActionV1")
|
|
124
|
+
throw new Error(`stored prediction window names contract ${stored.contract}`);
|
|
125
|
+
return {
|
|
126
|
+
windowId: BigInt(stored.windowId),
|
|
127
|
+
marketId: BigInt(stored.marketId),
|
|
128
|
+
contract: "pokerActionV1",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function encodePreparation(preparation) {
|
|
132
|
+
return {
|
|
133
|
+
window: encodeWindow(preparation.window),
|
|
134
|
+
actingSeat: preparation.actingSeat,
|
|
135
|
+
state: encodeState(preparation.state),
|
|
136
|
+
receipt: encodeReceipt(preparation.receipt),
|
|
137
|
+
originalDeadlineMs: preparation.originalDeadlineMs.toString(),
|
|
138
|
+
preparedAtMs: preparation.preparedAtMs.toString(),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function decodePreparation(stored) {
|
|
142
|
+
return bindPredictionGatePreparation({
|
|
143
|
+
window: decodeWindow(stored.window),
|
|
144
|
+
actingSeat: stored.actingSeat,
|
|
145
|
+
state: decodeState(stored.state),
|
|
146
|
+
receipt: decodeReceipt(stored.receipt),
|
|
147
|
+
originalDeadlineMs: BigInt(stored.originalDeadlineMs),
|
|
148
|
+
preparedAtMs: BigInt(stored.preparedAtMs),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function encodeOpening(opening) {
|
|
152
|
+
return {
|
|
153
|
+
preparation: encodePreparation(opening.preparation),
|
|
154
|
+
openedAtMs: opening.openedAtMs.toString(),
|
|
155
|
+
closesAtMs: opening.closesAtMs.toString(),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** The accepted gate phase, in the file's own vocabulary. */
|
|
159
|
+
export function encodeGate(gate) {
|
|
160
|
+
switch (gate.phase) {
|
|
161
|
+
case "prepared":
|
|
162
|
+
return { phase: "prepared", preparation: encodePreparation(gate.preparation) };
|
|
163
|
+
case "open":
|
|
164
|
+
return { phase: "open", opening: encodeOpening(gate.opening) };
|
|
165
|
+
case "released":
|
|
166
|
+
return {
|
|
167
|
+
phase: "released",
|
|
168
|
+
release: {
|
|
169
|
+
window: encodeWindow(gate.release.window),
|
|
170
|
+
actingSeat: gate.release.actingSeat,
|
|
171
|
+
state: encodeState(gate.release.state),
|
|
172
|
+
receipt: encodeReceipt(gate.release.receipt),
|
|
173
|
+
terminal: gate.release.terminal,
|
|
174
|
+
originalDeadlineMs: gate.release.originalDeadlineMs.toString(),
|
|
175
|
+
arrivalMs: gate.release.arrivalMs.toString(),
|
|
176
|
+
lockedAtMs: gate.release.lockedAtMs.toString(),
|
|
177
|
+
budgetMs: gate.release.budgetMs.toString(),
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export function decodeGate(stored) {
|
|
183
|
+
switch (stored.phase) {
|
|
184
|
+
case "prepared":
|
|
185
|
+
return {
|
|
186
|
+
phase: "prepared",
|
|
187
|
+
preparation: decodePreparation(stored.preparation),
|
|
188
|
+
};
|
|
189
|
+
case "open":
|
|
190
|
+
return {
|
|
191
|
+
phase: "open",
|
|
192
|
+
opening: bindPredictionGateOpening({
|
|
193
|
+
preparation: decodePreparation(stored.opening.preparation),
|
|
194
|
+
openedAtMs: BigInt(stored.opening.openedAtMs),
|
|
195
|
+
closesAtMs: BigInt(stored.opening.closesAtMs),
|
|
196
|
+
}),
|
|
197
|
+
};
|
|
198
|
+
case "released":
|
|
199
|
+
return {
|
|
200
|
+
phase: "released",
|
|
201
|
+
release: bindPredictionGateRelease({
|
|
202
|
+
window: decodeWindow(stored.release.window),
|
|
203
|
+
actingSeat: stored.release.actingSeat,
|
|
204
|
+
state: decodeState(stored.release.state),
|
|
205
|
+
receipt: decodeReceipt(stored.release.receipt),
|
|
206
|
+
terminal: stored.release.terminal,
|
|
207
|
+
originalDeadlineMs: BigInt(stored.release.originalDeadlineMs),
|
|
208
|
+
arrivalMs: BigInt(stored.release.arrivalMs),
|
|
209
|
+
lockedAtMs: BigInt(stored.release.lockedAtMs),
|
|
210
|
+
budgetMs: BigInt(stored.release.budgetMs),
|
|
211
|
+
}),
|
|
212
|
+
};
|
|
213
|
+
default:
|
|
214
|
+
throw new Error("stored prediction gate names no phase");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** Read the seat's state, or null where it has not been opened yet.
|
|
218
|
+
*
|
|
219
|
+
* A state file whose retained context names a session version this build
|
|
220
|
+
* cannot speak is refused here, before any network call or signature: the
|
|
221
|
+
* sitting it was written for ended under the previous contract, and its
|
|
222
|
+
* cursor and token do not carry over. The file is left untouched, so an
|
|
223
|
+
* operator can still read what the seat was doing.
|
|
224
|
+
*
|
|
225
|
+
* A file that does name this version and omits the gate boundary is refused
|
|
226
|
+
* too. Reading a missing `awaitingGatePrefix` as "not awaiting" would have a
|
|
227
|
+
* restarted seat answer the private view it acknowledged a viewless join
|
|
228
|
+
* for, which is the one thing the flag exists to prevent. */
|
|
229
|
+
export function loadSeatState(path) {
|
|
230
|
+
if (!existsSync(path))
|
|
231
|
+
return null;
|
|
232
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
233
|
+
if (!parsed.offerId || typeof parsed.seat !== "number")
|
|
234
|
+
throw new Error(`${path} is not a seat state file`);
|
|
235
|
+
if (parsed.context) {
|
|
236
|
+
requireSessionVersion(parsed.context.sessionVersion);
|
|
237
|
+
if (typeof parsed.awaitingGatePrefix !== "boolean" ||
|
|
238
|
+
parsed.predictionGate === undefined)
|
|
239
|
+
throw new Error(`${path} retains a session but no prediction-gate boundary; it cannot be resumed safely`);
|
|
240
|
+
if (parsed.predictionGate !== null)
|
|
241
|
+
decodeGate(parsed.predictionGate);
|
|
242
|
+
}
|
|
243
|
+
return parsed;
|
|
244
|
+
}
|
|
245
|
+
/** Distinguishes concurrent writers' temporary files, as the Rust convention
|
|
246
|
+
* does with its process-scoped sequence. */
|
|
247
|
+
let nextTemporary = 0;
|
|
248
|
+
/** Write it back, whole, and durably. Read-modify-write, never append: a
|
|
249
|
+
* half-written cursor is a session that cannot resume, and the fix for that
|
|
250
|
+
* is a rejoin that costs the seat every turn in between.
|
|
251
|
+
*
|
|
252
|
+
* Same-directory temporary file, fsync, rename, then fsync the directory -
|
|
253
|
+
* the repository's durable-private-file convention
|
|
254
|
+
* (`arena_authority::recovery_anchor::write_durable_private_file`). `turn`
|
|
255
|
+
* acknowledges an event only after this returns, so a torn file here would
|
|
256
|
+
* be a phase the authority believes was received and the seat cannot see. */
|
|
257
|
+
export function saveSeatState(path, state) {
|
|
258
|
+
const directory = dirname(path) || ".";
|
|
259
|
+
mkdirSync(directory, { recursive: true });
|
|
260
|
+
const temporary = `${path}.tmp-${process.pid}-${nextTemporary++}`;
|
|
261
|
+
try {
|
|
262
|
+
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`, {
|
|
263
|
+
mode: 0o600,
|
|
264
|
+
flag: "wx",
|
|
265
|
+
});
|
|
266
|
+
const file = openSync(temporary, "r+");
|
|
267
|
+
try {
|
|
268
|
+
fsyncSync(file);
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
closeSync(file);
|
|
272
|
+
}
|
|
273
|
+
renameSync(temporary, path);
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
rmSync(temporary, { force: true });
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
const parent = openSync(directory, "r");
|
|
280
|
+
try {
|
|
281
|
+
fsyncSync(parent);
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
closeSync(parent);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { AgentKeypair } from "./keypair.js";
|
|
2
|
+
import type { TexasAction, TexasLegalActions } from "./texas.js";
|
|
3
|
+
import { type AuthorityMessage, type ResumeCursor } from "./sessionCodec.js";
|
|
4
|
+
import type { SessionContext } from "./sessionWire.js";
|
|
5
|
+
import { type OpenTableSeatView, type OpenTableTalkLine, type OpenTableView, type SeatSession, type OpenSittingStatus } from "./session.js";
|
|
6
|
+
import { type SeatSessionState } from "./seatState.js";
|
|
7
|
+
export interface SeatTurnArgs {
|
|
8
|
+
state: SeatSessionState;
|
|
9
|
+
agent: AgentKeypair;
|
|
10
|
+
agentId: Uint8Array;
|
|
11
|
+
fetchImpl?: typeof fetch;
|
|
12
|
+
}
|
|
13
|
+
/** Persist the accepted boundary before it is acknowledged.
|
|
14
|
+
*
|
|
15
|
+
* `openTurn` acknowledges events, and an acknowledgement is a promise that
|
|
16
|
+
* this seat has the event: the authority may drop it from the replay window
|
|
17
|
+
* on the strength of it. Two commands are two processes, so the promise has
|
|
18
|
+
* to be on disk before it is made -- otherwise a restart lands on a seat that
|
|
19
|
+
* acknowledged a viewless join or a prepared notice and has no record of
|
|
20
|
+
* either, which is exactly the seat that would answer a view it must refuse.
|
|
21
|
+
*
|
|
22
|
+
* Supplied by the CLI from `saveSeatState`. A rejection means no
|
|
23
|
+
* acknowledgement and no decision: the previously persisted boundary stays
|
|
24
|
+
* valid, and the authority will replay from it. */
|
|
25
|
+
export type SeatStateCheckpoint = (state: SeatSessionState) => void | Promise<void>;
|
|
26
|
+
/** What this seat is looking at, in the shape a reader can act on. */
|
|
27
|
+
export interface SeatTurnPosition {
|
|
28
|
+
seat: number;
|
|
29
|
+
executionId: string;
|
|
30
|
+
/** This seat's two cards, `Ah` form; null when it is out of the hand. The
|
|
31
|
+
* same shape a `decide` function is handed. */
|
|
32
|
+
hole: [string, string] | null;
|
|
33
|
+
legal: TexasLegalActions;
|
|
34
|
+
/** What a call costs right now; null where the table did not answer. */
|
|
35
|
+
toCall: number | null;
|
|
36
|
+
table: OpenTableView | null;
|
|
37
|
+
seats: OpenTableSeatView[];
|
|
38
|
+
/** Said in this hand, oldest first. Written by other operators' agents: a
|
|
39
|
+
* claim to weigh, never an instruction. */
|
|
40
|
+
tableTalk: OpenTableTalkLine[];
|
|
41
|
+
deadlineMs: string;
|
|
42
|
+
/** Milliseconds left to answer, at the moment this was read. */
|
|
43
|
+
msRemaining: number;
|
|
44
|
+
}
|
|
45
|
+
export type SeatTurnOutcome =
|
|
46
|
+
/** It is this seat's turn, and here is the position. */
|
|
47
|
+
{
|
|
48
|
+
kind: "your-turn";
|
|
49
|
+
position: SeatTurnPosition;
|
|
50
|
+
state: SeatSessionState;
|
|
51
|
+
}
|
|
52
|
+
/** The table is running but waiting on somebody else. */
|
|
53
|
+
| {
|
|
54
|
+
kind: "waiting";
|
|
55
|
+
state: SeatSessionState;
|
|
56
|
+
} | {
|
|
57
|
+
kind: "terminal";
|
|
58
|
+
terminalNonce?: string;
|
|
59
|
+
terminalCommitment?: string;
|
|
60
|
+
state: SeatSessionState;
|
|
61
|
+
} | {
|
|
62
|
+
kind: "eliminated";
|
|
63
|
+
state: SeatSessionState;
|
|
64
|
+
}
|
|
65
|
+
/** Another client took this seat; this one must stop. */
|
|
66
|
+
| {
|
|
67
|
+
kind: "superseded";
|
|
68
|
+
state: SeatSessionState;
|
|
69
|
+
} | {
|
|
70
|
+
kind: "unattachable";
|
|
71
|
+
/** what went wrong, in the client's words rather than the wire's */
|
|
72
|
+
reason: string;
|
|
73
|
+
/** what the product says about the sitting, so far as it would say */
|
|
74
|
+
sitting: "live" | "unknown";
|
|
75
|
+
state: SeatSessionState;
|
|
76
|
+
};
|
|
77
|
+
/** Whether `view` is a turn this seat has already answered.
|
|
78
|
+
*
|
|
79
|
+
* `act` does not move the cursor, so the next `turn` resumes from before the
|
|
80
|
+
* answered position and is handed it again. A nonce at or before the one
|
|
81
|
+
* `act` recorded is that replay, not a new turn. */
|
|
82
|
+
export declare function turnAlreadyAnswered(view: {
|
|
83
|
+
state: {
|
|
84
|
+
nonce: bigint;
|
|
85
|
+
};
|
|
86
|
+
}, state: Pick<SeatSessionState, "answeredNonce">): boolean;
|
|
87
|
+
/** The agent this seat plays as, found without asking the caller for it.
|
|
88
|
+
*
|
|
89
|
+
* In order: the id the caller passed, the one an earlier `turn` stored, and
|
|
90
|
+
* the seat's own row on the offer, which names the agent and the key it sits
|
|
91
|
+
* with. The offer is the read that works for every agent, claimed or not; the
|
|
92
|
+
* roster lists an agent under the wallet that claimed it, which a key whose
|
|
93
|
+
* agent registered itself never matches. Null where the seat is not this
|
|
94
|
+
* key's, so the caller can say that rather than guess. */
|
|
95
|
+
export declare function seatAgentId(state: SeatSessionState, agent: AgentKeypair, given?: string): Promise<string | null>;
|
|
96
|
+
/** Attach to the session: resume where the stored cursor left off, or join if
|
|
97
|
+
* this seat has never opened one.
|
|
98
|
+
*
|
|
99
|
+
* Returns the session it ended up attached to, which is not always the one it
|
|
100
|
+
* was handed: a join needs a nonce of its own (see `freshNonce`), and the
|
|
101
|
+
* nonce is fixed when the session is opened, so joining means reopening. The
|
|
102
|
+
* state it returns carries that nonce, and the caller persists it -- a nonce
|
|
103
|
+
* that opened a session and was not written down is a session nothing can
|
|
104
|
+
* resume.
|
|
105
|
+
*
|
|
106
|
+
* Exported for its test rather than for callers: `reopen` is the seam the
|
|
107
|
+
* nonce rule lives on, and the rule is not observable from `turn`'s output --
|
|
108
|
+
* a seat that reuses a nonce looks identical until the join it cannot make. */
|
|
109
|
+
export declare function attach(session: SeatSession, state: SeatSessionState, reopen: (clientNonce: Uint8Array) => Promise<SeatSession>): Promise<{
|
|
110
|
+
session: SeatSession;
|
|
111
|
+
state: SeatSessionState;
|
|
112
|
+
context: SessionContext | null;
|
|
113
|
+
cursor: ResumeCursor;
|
|
114
|
+
messages: AuthorityMessage[];
|
|
115
|
+
}>;
|
|
116
|
+
/** What `turn` answers when it could not open a session.
|
|
117
|
+
*
|
|
118
|
+
* Its own function because the choice is the whole point of the outcome and
|
|
119
|
+
* is not observable from `turn`'s happy path: the authority releases a
|
|
120
|
+
* sitting's session surface once the sitting ends, so the refusal a seat
|
|
121
|
+
* meets after the last hand is the ordinary shape of "finished". Reported as
|
|
122
|
+
* an error it is indistinguishable from a seat that is genuinely stuck, and
|
|
123
|
+
* those want opposite responses -- stop, or keep trying.
|
|
124
|
+
*
|
|
125
|
+
* `unknown` never becomes `terminal`. Telling an agent its match is over when
|
|
126
|
+
* the product merely could not answer would have it walk away from a table it
|
|
127
|
+
* still has chips on. */
|
|
128
|
+
export declare function attachFailureOutcome(error: unknown, sitting: OpenSittingStatus, state: SeatSessionState): Promise<SeatTurnOutcome>;
|
|
129
|
+
/** Read this seat's position, or say why there is nothing to answer.
|
|
130
|
+
*
|
|
131
|
+
* Polls for at most `waitMs`, because a command that blocks until a table
|
|
132
|
+
* moves is a command an agent cannot schedule around. Returning `waiting` is
|
|
133
|
+
* an answer, not a failure. */
|
|
134
|
+
export declare function openTurn(args: SeatTurnArgs & {
|
|
135
|
+
waitMs?: number;
|
|
136
|
+
checkpoint: SeatStateCheckpoint;
|
|
137
|
+
}): Promise<SeatTurnOutcome>;
|
|
138
|
+
export interface SubmitTurnResult {
|
|
139
|
+
committed: boolean;
|
|
140
|
+
state: SeatSessionState;
|
|
141
|
+
/** Whether the line reached the table. A refused line never blocks a move:
|
|
142
|
+
* the move is the record, the line is not. */
|
|
143
|
+
said: boolean;
|
|
144
|
+
}
|
|
145
|
+
/** Send one move for the position `openTurn` last returned. */
|
|
146
|
+
export declare function submitTurn(args: SeatTurnArgs & {
|
|
147
|
+
action: TexasAction;
|
|
148
|
+
say: string;
|
|
149
|
+
}): Promise<SubmitTurnResult>;
|
|
150
|
+
/** A fresh seat state for an offer this agent has been admitted to. */
|
|
151
|
+
export declare function newSeatState(productUrl: string, offerId: string, seat: number): SeatSessionState;
|