@onlyworlds/sdk 4.27.1 → 4.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ earlier history lives in git log only.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.28.0] — 2026-10-16
9
+
10
+ ### Added
11
+ - **Seats for your own AI** (keel #355, D106), on an account token (`ow_a_`):
12
+ - `createSeat(worldId, { name, role?, character? })`: a seat for your own AI in a world you own or
13
+ belong to, with its own write key and `ow_s_` secret and no join link. You answer for it. `role` is at
14
+ most your own; `character` is `new` (default), `none`, or `mine` (it plays the Character you play).
15
+ The answer (`OwSeatCreated`) carries `key`, `pin` and the `.env` lines in `env`, shown once.
16
+ - `listSeats(worldId)`: the seats you can see (yours, or every seat for the world's owner), as `OwSeat`
17
+ rows with their `sponsor`, no keys.
18
+ - `removeSeat(worldId, seatId)`: its sponsor or the world's owner removes it; its key and secret stop.
19
+ - No Idempotency-Key on `createSeat`, by keel's rule (a replay would keep the key). A lost answer:
20
+ `listSeats` shows the seat, `removeSeat` it and make another.
21
+
22
+ ## [4.27.2] — 2026-10-16
23
+
24
+ ### Fixed
25
+ - **`bulk` mints an id for each element that has none, as `create` does.** It sent id-less elements
26
+ bare and keel minted them. With an `idempotencyKey`, each id is now derived from the key and the
27
+ element's position in `items` (the ids the Python package mints), so the same key and items send the
28
+ same body from either client, and a resend after keel's 24-hour replay window rewrites the same
29
+ elements instead of creating new ones. Without a key, a fresh UUIDv7 each. Ids you pass are kept, and
30
+ your items are not changed. Found by Quillon's parity audit of the Unity SDK (h37 #5794).
31
+
8
32
  ## [4.27.1] — 2026-10-16
9
33
 
10
34
  ### Fixed
package/dist/index.d.ts CHANGED
@@ -2259,6 +2259,49 @@ interface OwCreatedWorldLink extends OwWorldLink {
2259
2259
  /** The one line to send: who invites, to which world, and the link. */
2260
2260
  line: string;
2261
2261
  }
2262
+ /** One live agent seat, as GET /account/worlds/{id}/seats lists it (keel #355, D106). No key or secret. */
2263
+ interface OwSeat {
2264
+ /** The seat's membership id: removeSeat takes it, and GET /members names the seat by it. */
2265
+ id: string;
2266
+ /** The AI's own name in the world. */
2267
+ name: string;
2268
+ role: OwMemberRole;
2269
+ /** Always `agent`: a seat has no account of its own. */
2270
+ kind: 'agent';
2271
+ /** The Character the seat plays, or null. With `character: 'mine'` it is its sponsor's own: two rows, one Character. */
2272
+ character: string | null;
2273
+ /** Who answers for the seat: a username when they are the world's owner or a member; null for a seat from an email invite, or a sponsor who left. */
2274
+ sponsor: string | null;
2275
+ /** ISO 8601, UTC. */
2276
+ joined_at: string | null;
2277
+ /** The latest use of the seat's key, or null. */
2278
+ last_active: string | null;
2279
+ [k: string]: unknown;
2280
+ }
2281
+ /** POST /account/worlds/{id}/seats's answer: the seat, its key and secret, shown in this answer only. */
2282
+ interface OwSeatCreated {
2283
+ world: {
2284
+ id: string;
2285
+ name: string;
2286
+ [k: string]: unknown;
2287
+ };
2288
+ seat: {
2289
+ id: string;
2290
+ name: string;
2291
+ role: OwMemberRole;
2292
+ kind: 'agent';
2293
+ [k: string]: unknown;
2294
+ };
2295
+ /** The Character the seat plays, or null for none. */
2296
+ character: string | null;
2297
+ /** The seat's write key (`API-Key`). */
2298
+ key: string;
2299
+ /** The seat's `ow_s_` secret, sent as `API-Pin` where a person sends their PIN. */
2300
+ pin: string;
2301
+ /** The lines of a `.env` file for the AI: OW_API_KEY, OW_API_PIN, OW_WORLD, OW_CHARACTER when it plays one, OW_API_BASE. */
2302
+ env: string;
2303
+ [k: string]: unknown;
2304
+ }
2262
2305
  /** POST /account/join: the new membership (the same row as accepting an email invite). */
2263
2306
  interface OwJoinedMembership {
2264
2307
  /** The membership's id. */
@@ -2610,6 +2653,10 @@ declare class OwV2Client {
2610
2653
  * After an atomic:true request with errors:true NOTHING was written, yet the slots of
2611
2654
  * the items that would have succeeded still say 201 with the ids and timestamps they
2612
2655
  * would have had (keel's "counterfactual 201s"): do not record those ids as created.
2656
+ * An element without an id gets one, as `create` mints it, by its position in `items`: with an
2657
+ * `idempotencyKey`, derived from the key and the position (the ids the Python package mints),
2658
+ * so a retry with the same key and items sends the same body; without one, a
2659
+ * fresh UUIDv7. Ids you supply are kept.
2613
2660
  */
2614
2661
  bulk(items: OwBulkItem[], opts?: {
2615
2662
  atomic?: boolean;
@@ -2759,6 +2806,33 @@ declare class OwV2Client {
2759
2806
  * createWorldLink. Needs the client's `ow_a_` token.
2760
2807
  */
2761
2808
  revokeWorldLink(worldId: string, linkId: string): Promise<void>;
2809
+ /**
2810
+ * POST /account/worlds/{worldId}/seats -- a seat for your own AI in a world you own or are a
2811
+ * member of: its own membership, write key and `ow_s_` secret, no join link. You answer for
2812
+ * it, and its writes are marked as an agent's. `name` is required (1 to 80 characters);
2813
+ * `role` is at most your own (the owner may give any; default contributor, or your own role
2814
+ * when that is lower); `character` is `new` (default: a Character of its own), `none`, or
2815
+ * `mine` (members only: it plays the Character you play). 201 with `key`, `pin` and the
2816
+ * `.env` lines in `env`, shown in this answer only. Sends no Idempotency-Key, by keel's rule:
2817
+ * a replay would have to keep the key. If the answer is lost, listSeats shows the seat without
2818
+ * its key: remove it with removeSeat and make another. Needs the client's `ow_a_` token.
2819
+ */
2820
+ createSeat(worldId: string, opts: {
2821
+ name: string;
2822
+ role?: OwMemberRole;
2823
+ character?: 'new' | 'none' | 'mine';
2824
+ }): Promise<OwSeatCreated>;
2825
+ /**
2826
+ * GET /account/worlds/{worldId}/seats -- the live agent seats you can see: your own AI's, or,
2827
+ * as the world's owner, every seat. No keys or secrets. Needs the client's `ow_a_` token.
2828
+ */
2829
+ listSeats(worldId: string): Promise<OwSeat[]>;
2830
+ /**
2831
+ * DELETE /account/worlds/{worldId}/seats/{seatId} -- remove an agent seat: its sponsor or the
2832
+ * world's owner may. Its key and secret stop at once; a Character it made that nobody touched
2833
+ * goes with it, any other stays. Needs the client's `ow_a_` token.
2834
+ */
2835
+ removeSeat(worldId: string, seatId: string): Promise<void>;
2762
2836
  /**
2763
2837
  * POST /account/ask -- a question about a world the account owns or is in, answered by
2764
2838
  * keel's AI proxy from `context` only (build it with buildAskContext from the elements
@@ -3760,4 +3834,4 @@ declare class TokenResource {
3760
3834
  getEncryptionInfo(): Promise<EncryptionInfo>;
3761
3835
  }
3762
3836
 
3763
- export { AGENT_DOORS, ASK_LIMITS, type AccessKeyResponse, CALENDAR_FIELD, CALENDAR_SUPERTYPE, type CalendarConfig, type CalendarEra, type CalendarIntercalary, type CalendarMonths, type CalendarWeek, ELEMENT_FAMILIES, ELEMENT_ICONS, ELEMENT_LABELS, ELEMENT_SECTIONS, ELEMENT_TYPES, type ElementFamily, type ElementType, type EncryptionInfo, type EraDomain, FAMILY_COLORS, FAMILY_ORDER, FIELD_SCHEMA, type FieldInfo, type FieldType, GameTier, type ListParams, ONLYWORLDS_VERSION, type OwAllowance, OwApiError, type OwAskAnswer, type OwAskContext, type OwAuthErrorCode, type OwBulkItem, type OwBulkItemResult, type OwBulkResponse, type OwCalendarElement, type OwChange, type OwChangeDelete, type OwChangeUpsert, type OwChangesPage, type OwClientConfig, type OwCreatedWorldLink, type OwElement, type OwElementBase, type OwErrorBody, type OwJoinPreview, type OwJoinedMembership, type OwKeyKind, type OwLinkEdit, type OwLookupElement, type OwLookupHit, type OwLookupLink, type OwLookupOptions, type OwLookupReason, type OwMe, type OwMediaTicket, type OwMember, type OwMemberRole, OwNetworkError, type OwPage, type OwPhotoOptions, type OwPhotoPlan, OwPreconditionFailedError, type OwPreparedPhoto, type OwReadWorldFile, type OwReadWorldFileOptions, type OwRemovalTicket, type OwRemovedImage, type OwRenderOptions, type OwSortAddToRow, type OwSortAnswer, type OwSortAskRow, type OwSortLinkRow, type OwSortMakeRow, type OwSortNote, type OwSortRef, type OwSortRow, type OwUploadedImage, OwV2Client, type OwWorldFileEntry, OwWorldFileError, type OwWorldFileReason, type OwWorldIndex, type OwWorldLink, type OwWorldMeta, type OwWriteDoor, PHOTO_MAX_BYTES, PHOTO_TYPES, type ParseError, type ParseOptions, type ParseResult, type RevokeAllSessionsResponse, type RevokeSessionResponse, SPATIAL_TYPES, type SectionInfo, type StepBreakdown, type TokenConsumeParams, type TokenConsumeResponse, TokenResource, type TokenStatus, type TokenTransport, WORLD_FILE_MAX_BYTES, WORLD_FILE_MAX_ENTRIES, type WorldCalendar, absYearForEraYear, absoluteYear, approxYearLength, breakdownStep, buildAskContext, buildWorldIndex, detectKeyKind, displayYear, elementColor, eraForStep, errorFromResponse, familyOf, floorDiv, floorMod, formatDuration, formatMidLabel, formatStep, formatYearLabel, getElementIcon, getElementLabel, isAgentDoor, isDemoKey, isLeapYear, isParseError, kindCanWrite, lookupWorld, monthStartSteps, parseDate, parseErrorEnvelope, pinExpectation, planPhoto, preparePhoto, readCalendarConfig, readCalendarValue, readWorldFile, renderElement, resolveCalendarLens, resolveEraDomains, validateCalendarConfig, weekdayForStep, worldCalendars, writeWorldFile, yearLength, yearStartStep };
3837
+ export { AGENT_DOORS, ASK_LIMITS, type AccessKeyResponse, CALENDAR_FIELD, CALENDAR_SUPERTYPE, type CalendarConfig, type CalendarEra, type CalendarIntercalary, type CalendarMonths, type CalendarWeek, ELEMENT_FAMILIES, ELEMENT_ICONS, ELEMENT_LABELS, ELEMENT_SECTIONS, ELEMENT_TYPES, type ElementFamily, type ElementType, type EncryptionInfo, type EraDomain, FAMILY_COLORS, FAMILY_ORDER, FIELD_SCHEMA, type FieldInfo, type FieldType, GameTier, type ListParams, ONLYWORLDS_VERSION, type OwAllowance, OwApiError, type OwAskAnswer, type OwAskContext, type OwAuthErrorCode, type OwBulkItem, type OwBulkItemResult, type OwBulkResponse, type OwCalendarElement, type OwChange, type OwChangeDelete, type OwChangeUpsert, type OwChangesPage, type OwClientConfig, type OwCreatedWorldLink, type OwElement, type OwElementBase, type OwErrorBody, type OwJoinPreview, type OwJoinedMembership, type OwKeyKind, type OwLinkEdit, type OwLookupElement, type OwLookupHit, type OwLookupLink, type OwLookupOptions, type OwLookupReason, type OwMe, type OwMediaTicket, type OwMember, type OwMemberRole, OwNetworkError, type OwPage, type OwPhotoOptions, type OwPhotoPlan, OwPreconditionFailedError, type OwPreparedPhoto, type OwReadWorldFile, type OwReadWorldFileOptions, type OwRemovalTicket, type OwRemovedImage, type OwRenderOptions, type OwSeat, type OwSeatCreated, type OwSortAddToRow, type OwSortAnswer, type OwSortAskRow, type OwSortLinkRow, type OwSortMakeRow, type OwSortNote, type OwSortRef, type OwSortRow, type OwUploadedImage, OwV2Client, type OwWorldFileEntry, OwWorldFileError, type OwWorldFileReason, type OwWorldIndex, type OwWorldLink, type OwWorldMeta, type OwWriteDoor, PHOTO_MAX_BYTES, PHOTO_TYPES, type ParseError, type ParseOptions, type ParseResult, type RevokeAllSessionsResponse, type RevokeSessionResponse, SPATIAL_TYPES, type SectionInfo, type StepBreakdown, type TokenConsumeParams, type TokenConsumeResponse, TokenResource, type TokenStatus, type TokenTransport, WORLD_FILE_MAX_BYTES, WORLD_FILE_MAX_ENTRIES, type WorldCalendar, absYearForEraYear, absoluteYear, approxYearLength, breakdownStep, buildAskContext, buildWorldIndex, detectKeyKind, displayYear, elementColor, eraForStep, errorFromResponse, familyOf, floorDiv, floorMod, formatDuration, formatMidLabel, formatStep, formatYearLabel, getElementIcon, getElementLabel, isAgentDoor, isDemoKey, isLeapYear, isParseError, kindCanWrite, lookupWorld, monthStartSteps, parseDate, parseErrorEnvelope, pinExpectation, planPhoto, preparePhoto, readCalendarConfig, readCalendarValue, readWorldFile, renderElement, resolveCalendarLens, resolveEraDomains, validateCalendarConfig, weekdayForStep, worldCalendars, writeWorldFile, yearLength, yearStartStep };
package/dist/index.js CHANGED
@@ -399,10 +399,20 @@ var OwV2Client = class {
399
399
  * After an atomic:true request with errors:true NOTHING was written, yet the slots of
400
400
  * the items that would have succeeded still say 201 with the ids and timestamps they
401
401
  * would have had (keel's "counterfactual 201s"): do not record those ids as created.
402
+ * An element without an id gets one, as `create` mints it, by its position in `items`: with an
403
+ * `idempotencyKey`, derived from the key and the position (the ids the Python package mints),
404
+ * so a retry with the same key and items sends the same body; without one, a
405
+ * fresh UUIDv7. Ids you supply are kept.
402
406
  */
403
407
  async bulk(items, opts = {}) {
404
408
  const body = {
405
- items: items.map((it) => ({ type: it.type, element: sanitizePayload(it.element) })),
409
+ items: items.map((it, index) => {
410
+ const element = sanitizePayload(it.element);
411
+ if (element.id === void 0 || element.id === null || element.id === "") {
412
+ element.id = opts.idempotencyKey ? keyedId(opts.idempotencyKey, index) : mintUuid();
413
+ }
414
+ return { type: it.type, element };
415
+ }),
406
416
  atomic: opts.atomic ?? false
407
417
  };
408
418
  return this.request("POST", "/bulk/", {
@@ -616,6 +626,49 @@ var OwV2Client = class {
616
626
  }
617
627
  await this.request("DELETE", `/account/worlds/${encodeURIComponent(worldId)}/links/${encodeURIComponent(linkId)}`);
618
628
  }
629
+ // -- Seats for your own AI (keel #355, D106) ------------------------------
630
+ /**
631
+ * POST /account/worlds/{worldId}/seats -- a seat for your own AI in a world you own or are a
632
+ * member of: its own membership, write key and `ow_s_` secret, no join link. You answer for
633
+ * it, and its writes are marked as an agent's. `name` is required (1 to 80 characters);
634
+ * `role` is at most your own (the owner may give any; default contributor, or your own role
635
+ * when that is lower); `character` is `new` (default: a Character of its own), `none`, or
636
+ * `mine` (members only: it plays the Character you play). 201 with `key`, `pin` and the
637
+ * `.env` lines in `env`, shown in this answer only. Sends no Idempotency-Key, by keel's rule:
638
+ * a replay would have to keep the key. If the answer is lost, listSeats shows the seat without
639
+ * its key: remove it with removeSeat and make another. Needs the client's `ow_a_` token.
640
+ */
641
+ async createSeat(worldId, opts) {
642
+ if (this.keyKind !== "account") {
643
+ throw new TypeError("createSeat needs an account token (ow_a_): a seat is made by the person who answers for it");
644
+ }
645
+ const body = { name: opts.name };
646
+ if (opts.role !== void 0) body.role = opts.role;
647
+ if (opts.character !== void 0) body.character = opts.character;
648
+ return this.request("POST", `/account/worlds/${encodeURIComponent(worldId)}/seats`, { body });
649
+ }
650
+ /**
651
+ * GET /account/worlds/{worldId}/seats -- the live agent seats you can see: your own AI's, or,
652
+ * as the world's owner, every seat. No keys or secrets. Needs the client's `ow_a_` token.
653
+ */
654
+ async listSeats(worldId) {
655
+ if (this.keyKind !== "account") {
656
+ throw new TypeError("listSeats needs an account token (ow_a_)");
657
+ }
658
+ const page = await this.request("GET", `/account/worlds/${encodeURIComponent(worldId)}/seats`);
659
+ return page.data;
660
+ }
661
+ /**
662
+ * DELETE /account/worlds/{worldId}/seats/{seatId} -- remove an agent seat: its sponsor or the
663
+ * world's owner may. Its key and secret stop at once; a Character it made that nobody touched
664
+ * goes with it, any other stays. Needs the client's `ow_a_` token.
665
+ */
666
+ async removeSeat(worldId, seatId) {
667
+ if (this.keyKind !== "account") {
668
+ throw new TypeError("removeSeat needs an account token (ow_a_)");
669
+ }
670
+ await this.request("DELETE", `/account/worlds/${encodeURIComponent(worldId)}/seats/${encodeURIComponent(seatId)}`);
671
+ }
619
672
  // -- Ask your world (keel #159) -------------------------------------------
620
673
  /**
621
674
  * POST /account/ask -- a question about a world the account owns or is in, answered by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlyworlds/sdk",
3
- "version": "4.27.1",
3
+ "version": "4.28.0",
4
4
  "description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety",
5
5
  "type": "module",
6
6
  "exports": {