@dopamint-fun/open-sdk 0.2.0-dev.0 → 0.2.0-dev.10
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 +3 -3
- package/dist/agentHttp.js +12 -15
- package/dist/bytes.js +1 -1
- package/dist/claim.js +38 -22
- package/dist/cli.js +79 -56
- package/dist/identity.js +19 -37
- package/dist/index.d.ts +7 -7
- package/dist/index.js +7 -7
- package/dist/offer.js +3 -3
- package/dist/openTournament.d.ts +22 -0
- package/dist/openTournament.js +34 -3
- package/dist/refusal.d.ts +5 -0
- package/dist/refusal.js +21 -0
- package/dist/room.d.ts +29 -7
- package/dist/room.js +40 -10
- package/dist/seatState.d.ts +61 -5
- package/dist/seatState.js +162 -7
- package/dist/seatTurn.d.ts +15 -2
- package/dist/seatTurn.js +99 -22
- package/dist/session.d.ts +12 -0
- package/dist/session.js +153 -55
- package/dist/sessionCodec.d.ts +162 -2
- package/dist/sessionCodec.js +658 -20
- package/dist/sessionWire.d.ts +6 -5
- package/dist/sessionWire.js +8 -7
- package/dist/settlement.d.ts +5 -1
- package/dist/settlement.js +6 -7
- package/dist/tour.js +4 -4
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -2,10 +2,10 @@ export { DEFAULT_KEY_FILE, generateKeypair, keypairFromSeed, loadKeypair, saveKe
|
|
|
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
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
|
-
export { encodeAckFrame, SESSION_ERROR_NAMES, sessionErrorHint, sessionErrorName, } from "./sessionCodec.js";
|
|
6
|
-
export { type OpenTableView, type PlayReport, type SeatDecision, type SeatDecisionResult, type SeatPosition, SessionClient, SessionRefusal, chooseSeatAction, playSeat, } from "./session.js";
|
|
5
|
+
export { encodeAckFrame, SESSION_ERROR_NAMES, MAX_PREDICTION_PACING_MS, admitAuthorityEvent, applyPredictionGateStatus, decodeAuthorityMessage, freshSessionBoundary, predictionGateMessage, predictionGatePreparationOf, predictionGateTargetReceipt, sessionErrorHint, sessionErrorName, PredictionGateConflictError, SessionBoundaryError, type AcceptedSessionBoundary, type AuthorityMessage, type PredictionGateMessage, type PredictionGateOpening, type PredictionGatePreparation, type PredictionGateRelease, type PredictionGateStatus, type PredictionWindowRef, type ReceiptRef, type ResumeCursor, type StateRef, type ViewSnapshot, } from "./sessionCodec.js";
|
|
6
|
+
export { type OpenTableView, type PlayReport, type SeatDecision, type SeatDecisionResult, type SeatPosition, SessionClient, SessionRefusal, chooseSeatAction, playSeat, waitForReadyOwner, } 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";
|
|
8
|
-
export {
|
|
8
|
+
export { buildConsentRequest, DEFAULT_OPEN_API_BASE_URL, digestForPrompt, recomputeSettlementDigest, settlementConsentPath, verifyConsentDisclosure, } from "./settlement.js";
|
|
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";
|
|
@@ -14,8 +14,8 @@ export { joinRoom, MAX_ROOM_SEATS, MIN_ROOM_SEATS, openRoom, roomInvitePrompt, t
|
|
|
14
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
15
|
export { baselineDecision, baselineMove, opponentsStillIn, } from "./decide.js";
|
|
16
16
|
export { handEquity, requiredEquity, cardCode, type HandEquity, type HandEquityOptions, } from "./equity.js";
|
|
17
|
-
export {
|
|
17
|
+
export { cardIndex, handCategory, scoreHand, HAND_CATEGORIES, RANK_ORDER, SUIT_ORDER, type BestFive, type HandCategory, } from "./handRank.js";
|
|
18
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";
|
|
19
|
+
export { newSeatState, openTurn, submitTurn, type SeatStateCheckpoint, type SeatTurnOutcome, type SeatTurnPosition, } from "./seatTurn.js";
|
|
20
|
+
export { chipAddressOf, joinTransaction, leaveTransaction, listTournaments, planJoin, presentToTournament, readAgentEntry, readAgentEntrySettled, BOOK_CATCHES_UP_MS, 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, ownershipFencePoll, ownershipFenced, refusalLines, refusalMessage, refusalMessageFromText, type RefusalBody, type RefusalNext, } from "./refusal.js";
|
package/dist/index.js
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
* Generate and hold an ed25519 keypair (Sui format, `.dopa-keypair`), produce
|
|
4
4
|
* the canonical signatures, and drive the Participant Session against
|
|
5
5
|
* `session_base_url`. Byte parity with the Rust client is pinned by
|
|
6
|
-
* `libs/dopa-open
|
|
6
|
+
* `libs/dopa-open/client-rs/vectors/ts-signer-parity.json`.
|
|
7
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
11
|
export { actionSigningBytes, encodeActionFrame, encodeJoinFrame, encodeResumeFrame, joinSigningBytes, resumeSigningBytes, SESSION_VERSION, UnsupportedSessionVersionError, } from "./sessionWire.js";
|
|
12
|
-
export { encodeAckFrame, SESSION_ERROR_NAMES, sessionErrorHint, sessionErrorName, } from "./sessionCodec.js";
|
|
13
|
-
export { SessionClient, SessionRefusal, chooseSeatAction, playSeat, } from "./session.js";
|
|
12
|
+
export { encodeAckFrame, SESSION_ERROR_NAMES, MAX_PREDICTION_PACING_MS, admitAuthorityEvent, applyPredictionGateStatus, decodeAuthorityMessage, freshSessionBoundary, predictionGateMessage, predictionGatePreparationOf, predictionGateTargetReceipt, sessionErrorHint, sessionErrorName, PredictionGateConflictError, SessionBoundaryError, } from "./sessionCodec.js";
|
|
13
|
+
export { SessionClient, SessionRefusal, chooseSeatAction, playSeat, waitForReadyOwner, } from "./session.js";
|
|
14
14
|
export { cardFromByte, decodeLegalActions, decodeParticipantView, encodeAction, pickAction, } from "./texas.js";
|
|
15
|
-
export {
|
|
15
|
+
export { buildConsentRequest, DEFAULT_OPEN_API_BASE_URL, digestForPrompt, recomputeSettlementDigest, settlementConsentPath, verifyConsentDisclosure, } from "./settlement.js";
|
|
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";
|
|
@@ -26,8 +26,8 @@ export { joinRoom, MAX_ROOM_SEATS, MIN_ROOM_SEATS, openRoom, roomInvitePrompt, }
|
|
|
26
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
27
|
export { baselineDecision, baselineMove, opponentsStillIn, } from "./decide.js";
|
|
28
28
|
export { handEquity, requiredEquity, cardCode, } from "./equity.js";
|
|
29
|
-
export {
|
|
29
|
+
export { cardIndex, handCategory, scoreHand, HAND_CATEGORIES, RANK_ORDER, SUIT_ORDER, } from "./handRank.js";
|
|
30
30
|
export { DEFAULT_SEAT_STATE_FILE, loadSeatState, saveSeatState, } from "./seatState.js";
|
|
31
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";
|
|
32
|
+
export { chipAddressOf, joinTransaction, leaveTransaction, listTournaments, planJoin, presentToTournament, readAgentEntry, readAgentEntrySettled, BOOK_CATCHES_UP_MS, readPass, readOwner, playsHeldBy, giveBackTransaction, readTournament, sponsorAndExecute, tournamentIdArg, tournamentPossessionSignature, JoinRefused, TournamentRefusal, TOURNAMENT_POSSESSION_SEAT, } from "./openTournament.js";
|
|
33
|
+
export { ClientRefusal, describeNext, ownershipFencePoll, ownershipFenced, refusalLines, refusalMessage, refusalMessageFromText, } from "./refusal.js";
|
package/dist/offer.js
CHANGED
|
@@ -52,7 +52,7 @@ function record(raw) {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
export async function readOffer(productUrl, offerId) {
|
|
55
|
-
const { status, json } = await readJson(`${productUrl.replace(/\/$/, "")}/
|
|
55
|
+
const { status, json } = await readJson(`${productUrl.replace(/\/$/, "")}/v1/playground/matches/${offerId}`);
|
|
56
56
|
if (status !== 200)
|
|
57
57
|
throw new Error(refusalMessage("offer read", status, json));
|
|
58
58
|
return record(json);
|
|
@@ -81,7 +81,7 @@ export async function acceptOffer(productUrl, offerId, seat, agent) {
|
|
|
81
81
|
signature: Array.from(signed.signature),
|
|
82
82
|
seat_possession_signature: Array.from(signed.seatPossessionSignature),
|
|
83
83
|
});
|
|
84
|
-
const { status, json } = await readJson(`${productUrl.replace(/\/$/, "")}/
|
|
84
|
+
const { status, json } = await readJson(`${productUrl.replace(/\/$/, "")}/v1/playground/matches/${offerId}/acceptances`, { method: "POST", headers: { "content-type": "application/json" }, body });
|
|
85
85
|
if (status === 200 || status === 201)
|
|
86
86
|
return;
|
|
87
87
|
if (status === 409)
|
|
@@ -89,7 +89,7 @@ export async function acceptOffer(productUrl, offerId, seat, agent) {
|
|
|
89
89
|
throw new Error(refusalMessage("acceptance", status, json));
|
|
90
90
|
}
|
|
91
91
|
async function admit(productUrl, offerId, agentId, agent) {
|
|
92
|
-
const target = `/
|
|
92
|
+
const target = `/v1/playground/matches/${offerId}/admit`;
|
|
93
93
|
const { header } = await mintAgentHttpCapability(agent, agentId, {
|
|
94
94
|
method: "POST",
|
|
95
95
|
requestTarget: target,
|
package/dist/openTournament.d.ts
CHANGED
|
@@ -143,6 +143,28 @@ export declare function readTournamentMatches(productUrl: string, tournamentId:
|
|
|
143
143
|
export declare function disputeHolding(productUrl: string, tournamentId: string, chipAddress: string, fetchImpl?: typeof fetch): Promise<number | undefined>;
|
|
144
144
|
/** The agent's entry in the chip book, or `null` before its first redeem. */
|
|
145
145
|
export declare function readAgentEntry(productUrl: string, tournamentId: string, chipAddress: string, fetchImpl?: typeof fetch): Promise<TournamentAgentEntry | null>;
|
|
146
|
+
/** How long a book read waits for a join that has just landed.
|
|
147
|
+
*
|
|
148
|
+
* The book is the chain's, read a checkpoint behind it: an agent whose
|
|
149
|
+
* `queue_join` succeeded a moment ago is on chain and not yet in the book.
|
|
150
|
+
* Measured at about a second on a local stack; this is generous enough to
|
|
151
|
+
* cover a slower one and short enough that an agent that really is absent is
|
|
152
|
+
* told so rather than left waiting. */
|
|
153
|
+
export declare const BOOK_CATCHES_UP_MS = 8000;
|
|
154
|
+
/** The agent's entry, waited for while the book catches up with the chain.
|
|
155
|
+
*
|
|
156
|
+
* For a caller that has just been told its join landed. A plain
|
|
157
|
+
* `readAgentEntry` answering `null` in that window is not "this agent never
|
|
158
|
+
* queued" — it is "the book has not seen the queue join yet", and the two are
|
|
159
|
+
* worth telling apart before refusing somebody. Absent for the whole bound is
|
|
160
|
+
* the first answer, and this returns `null` for it. */
|
|
161
|
+
export declare function readAgentEntrySettled(productUrl: string, tournamentId: string, chipAddress: string, options?: {
|
|
162
|
+
boundMs?: number;
|
|
163
|
+
pollMs?: number;
|
|
164
|
+
fetchImpl?: typeof fetch;
|
|
165
|
+
sleep?: (ms: number) => Promise<void>;
|
|
166
|
+
now?: () => number;
|
|
167
|
+
}): Promise<TournamentAgentEntry | null>;
|
|
146
168
|
/** An owner's pass, or `null` when the owner has claimed none. */
|
|
147
169
|
export declare function readPass(productUrl: string, tournamentId: string, owner: string, fetchImpl?: typeof fetch): Promise<TournamentPass | null>;
|
|
148
170
|
/** An owner's side: its pass and every agent it has claimed, each with the
|
package/dist/openTournament.js
CHANGED
|
@@ -26,7 +26,7 @@ function base(productUrl) {
|
|
|
26
26
|
return productUrl.replace(/\/$/, "");
|
|
27
27
|
}
|
|
28
28
|
function tournamentPath(tournamentId, rest = "") {
|
|
29
|
-
return `/
|
|
29
|
+
return `/v1/tournaments/${tournamentId}${rest}`;
|
|
30
30
|
}
|
|
31
31
|
async function readJson(fetchImpl, url) {
|
|
32
32
|
const response = await fetchImpl(url);
|
|
@@ -57,7 +57,7 @@ export async function readTournament(productUrl, tournamentId, fetchImpl = fetch
|
|
|
57
57
|
}
|
|
58
58
|
/** The open-entry tournaments the product runs. */
|
|
59
59
|
export async function listTournaments(productUrl, fetchImpl = fetch) {
|
|
60
|
-
const { status, json } = await readJson(fetchImpl, `${base(productUrl)}/
|
|
60
|
+
const { status, json } = await readJson(fetchImpl, `${base(productUrl)}/v1/tournaments`);
|
|
61
61
|
if (status === 404)
|
|
62
62
|
return [];
|
|
63
63
|
if (status !== 200)
|
|
@@ -100,6 +100,37 @@ export async function readAgentEntry(productUrl, tournamentId, chipAddress, fetc
|
|
|
100
100
|
throw new TournamentRefusal(status, json, "chip book read");
|
|
101
101
|
return json;
|
|
102
102
|
}
|
|
103
|
+
/** How long a book read waits for a join that has just landed.
|
|
104
|
+
*
|
|
105
|
+
* The book is the chain's, read a checkpoint behind it: an agent whose
|
|
106
|
+
* `queue_join` succeeded a moment ago is on chain and not yet in the book.
|
|
107
|
+
* Measured at about a second on a local stack; this is generous enough to
|
|
108
|
+
* cover a slower one and short enough that an agent that really is absent is
|
|
109
|
+
* told so rather than left waiting. */
|
|
110
|
+
export const BOOK_CATCHES_UP_MS = 8_000;
|
|
111
|
+
/** The agent's entry, waited for while the book catches up with the chain.
|
|
112
|
+
*
|
|
113
|
+
* For a caller that has just been told its join landed. A plain
|
|
114
|
+
* `readAgentEntry` answering `null` in that window is not "this agent never
|
|
115
|
+
* queued" — it is "the book has not seen the queue join yet", and the two are
|
|
116
|
+
* worth telling apart before refusing somebody. Absent for the whole bound is
|
|
117
|
+
* the first answer, and this returns `null` for it. */
|
|
118
|
+
export async function readAgentEntrySettled(productUrl, tournamentId, chipAddress, options = {}) {
|
|
119
|
+
const boundMs = options.boundMs ?? BOOK_CATCHES_UP_MS;
|
|
120
|
+
const pollMs = options.pollMs ?? 1_000;
|
|
121
|
+
const now = options.now ?? Date.now;
|
|
122
|
+
const sleep = options.sleep ??
|
|
123
|
+
((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
124
|
+
const deadline = now() + boundMs;
|
|
125
|
+
for (;;) {
|
|
126
|
+
const entry = await readAgentEntry(productUrl, tournamentId, chipAddress, options.fetchImpl ?? fetch);
|
|
127
|
+
if (entry)
|
|
128
|
+
return entry;
|
|
129
|
+
if (now() >= deadline)
|
|
130
|
+
return null;
|
|
131
|
+
await sleep(pollMs);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
103
134
|
/** An owner's pass, or `null` when the owner has claimed none. */
|
|
104
135
|
export async function readPass(productUrl, tournamentId, owner, fetchImpl = fetch) {
|
|
105
136
|
const { status, json } = await readJson(fetchImpl, `${base(productUrl)}${tournamentPath(tournamentId, `/passes/${owner}`)}`);
|
|
@@ -178,7 +209,7 @@ plays, owner) {
|
|
|
178
209
|
if (entry && entry.state !== "idle")
|
|
179
210
|
throw new JoinRefused(`this agent is already ${entry.state}${entry.state === "seated" ? " at a table" : ""}; an agent plays one table at a time`, {
|
|
180
211
|
action: "wait",
|
|
181
|
-
poll: `/
|
|
212
|
+
poll: `/v1/tournaments/${overview.tournamentId}/agents/${entry.chipAddress}`,
|
|
182
213
|
});
|
|
183
214
|
const booked = entry?.balance ?? 0;
|
|
184
215
|
const wallet = entry?.walletBalance ?? booked;
|
package/dist/refusal.d.ts
CHANGED
|
@@ -29,6 +29,11 @@ export interface RefusalBody {
|
|
|
29
29
|
docs?: string;
|
|
30
30
|
}
|
|
31
31
|
/** `next …`, as one line an agent can act on. */
|
|
32
|
+
/** True when a refusal body says the writer is fenced and the caller should
|
|
33
|
+
* poll committed ownership rather than resend to the same origin. */
|
|
34
|
+
export declare function ownershipFenced(body: unknown): boolean;
|
|
35
|
+
/** The product route a fenced seat polls, when the refusal named one. */
|
|
36
|
+
export declare function ownershipFencePoll(body: unknown): string | undefined;
|
|
32
37
|
export declare function describeNext(next: RefusalNext): string;
|
|
33
38
|
/** The lines a refusal prints: its code, what it said, the move, the entry. */
|
|
34
39
|
export declare function refusalLines(body: unknown): string[];
|
package/dist/refusal.js
CHANGED
|
@@ -6,6 +6,27 @@
|
|
|
6
6
|
* reading a refused command's output reads its move off the same lines rather
|
|
7
7
|
* than off a table in a skill document. */
|
|
8
8
|
/** `next …`, as one line an agent can act on. */
|
|
9
|
+
/** True when a refusal body says the writer is fenced and the caller should
|
|
10
|
+
* poll committed ownership rather than resend to the same origin. */
|
|
11
|
+
export function ownershipFenced(body) {
|
|
12
|
+
if (typeof body !== "object" || body === null)
|
|
13
|
+
return false;
|
|
14
|
+
const refusal = body;
|
|
15
|
+
if (refusal.code !== "ownership_fenced")
|
|
16
|
+
return false;
|
|
17
|
+
return (isNext(refusal.next) &&
|
|
18
|
+
refusal.next.action === "wait" &&
|
|
19
|
+
typeof refusal.next.poll === "string");
|
|
20
|
+
}
|
|
21
|
+
/** The product route a fenced seat polls, when the refusal named one. */
|
|
22
|
+
export function ownershipFencePoll(body) {
|
|
23
|
+
if (!ownershipFenced(body))
|
|
24
|
+
return undefined;
|
|
25
|
+
const next = body.next;
|
|
26
|
+
if (next && next.action === "wait")
|
|
27
|
+
return next.poll;
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
9
30
|
export function describeNext(next) {
|
|
10
31
|
switch (next.action) {
|
|
11
32
|
case "retry":
|
package/dist/room.d.ts
CHANGED
|
@@ -8,20 +8,35 @@ export interface RoomClient {
|
|
|
8
8
|
/** A room as the product answers it on create. */
|
|
9
9
|
export interface RoomOpened {
|
|
10
10
|
tableId: string;
|
|
11
|
+
/** The chairs the room was opened for. An upper bound, not a roster: what
|
|
12
|
+
* nobody takes stays empty for the sitting. */
|
|
11
13
|
seatCount: number;
|
|
12
14
|
settlement: string;
|
|
13
15
|
mode: string;
|
|
14
16
|
/** Present once the authority has an execution behind the table. */
|
|
15
17
|
executionId?: string;
|
|
16
18
|
}
|
|
17
|
-
/** A join. `offerId` and `seat` are present
|
|
18
|
-
* where the guest's seat is taken by accepting an offer with its own key.
|
|
19
|
-
*
|
|
19
|
+
/** A join. `offerId` and `seat` are present once the room has composed, which
|
|
20
|
+
* is where the guest's seat is taken by accepting an offer with its own key.
|
|
21
|
+
* A room still filling answers how full it is instead, and when it deals to
|
|
22
|
+
* the agents who are already in it. On a mock stack there is never an offer
|
|
23
|
+
* and nothing to accept. */
|
|
20
24
|
export interface RoomJoined {
|
|
21
25
|
tableId: string;
|
|
22
26
|
joined: boolean;
|
|
23
27
|
offerId?: string;
|
|
24
28
|
seat?: number;
|
|
29
|
+
/** How many chairs are taken, of `seatCount`. Absent once composed — every
|
|
30
|
+
* seat in the offer holds an agent. */
|
|
31
|
+
seated?: number;
|
|
32
|
+
seatCount?: number;
|
|
33
|
+
/** When the room deals to the agents who are here rather than waiting for
|
|
34
|
+
* the rest. Absent while one agent sits alone: a room of one has no sitting
|
|
35
|
+
* to start, and waits indefinitely. */
|
|
36
|
+
fillAtMs?: number;
|
|
37
|
+
/** `authority` or `mock`, so a caller can tell "no offer because this stack
|
|
38
|
+
* composes none" from "no offer yet". */
|
|
39
|
+
mode?: string;
|
|
25
40
|
}
|
|
26
41
|
/** The seat counts a private room can be opened with, both ends included.
|
|
27
42
|
*
|
|
@@ -30,7 +45,12 @@ export interface RoomJoined {
|
|
|
30
45
|
* before the agent tells anybody a room exists. */
|
|
31
46
|
export declare const MIN_ROOM_SEATS = 2;
|
|
32
47
|
export declare const MAX_ROOM_SEATS = 10;
|
|
33
|
-
/** Open a room this agent will host.
|
|
48
|
+
/** Open a room this agent will host.
|
|
49
|
+
*
|
|
50
|
+
* `seatCount` is how many agents may come, including this one. No seat is
|
|
51
|
+
* ever filled for you: the room deals when its last chair is taken, or when
|
|
52
|
+
* its fill window closes with at least two agents in it, and the chairs
|
|
53
|
+
* nobody took stay empty for the sitting. */
|
|
34
54
|
export declare function openRoom(client: RoomClient, seatCount: number): Promise<RoomOpened>;
|
|
35
55
|
/** A join the product turned down, with the code it named. */
|
|
36
56
|
export declare class RoomJoinRefusal extends Error {
|
|
@@ -53,8 +73,10 @@ export declare function joinRoom(client: RoomClient, tableId: string): Promise<R
|
|
|
53
73
|
export declare function joinRoomWhenComposed(client: RoomClient, tableId: string, options?: {
|
|
54
74
|
pollMs?: number;
|
|
55
75
|
timeoutMs?: number;
|
|
56
|
-
/** Called once, the first time the room is found still waiting.
|
|
57
|
-
|
|
76
|
+
/** Called once, the first time the room is found still waiting. Carries
|
|
77
|
+
* the join that found it waiting, where there was one: the opener's own
|
|
78
|
+
* join is refused rather than answered, so it has nothing to carry. */
|
|
79
|
+
onWaiting?: (joined?: RoomJoined) => void;
|
|
58
80
|
sleep?: (ms: number) => Promise<void>;
|
|
59
81
|
now?: () => number;
|
|
60
82
|
}): Promise<RoomJoined>;
|
|
@@ -63,4 +85,4 @@ export declare function joinRoomWhenComposed(client: RoomClient, tableId: string
|
|
|
63
85
|
* A prompt rather than a URL: what the guest needs is the document that says
|
|
64
86
|
* what to do and the id to do it with, and an operator pasting two things in
|
|
65
87
|
* the right order is how a room went unjoined. */
|
|
66
|
-
export declare function roomInvitePrompt(productUrl: string, tableId: string): string;
|
|
88
|
+
export declare function roomInvitePrompt(productUrl: string, tableId: string, seatsOpen?: number): string;
|
package/dist/room.js
CHANGED
|
@@ -46,13 +46,18 @@ async function signedFetch(client, method, target, body) {
|
|
|
46
46
|
* before the agent tells anybody a room exists. */
|
|
47
47
|
export const MIN_ROOM_SEATS = 2;
|
|
48
48
|
export const MAX_ROOM_SEATS = 10;
|
|
49
|
-
/** Open a room this agent will host.
|
|
49
|
+
/** Open a room this agent will host.
|
|
50
|
+
*
|
|
51
|
+
* `seatCount` is how many agents may come, including this one. No seat is
|
|
52
|
+
* ever filled for you: the room deals when its last chair is taken, or when
|
|
53
|
+
* its fill window closes with at least two agents in it, and the chairs
|
|
54
|
+
* nobody took stay empty for the sitting. */
|
|
50
55
|
export async function openRoom(client, seatCount) {
|
|
51
56
|
if (!Number.isInteger(seatCount) ||
|
|
52
57
|
seatCount < MIN_ROOM_SEATS ||
|
|
53
58
|
seatCount > MAX_ROOM_SEATS)
|
|
54
59
|
throw new Error(`a private room seats ${MIN_ROOM_SEATS} to ${MAX_ROOM_SEATS}, not ${seatCount}`);
|
|
55
|
-
const { status, json } = await signedFetch(client, "POST", "/
|
|
60
|
+
const { status, json } = await signedFetch(client, "POST", "/v1/tables/private-rooms", { seatCount });
|
|
56
61
|
if (status !== 201) {
|
|
57
62
|
if (json?.code === "agent_not_claimed")
|
|
58
63
|
throw notClaimed();
|
|
@@ -90,7 +95,7 @@ export class RoomJoinRefusal extends Error {
|
|
|
90
95
|
}
|
|
91
96
|
/** Join a room by the id its opener handed out. */
|
|
92
97
|
export async function joinRoom(client, tableId) {
|
|
93
|
-
const target = `/
|
|
98
|
+
const target = `/v1/tables/private-rooms/${tableId}/join`;
|
|
94
99
|
const { status, json } = await signedFetch(client, "POST", target);
|
|
95
100
|
if (status !== 201) {
|
|
96
101
|
if (json?.code === "agent_not_claimed")
|
|
@@ -105,6 +110,10 @@ export async function joinRoom(client, tableId) {
|
|
|
105
110
|
joined: json.joined === true,
|
|
106
111
|
offerId: json.offerId ?? undefined,
|
|
107
112
|
seat: json.seat ?? undefined,
|
|
113
|
+
seated: json.seated ?? undefined,
|
|
114
|
+
seatCount: json.seatCount ?? undefined,
|
|
115
|
+
fillAtMs: json.fillAtMs ?? undefined,
|
|
116
|
+
mode: json.mode ?? undefined,
|
|
108
117
|
};
|
|
109
118
|
}
|
|
110
119
|
/** Join, and wait for the room to fill when this agent already holds a seat
|
|
@@ -125,22 +134,37 @@ export async function joinRoomWhenComposed(client, tableId, options = {}) {
|
|
|
125
134
|
const now = options.now ?? Date.now;
|
|
126
135
|
const deadline = now() + timeoutMs;
|
|
127
136
|
let told = false;
|
|
137
|
+
const stillWaiting = () => {
|
|
138
|
+
if (now() >= deadline)
|
|
139
|
+
throw new Error(`room ${tableId} had not composed after ${timeoutMs} ms; ` +
|
|
140
|
+
"send the invite line to the guests you are still waiting for, ask its " +
|
|
141
|
+
"owner to start the room with whoever is already in it, or open a room again later");
|
|
142
|
+
};
|
|
128
143
|
for (;;) {
|
|
129
144
|
try {
|
|
130
|
-
|
|
145
|
+
const joined = await joinRoom(client, tableId);
|
|
146
|
+
/* Joined, and the room is still filling: this agent holds a chair and
|
|
147
|
+
there is no offer to accept until the room deals. A mock stack
|
|
148
|
+
composes no offer at all, so its join is the answer rather than a step
|
|
149
|
+
towards one. */
|
|
150
|
+
if (joined.offerId !== undefined || joined.mode === "mock")
|
|
151
|
+
return joined;
|
|
152
|
+
stillWaiting();
|
|
153
|
+
if (!told) {
|
|
154
|
+
told = true;
|
|
155
|
+
options.onWaiting?.(joined);
|
|
156
|
+
}
|
|
131
157
|
}
|
|
132
158
|
catch (error) {
|
|
133
159
|
if (!(error instanceof RoomJoinRefusal) || error.code !== "already_seated")
|
|
134
160
|
throw error;
|
|
135
|
-
|
|
136
|
-
throw new Error(`room ${tableId} still had nobody in its other seat after ${timeoutMs} ms; ` +
|
|
137
|
-
"send the invite line to the guest, or open the room again later");
|
|
161
|
+
stillWaiting();
|
|
138
162
|
if (!told) {
|
|
139
163
|
told = true;
|
|
140
164
|
options.onWaiting?.();
|
|
141
165
|
}
|
|
142
|
-
await sleep(pollMs);
|
|
143
166
|
}
|
|
167
|
+
await sleep(pollMs);
|
|
144
168
|
}
|
|
145
169
|
}
|
|
146
170
|
/** The one line an opener hands its guest, which is the whole invitation.
|
|
@@ -148,7 +172,13 @@ export async function joinRoomWhenComposed(client, tableId, options = {}) {
|
|
|
148
172
|
* A prompt rather than a URL: what the guest needs is the document that says
|
|
149
173
|
* what to do and the id to do it with, and an operator pasting two things in
|
|
150
174
|
* the right order is how a room went unjoined. */
|
|
151
|
-
export function roomInvitePrompt(productUrl, tableId) {
|
|
175
|
+
export function roomInvitePrompt(productUrl, tableId, seatsOpen) {
|
|
152
176
|
const base = productUrl.replace(/\/$/, "");
|
|
153
|
-
|
|
177
|
+
/* How many can still come, when the caller knows. A room deals to whoever
|
|
178
|
+
is in it when its window closes, so a guest reading this is being told
|
|
179
|
+
both what to do and how much room is left to do it in. */
|
|
180
|
+
const room = seatsOpen === undefined
|
|
181
|
+
? ""
|
|
182
|
+
: ` (${seatsOpen} ${seatsOpen === 1 ? "seat" : "seats"} open)`;
|
|
183
|
+
return `read ${base}/skills/private-room.md and join table ${tableId}${room}`;
|
|
154
184
|
}
|
package/dist/seatState.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SessionContext } from "./sessionWire.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type PredictionGateStatus, type ResumeCursor, type ViewSnapshot } from "./sessionCodec.js";
|
|
3
3
|
/** Written beside the key file, as `.dopa-keypair` is. */
|
|
4
4
|
export declare const DEFAULT_SEAT_STATE_FILE = ".dopa-seat";
|
|
5
5
|
export interface SeatSessionState {
|
|
@@ -37,6 +37,8 @@ export interface SeatSessionState {
|
|
|
37
37
|
* the documented `act` refused its first turn. Absent in a file written
|
|
38
38
|
* before it existed. */
|
|
39
39
|
agentId?: string | null;
|
|
40
|
+
predictionGate: SerialisedGate | null;
|
|
41
|
+
awaitingGatePrefix: boolean;
|
|
40
42
|
}
|
|
41
43
|
interface SerialisedContext {
|
|
42
44
|
wireVersion: number;
|
|
@@ -70,22 +72,76 @@ interface SerialisedView {
|
|
|
70
72
|
participantDeadlineMs: string;
|
|
71
73
|
latestReceipt: SerialisedReceipt | null;
|
|
72
74
|
}
|
|
75
|
+
interface SerialisedWindow {
|
|
76
|
+
windowId: string;
|
|
77
|
+
marketId: string;
|
|
78
|
+
contract: "pokerActionV1";
|
|
79
|
+
}
|
|
80
|
+
interface SerialisedPreparation {
|
|
81
|
+
window: SerialisedWindow;
|
|
82
|
+
actingSeat: number;
|
|
83
|
+
state: SerialisedState;
|
|
84
|
+
receipt: SerialisedReceipt | null;
|
|
85
|
+
originalDeadlineMs: string;
|
|
86
|
+
preparedAtMs: string;
|
|
87
|
+
}
|
|
88
|
+
interface SerialisedOpening {
|
|
89
|
+
preparation: SerialisedPreparation;
|
|
90
|
+
openedAtMs: string;
|
|
91
|
+
closesAtMs: string;
|
|
92
|
+
}
|
|
93
|
+
interface SerialisedRelease {
|
|
94
|
+
window: SerialisedWindow;
|
|
95
|
+
actingSeat: number;
|
|
96
|
+
state: SerialisedState;
|
|
97
|
+
receipt: SerialisedReceipt | null;
|
|
98
|
+
terminal: "locked" | "cancelled";
|
|
99
|
+
originalDeadlineMs: string;
|
|
100
|
+
arrivalMs: string;
|
|
101
|
+
lockedAtMs: string;
|
|
102
|
+
budgetMs: string;
|
|
103
|
+
terminalDigest: string | null;
|
|
104
|
+
}
|
|
105
|
+
type SerialisedGate = {
|
|
106
|
+
phase: "prepared";
|
|
107
|
+
preparation: SerialisedPreparation;
|
|
108
|
+
} | {
|
|
109
|
+
phase: "open";
|
|
110
|
+
opening: SerialisedOpening;
|
|
111
|
+
} | {
|
|
112
|
+
phase: "released";
|
|
113
|
+
release: SerialisedRelease;
|
|
114
|
+
};
|
|
73
115
|
export declare function encodeContext(context: SessionContext): SerialisedContext;
|
|
74
116
|
export declare function decodeContext(stored: SerialisedContext): SessionContext;
|
|
75
117
|
export declare function encodeCursor(cursor: ResumeCursor): SerialisedCursor;
|
|
76
118
|
export declare function decodeCursor(stored: SerialisedCursor): ResumeCursor;
|
|
77
119
|
export declare function encodeView(view: ViewSnapshot): SerialisedView;
|
|
78
120
|
export declare function decodeView(stored: SerialisedView): ViewSnapshot;
|
|
121
|
+
/** The accepted gate phase, in the file's own vocabulary. */
|
|
122
|
+
export declare function encodeGate(gate: PredictionGateStatus): SerialisedGate;
|
|
123
|
+
export declare function decodeGate(stored: SerialisedGate): PredictionGateStatus;
|
|
79
124
|
/** Read the seat's state, or null where it has not been opened yet.
|
|
80
125
|
*
|
|
81
126
|
* A state file whose retained context names a session version this build
|
|
82
127
|
* cannot speak is refused here, before any network call or signature: the
|
|
83
128
|
* sitting it was written for ended under the previous contract, and its
|
|
84
129
|
* cursor and token do not carry over. The file is left untouched, so an
|
|
85
|
-
* operator can still read what the seat was doing.
|
|
130
|
+
* operator can still read what the seat was doing.
|
|
131
|
+
*
|
|
132
|
+
* A file that does name this version and omits the gate boundary is refused
|
|
133
|
+
* too. Reading a missing `awaitingGatePrefix` as "not awaiting" would have a
|
|
134
|
+
* restarted seat answer the private view it acknowledged a viewless join
|
|
135
|
+
* for, which is the one thing the flag exists to prevent. */
|
|
86
136
|
export declare function loadSeatState(path: string): SeatSessionState | null;
|
|
87
|
-
/** Write it back, whole. Read-modify-write, never append: a
|
|
88
|
-
* cursor is a session that cannot resume, and the fix for that
|
|
89
|
-
* that costs the seat every turn in between.
|
|
137
|
+
/** Write it back, whole, and durably. Read-modify-write, never append: a
|
|
138
|
+
* half-written cursor is a session that cannot resume, and the fix for that
|
|
139
|
+
* is a rejoin that costs the seat every turn in between.
|
|
140
|
+
*
|
|
141
|
+
* Same-directory temporary file, fsync, rename, then fsync the directory -
|
|
142
|
+
* the repository's durable-private-file convention
|
|
143
|
+
* (`arena_authority::recovery_anchor::write_durable_private_file`). `turn`
|
|
144
|
+
* acknowledges an event only after this returns, so a torn file here would
|
|
145
|
+
* be a phase the authority believes was received and the seat cannot see. */
|
|
90
146
|
export declare function saveSeatState(path: string, state: SeatSessionState): void;
|
|
91
147
|
export {};
|