@xema/omni-protocol 0.1.77 → 0.1.79

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 CHANGED
@@ -126,11 +126,18 @@ const request = { reasonId, requestedAt } satisfies BreakRequest; // error: requ
126
126
  ## Building
127
127
 
128
128
  ```
129
- pnpm install
129
+ pnpm install --frozen-lockfile
130
+ pnpm check # clean build, type checks, tests, and packed-package checks
130
131
  pnpm build # emits dist/
131
132
  pnpm test # type-checks the tests, then runs them
133
+ pnpm check:package # verifies the current dist/ as a consumer would
132
134
  ```
133
135
 
136
+ CI runs the complete check on Node 20, 22, and 24 for pull requests (including drafts)
137
+ and pushes to main. Package checks create a temporary npm tarball, verify its contents,
138
+ and check JavaScript imports and TypeScript declarations for every exported entry point
139
+ from an isolated consumer. Publishing also runs `pnpm check` through `prepublishOnly`.
140
+
134
141
  ## The guide is authoritative
135
142
 
136
143
  Where `guide.md` and any code here disagree, the guide is right and the code is a defect. It has
package/dist/index.d.ts CHANGED
@@ -88,7 +88,8 @@ export interface BrowserAccess {
88
88
  }
89
89
  export interface PersonalBrowserCapability {
90
90
  access: BrowserAccess;
91
- accessAppliesTo?: "initial-url" | "all-navigation";
91
+ /** Which navigations the access policy is checked against: every redirect and navigation, or the starting URL alone. Stated, never defaulted. */
92
+ accessAppliesTo: "initial-url" | "all-navigation";
92
93
  }
93
94
  export type DialDestinations = "contacts-only" | "any-number";
94
95
  export interface DialCapability {
@@ -175,8 +176,9 @@ export interface Manifest<C extends Channel = Channel> {
175
176
  */
176
177
  runningStepReports?: true;
177
178
  /**
178
- * How long after an applied disposal -- `complete`, `transfer` `complete`, `lead-assist`
179
- * `take-over` -- the provider's `task-ended` is owed, in milliseconds. `applied` says the provider
179
+ * How long after an applied disposal -- `complete`, or a lead's `take-over` -- the provider's
180
+ * `task-ended` is owed, in milliseconds. A warm transfer's `complete` is not one: the agent's
181
+ * wrap runs after it as after any call. `applied` says the provider
180
182
  * has disposed of the task; the ending follows within this, or the host resyncs and shows the
181
183
  * task as unsettled. Stated per provider, since platforms settle at different speeds.
182
184
  */
@@ -548,8 +550,8 @@ export interface CustomCapability {
548
550
  control: "button" | "toggle" | "menu-item";
549
551
  label: string;
550
552
  placement: "primary" | "secondary" | "overflow";
551
- /** Where the control's work renders: inline in the workspace, or as a page of its own. Inline when absent. */
552
- render?: "inline" | "page";
553
+ /** Where the control's work renders: inline in the workspace, or as a page of its own. Stated, never defaulted. */
554
+ render: "inline" | "page";
553
555
  };
554
556
  /** What the agent supplies before the action runs; the values travel on the custom command. */
555
557
  prompt?: {
@@ -729,11 +731,12 @@ export declare const ON_CALL_ROLES: readonly ["party", "agent", "consulted", "co
729
731
  * stated so a snapshot says who is present without anyone having seen the outcome. Nobody ringing is
730
732
  * held. `label` names a destination -- a person, a queue -- not a phrase. At most one `party` and
731
733
  * one `consulted`: the consult commands name neither because there is exactly one. The `party`
732
- * carries a `stage` only while being dialled again on the same task -- a connect-back the host
733
- * placed, carrying its `dialId`, or a callback the platform places itself, carrying none --
734
- * `ringing` from the moment the dial is placed and `joined` on its answered outcome, so a host can
735
- * show a call being placed rather than assert one that is still ringing. A party with no stage is
736
- * on the call.
734
+ * carries a `stage` while being dialled -- by any host dial, a dialpad call, a preview Call or a
735
+ * connect-back, carrying its `dialId`, or by a callback the platform places itself, carrying none --
736
+ * `ringing` from the moment the dial is placed, `joined` on the publication that follows its
737
+ * answered outcome, and no stage from the next publication on, so a host can show a call being
738
+ * placed rather than assert one that is still ringing, and a stale copy carrying `joined` cannot
739
+ * pass as a connect-back. A party with no stage is on the call.
737
740
  */
738
741
  export type OnCall = {
739
742
  since: IsoTimestamp;
@@ -760,12 +763,16 @@ export type OnCall = {
760
763
  * The agent's request for a lead, from asking until the lead leaves or the request ends.
761
764
  * `requested` while nobody has joined; `joined`, with `leadId`, once somebody has.
762
765
  */
763
- export interface TaskLeadAssist {
764
- stage: "requested" | "joined";
765
- leadId?: UserId;
766
+ export type TaskLeadAssist = {
766
767
  note?: string;
767
768
  since: IsoTimestamp;
768
- }
769
+ } & ({
770
+ stage: "requested";
771
+ leadId?: never;
772
+ } | {
773
+ stage: "joined";
774
+ leadId: UserId;
775
+ });
769
776
  /**
770
777
  * On the lead's own task for a call they joined: which member asked, with their note. Its
771
778
  * presence is what makes `lead-assist` `take-over` and `leave` issuable.
@@ -864,7 +871,9 @@ export type Task<C extends Channel = Channel> = {
864
871
  * How this offer is accepted, stated on the pending task rather than the offer so a reconnect
865
872
  * snapshot says it too: an offer the host never received is not accepted on the person's behalf
866
873
  * for want of a word. Required while `pending` when Omni said it may auto-accept
867
- * (`autoAcceptTasks: true`), forbidden when it said not, and absent past `pending`.
874
+ * (`autoAcceptTasks: true`) or the work is the agent's own -- a dial, a connect-back, a join, a
875
+ * monitor, which then say `automatic` under either provisioning -- forbidden on routed work when
876
+ * Omni said not, and absent past `pending`.
868
877
  */
869
878
  acceptance?: AcceptanceMode;
870
879
  /**
@@ -892,19 +901,26 @@ export type Task<C extends Channel = Channel> = {
892
901
  media?: never;
893
902
  });
894
903
  /**
895
- * What the provider wants of Omni's acceptance policy for one offer. Present only where Omni was
896
- * willing to accept for the agent (`autoAcceptTasks: true`): `consent` is therefore always the
897
- * provider's requirement of an explicit acceptance, never Omni's own policy, which travels as an
898
- * absent field.
904
+ * What the provider wants of Omni's acceptance policy for one offer. On routed work, present only
905
+ * where Omni was willing to accept for the agent (`autoAcceptTasks: true`): `consent` is therefore
906
+ * always the provider's requirement of an explicit acceptance, never Omni's own policy, which
907
+ * travels as an absent field. Work the agent originated says `automatic` whatever the provisioning.
899
908
  */
900
909
  export type AcceptanceMode = "no-preference" | "consent" | "automatic";
901
910
  export type TaskOutcome = {
902
911
  type: "completed";
903
912
  by: "agent" | "provider";
904
- } | {
913
+ }
914
+ /** A cold transfer to a destination the agent chose: a directory item's id. */
915
+ | {
905
916
  type: "transferred";
906
917
  destinationId?: string;
907
918
  }
919
+ /** A lead took the call over: the agent's task ends and the lead's continues. Named by the lead, since a lead is not a directory item. */
920
+ | {
921
+ type: "taken-over";
922
+ leadId: UserId;
923
+ }
908
924
  /** Who called the work off: the agent declining, the provider withdrawing or re-routing, the party abandoning. */
909
925
  | {
910
926
  type: "cancelled";
@@ -1155,15 +1171,13 @@ export interface BreakState {
1155
1171
  retryAfterMs?: number;
1156
1172
  /** Not-ready codes this provider offers. Omitted when it defines none. */
1157
1173
  reasons?: BreakReason[];
1158
- /** Which reason the current break is on. Omitted when there is no break. */
1174
+ /** Which reason the current break is on, a published `BreakReason.id`. Omitted when there is no break; required on a break in effect or starting after the task where the provider publishes `reasons`, an imposed one included. */
1159
1175
  activeReasonId?: string;
1160
1176
  imposed?: ImposedBreak;
1161
1177
  }
1178
+ /** Capacity is a statement, not a request: it is taken, never refused. A provider that cannot carry the count allocates within what it can and says so on a `diagnostic`. */
1162
1179
  export type CapacityResult = {
1163
1180
  status: "applied";
1164
- } | {
1165
- status: "failed";
1166
- failure: ProtocolFailure;
1167
1181
  };
1168
1182
  /** Succeeding is not the outcome: `requested` says the provider holds it, not that it was granted. */
1169
1183
  export type BreakRequestResult = {
@@ -1191,13 +1205,15 @@ export type BreakEndResult = {
1191
1205
  status: "failed";
1192
1206
  failure: ProtocolFailure;
1193
1207
  };
1194
- export type TeamMemberAvailability = "ready" | "on-task" | "on-break" | "signed-out";
1208
+ /** `reserved`: signed in here, and the host holds this agent's capacity for another provider (`count: 0`, host-stopped); not receiving this provider's work. */
1209
+ export type TeamMemberAvailability = "ready" | "on-task" | "on-break" | "reserved" | "signed-out";
1195
1210
  export interface TeamMember {
1196
1211
  id: UserId;
1197
1212
  availability: TeamMemberAvailability;
1198
1213
  /** Omitted rather than invented: Omni renders it as a duration. */
1199
1214
  since?: IsoTimestamp;
1200
- break?: BreakApproval;
1215
+ /** A request in flight or a grant not yet in effect. `not-requested` is absence, and `in-effect` is `availability: "on-break"`. */
1216
+ break?: Extract<BreakApproval, "awaiting-decision" | "granted" | "starting-after-task">;
1201
1217
  }
1202
1218
  /** A member asking this lead to join their call. */
1203
1219
  export interface LeadRequest {
@@ -1273,9 +1289,12 @@ export type TeamBreakCommand = {
1273
1289
  } | {
1274
1290
  type: "policy";
1275
1291
  policy: "ask" | "auto-approve" | "suspended";
1276
- } | {
1292
+ }
1293
+ /** `reasonId` names a published `BreakReason.id`, required whenever the provider publishes reasons: the member's imposed break carries it as `activeReasonId`. */
1294
+ | {
1277
1295
  type: "place";
1278
1296
  memberId: UserId;
1297
+ reasonId?: string;
1279
1298
  reason?: string;
1280
1299
  } | {
1281
1300
  type: "release";
@@ -1291,7 +1310,7 @@ export interface TeamBreakCommandRequest {
1291
1310
  command: TeamBreakCommand;
1292
1311
  }
1293
1312
  /**
1294
- * The task's real-time audio as the provider holds it: `ready` while audio should be attached,
1313
+ * The task's real-time audio as the provider holds it: `started` while audio should be attached,
1295
1314
  * `ended` once primary handling's audio ended, and the field omitted while none should be. The
1296
1315
  * provider's word -- a desk attaches and renders audio from it, never from its own senses.
1297
1316
  */
@@ -1323,7 +1342,7 @@ export type OpenMediaResult = {
1323
1342
  */
1324
1343
  export type PreferenceId = "hold" | `skill:${string}`;
1325
1344
  /**
1326
- * Who stated a value as it stands: a level -- `person` among them -- or `provisioning`, the
1345
+ * Who stated a value as it stands: a level -- `person` among them -- or `provider`, the
1327
1346
  * protocol's own word for "no level has said anything and the provider's default applies".
1328
1347
  * Nothing is hidden for want of a row.
1329
1348
  */
@@ -1411,7 +1430,7 @@ export interface Snapshot<C extends Channel = Channel> {
1411
1430
  * tasks against it and needs no new signal when one ends. The agent is one person on several
1412
1431
  * providers, and the host divides their capacity among them: `0` is host-stopped -- the agent's
1413
1432
  * capacity is elsewhere for now, this provider allocates nothing and shows the member as
1414
- * `on-task` -- and is not a break, which is the agent not working at all. The host restates a
1433
+ * `reserved` -- and is not a break, which is the agent not working at all. The host restates a
1415
1434
  * count of one or more when this provider has the agent's capacity again.
1416
1435
  */
1417
1436
  export interface AgentCapacity {
package/dist/index.js CHANGED
@@ -180,11 +180,16 @@ export function handlingStepExpectsAPerson(step) {
180
180
  */
181
181
  export function sameCapabilities(a, b) {
182
182
  const modes = (team) => team?.monitorControl === undefined ? undefined : [...team.monitorControl].sort().join(",");
183
+ // Every field, so a capability added later cannot be missed here: `satisfies` pins the field
184
+ // lists to the types, and a new key is a compile error until it is compared.
185
+ const teamKeys = { breakControl: true, leadAssistControl: true, monitorControl: true, policyControl: true };
186
+ void { breaks: true, preferences: true, team: true };
187
+ const preferences = (list) => list === undefined ? undefined : list.map(p => [p.id, p.label, p.enabled, p.setBy, p.lockedBy ?? "", p.reason ?? ""].join("\u0000")).sort().join("\u0001");
183
188
  return a.breaks === b.breaks &&
184
189
  (a.team === undefined) === (b.team === undefined) &&
185
- a.team?.breakControl === b.team?.breakControl &&
186
- a.team?.leadAssistControl === b.team?.leadAssistControl &&
187
- modes(a.team) === modes(b.team);
190
+ Object.keys(teamKeys).every(key => key === "monitorControl" || a.team?.[key] === b.team?.[key]) &&
191
+ modes(a.team) === modes(b.team) &&
192
+ preferences(a.preferences) === preferences(b.preferences);
188
193
  }
189
194
  /**
190
195
  * The storage-profile key a reusing browser shares, or `undefined` where it shares nothing.
package/dist/testing.d.ts CHANGED
@@ -163,8 +163,12 @@ export declare class TaskStream {
163
163
  openCount(): number;
164
164
  /** Whether an offered task is a dial arriving -- the host's own call, a connect-back -- which counts against no capacity: an entry on its call carrying a dial. */
165
165
  static carriesDial(task: unknown): boolean;
166
- /** Whether the update shows the party being dialled again -- a connect-back, a platform's callback -- the one thing that brings a completing task back. */
167
- private static partyDialled;
166
+ /**
167
+ * Whether the update shows the party being dialled again -- a connect-back, a platform's callback --
168
+ * the one thing that brings a completing task back: the party ringing, or joined by a dial whose
169
+ * answered outcome this stream saw. A stale copy carrying `joined` from a dial long over is not that.
170
+ */
171
+ private partyDialled;
168
172
  /** The entries of a task's record, each by step and instant, or undefined where the task carries no record. */
169
173
  private static record;
170
174
  /** What a restated record lost of the one read before it: nothing, or the entries by step and instant. */