@xema/omni-protocol 0.1.25 → 0.1.27
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 +1 -1
- package/dist/index.d.ts +78 -72
- package/dist/index.js +7 -7
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +27 -27
- package/dist/validation.d.ts +10 -10
- package/dist/validation.js +97 -100
- package/guide.md +160 -150
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ clean result is read for what it covers and not for the whole contract; `assertR
|
|
|
65
65
|
subjects)` is the paired assertion.
|
|
66
66
|
|
|
67
67
|
```ts
|
|
68
|
-
const context = { protocolVersion: OMNI_PROTOCOL_VERSION,
|
|
68
|
+
const context = { protocolVersion: OMNI_PROTOCOL_VERSION, loginId: "session-1", host: stillHost(report) };
|
|
69
69
|
const result = await exerciseAdapter(adapter, context, { collectOnly: true });
|
|
70
70
|
expect(result.violations).toEqual([]);
|
|
71
71
|
expect(result.disconnectWasClean).toBe(true);
|
package/dist/index.d.ts
CHANGED
|
@@ -54,19 +54,19 @@ export interface ProtocolViolation {
|
|
|
54
54
|
message: string;
|
|
55
55
|
}
|
|
56
56
|
export type AuthenticationMethod = "browser-sso" | "credentials";
|
|
57
|
-
export interface
|
|
57
|
+
export interface BrowserAccess {
|
|
58
58
|
/** The decision when no list entry matches. */
|
|
59
59
|
mode: "allow-all" | "block-all";
|
|
60
60
|
allowList?: string[];
|
|
61
61
|
blockList?: string[];
|
|
62
62
|
}
|
|
63
63
|
export interface PersonalBrowserCapability {
|
|
64
|
-
access:
|
|
64
|
+
access: BrowserAccess;
|
|
65
65
|
accessPolicyScope?: "initial-url" | "all-navigation";
|
|
66
66
|
}
|
|
67
|
-
export type
|
|
67
|
+
export type DialDestinations = "contacts-only" | "any-number";
|
|
68
68
|
export interface DialCapability {
|
|
69
|
-
|
|
69
|
+
destinations: DialDestinations;
|
|
70
70
|
}
|
|
71
71
|
/** Every idle capability a provider may declare. Only voice may `dial`; the channel arm says so. */
|
|
72
72
|
export declare const IDLE_CAPABILITIES: readonly ["dial", "personalBrowser", "calendar", "contacts"];
|
|
@@ -111,8 +111,8 @@ export interface Manifest<C extends Channel = Channel> {
|
|
|
111
111
|
phaseLabels?: TaskPhaseLabels;
|
|
112
112
|
/** Keyed by `taskType`. An entry replaces the channel default outright rather than merging. */
|
|
113
113
|
taskTypePresentation?: Record<string, TaskTypePresentation>;
|
|
114
|
-
/** The organisation's whole ladder, stated outright, `person` included. Omitted for the typical four, `
|
|
115
|
-
|
|
114
|
+
/** The organisation's whole ladder, stated outright, `person` included. Omitted for the typical four, `DEFAULT_LEVELS`. */
|
|
115
|
+
orgLevels?: LevelDeclaration[];
|
|
116
116
|
}
|
|
117
117
|
export interface SecretStore {
|
|
118
118
|
get(key: string): Promise<string | undefined>;
|
|
@@ -121,8 +121,8 @@ export interface SecretStore {
|
|
|
121
121
|
}
|
|
122
122
|
export interface AuthenticationContext {
|
|
123
123
|
protocolVersion: number;
|
|
124
|
-
/** Omni's identity for this login. The same value arrives later as `ConnectContext.
|
|
125
|
-
|
|
124
|
+
/** Omni's identity for this login. The same value arrives later as `ConnectContext.loginId`. */
|
|
125
|
+
loginId: string;
|
|
126
126
|
/** Scoped to this provider's manifest id. */
|
|
127
127
|
secrets: SecretStore;
|
|
128
128
|
signal?: AbortSignal;
|
|
@@ -155,7 +155,7 @@ export interface TeamCapabilities {
|
|
|
155
155
|
* of who the agent is on this provider: the provider knows the roles and says so at sign-in.
|
|
156
156
|
* Current as of the latest `authenticated` state, never fixed for the login.
|
|
157
157
|
*/
|
|
158
|
-
export interface
|
|
158
|
+
export interface UserCapabilities {
|
|
159
159
|
/** This login may request a break. Requires the four break methods. */
|
|
160
160
|
breaks?: true;
|
|
161
161
|
/** The choices the team left to this person, with where each stands. Omitted when there are none. Requires `setPreference`. */
|
|
@@ -175,12 +175,12 @@ export type AuthenticationState = {
|
|
|
175
175
|
} | {
|
|
176
176
|
status: "authenticated";
|
|
177
177
|
identity: User;
|
|
178
|
-
capabilities:
|
|
178
|
+
capabilities: UserCapabilities;
|
|
179
179
|
expiresAt?: IsoTimestamp;
|
|
180
180
|
} | {
|
|
181
181
|
status: "refreshing";
|
|
182
182
|
identity: User;
|
|
183
|
-
capabilities:
|
|
183
|
+
capabilities: UserCapabilities;
|
|
184
184
|
} | {
|
|
185
185
|
status: "expired";
|
|
186
186
|
identity?: User;
|
|
@@ -230,7 +230,7 @@ export type CompleteAuthenticationRequest = {
|
|
|
230
230
|
export type CompleteAuthenticationResult = {
|
|
231
231
|
status: "authenticated";
|
|
232
232
|
identity: User;
|
|
233
|
-
capabilities:
|
|
233
|
+
capabilities: UserCapabilities;
|
|
234
234
|
expiresAt?: IsoTimestamp;
|
|
235
235
|
} | {
|
|
236
236
|
status: "rejected";
|
|
@@ -295,10 +295,6 @@ export type HostAudioOutput = {
|
|
|
295
295
|
/** What Omni's own chrome shows of a task browser's URL: nothing, the domain, or all of it. */
|
|
296
296
|
export type UrlVisibility = "full" | "domain" | "hidden";
|
|
297
297
|
export interface HostReport {
|
|
298
|
-
/** What the agent can see of a task browser's URL in Omni's chrome. Task browsers only; the personal browser is the agent's. */
|
|
299
|
-
browsers: {
|
|
300
|
-
urlVisibility: UrlVisibility;
|
|
301
|
-
};
|
|
302
298
|
/** Whether the host has a network interface up. Not a claim that anything is reachable: the adapter knows its own platform's reachability better than the host does. */
|
|
303
299
|
online: boolean;
|
|
304
300
|
audio?: {
|
|
@@ -314,7 +310,7 @@ export interface Host {
|
|
|
314
310
|
export interface ConnectContext {
|
|
315
311
|
protocolVersion: number;
|
|
316
312
|
/** The session that authenticated this connection. */
|
|
317
|
-
|
|
313
|
+
loginId: string;
|
|
318
314
|
/** Omni-side policy: whether the agent's tasks are accepted without asking them. */
|
|
319
315
|
autoAcceptTasks?: boolean;
|
|
320
316
|
/**
|
|
@@ -325,13 +321,13 @@ export interface ConnectContext {
|
|
|
325
321
|
signal?: AbortSignal;
|
|
326
322
|
log?: (entry: unknown) => void;
|
|
327
323
|
}
|
|
328
|
-
export type
|
|
324
|
+
export type TransportStatus = "connecting" | "active" | "error";
|
|
329
325
|
/**
|
|
330
326
|
* What revives a connection that reported `error`: `reconnect` -- the login is good, dispose this
|
|
331
327
|
* connection and call `connect()` again -- or `reauthenticate` -- run the authentication flow
|
|
332
328
|
* first. The adapter knows which; the host acts on its word.
|
|
333
329
|
*/
|
|
334
|
-
export type
|
|
330
|
+
export type TransportRecovery = "reconnect" | "reauthenticate";
|
|
335
331
|
/** Every field is optional: a provider sends what it knows and omits what it does not. */
|
|
336
332
|
export interface Contact {
|
|
337
333
|
name?: string;
|
|
@@ -346,7 +342,8 @@ export interface ScheduledActivity {
|
|
|
346
342
|
title: string;
|
|
347
343
|
startsAt: IsoTimestamp;
|
|
348
344
|
endsAt?: IsoTimestamp;
|
|
349
|
-
|
|
345
|
+
/** The person the activity reaches -- a callback's customer. */
|
|
346
|
+
party?: Contact;
|
|
350
347
|
attributes?: Attribute[];
|
|
351
348
|
}
|
|
352
349
|
export interface DispositionCode {
|
|
@@ -354,7 +351,7 @@ export interface DispositionCode {
|
|
|
354
351
|
label: string;
|
|
355
352
|
group?: string;
|
|
356
353
|
}
|
|
357
|
-
export interface
|
|
354
|
+
export interface DispositionRules {
|
|
358
355
|
required?: boolean;
|
|
359
356
|
notes?: "required" | "optional" | "hidden";
|
|
360
357
|
codes?: DispositionCode[];
|
|
@@ -373,7 +370,7 @@ export interface DestinationDirectory {
|
|
|
373
370
|
export interface CustomCapability {
|
|
374
371
|
id: string;
|
|
375
372
|
ui: {
|
|
376
|
-
|
|
373
|
+
control: "button" | "toggle" | "menu-item";
|
|
377
374
|
label: string;
|
|
378
375
|
placement: "primary" | "secondary" | "overflow";
|
|
379
376
|
/** Where the control's work renders: inline in the workspace, or as a page of its own. Inline when absent. */
|
|
@@ -386,7 +383,7 @@ export interface CustomCapability {
|
|
|
386
383
|
}
|
|
387
384
|
export interface SharedTaskCapabilities {
|
|
388
385
|
browsers?: true;
|
|
389
|
-
dispositions?: true |
|
|
386
|
+
dispositions?: true | DispositionRules;
|
|
390
387
|
custom?: CustomCapability[];
|
|
391
388
|
}
|
|
392
389
|
/**
|
|
@@ -429,24 +426,32 @@ export declare const BROWSER_ISOLATION_SCHEMES: {
|
|
|
429
426
|
readonly TASK_TYPE_NAME__TAB_NAME: "TaskTypeName.TabName";
|
|
430
427
|
};
|
|
431
428
|
export type BrowserIsolationScheme = (typeof BROWSER_ISOLATION_SCHEMES)[keyof typeof BROWSER_ISOLATION_SCHEMES];
|
|
432
|
-
|
|
429
|
+
/** One tab, in either workspace. */
|
|
430
|
+
export interface Browser {
|
|
433
431
|
id: string;
|
|
434
432
|
name: string;
|
|
435
|
-
purpose: string;
|
|
436
433
|
/** `http:` or `https:` only. */
|
|
437
434
|
url: string;
|
|
438
435
|
}
|
|
439
436
|
/**
|
|
440
|
-
*
|
|
441
|
-
*
|
|
437
|
+
* A tab the task brought: fixed at the task's definition -- count and details -- with why it is
|
|
438
|
+
* there, whether its session is shared across tasks, and whether the agent may read its URL. The
|
|
439
|
+
* union is what makes a reusing browser with no scheme fail to compile rather than inherit a
|
|
440
|
+
* default -- which is how two tasks end up sharing a session nobody intended.
|
|
442
441
|
*/
|
|
443
|
-
export type TaskBrowser =
|
|
444
|
-
|
|
442
|
+
export type TaskBrowser = Browser & {
|
|
443
|
+
purpose: string;
|
|
444
|
+
/** Hide this tab's URL from the agent in Omni's chrome. Omitted, the URL shows as any browser's does; a provider says `hidden` where the URL carries what the agent may not read. */
|
|
445
|
+
urlVisibility?: UrlVisibility;
|
|
446
|
+
} & ({
|
|
447
|
+
sharedSession: false;
|
|
445
448
|
isolationScheme?: never;
|
|
446
449
|
} | {
|
|
447
|
-
|
|
450
|
+
sharedSession: true;
|
|
448
451
|
isolationScheme: BrowserIsolationScheme;
|
|
449
452
|
});
|
|
453
|
+
/** A tab the agent opened in the personal workspace: theirs, as many as they like, and never on the wire. */
|
|
454
|
+
export type PersonalBrowser = Browser;
|
|
450
455
|
export declare const ALLOWED_BROWSER_URL_SCHEMES: readonly ["http:", "https:"];
|
|
451
456
|
export declare function isAllowedBrowserUrl(url: string): boolean;
|
|
452
457
|
export type TaskPhase =
|
|
@@ -469,7 +474,7 @@ export type TaskAttribute = TaskAttributeBase & ({
|
|
|
469
474
|
value: string;
|
|
470
475
|
} | {
|
|
471
476
|
type: "contact";
|
|
472
|
-
|
|
477
|
+
party: Contact;
|
|
473
478
|
} | {
|
|
474
479
|
type: "timestamp";
|
|
475
480
|
at: IsoTimestamp;
|
|
@@ -500,10 +505,10 @@ export interface TaskHandlingStep {
|
|
|
500
505
|
*/
|
|
501
506
|
export type TaskCompletion = {
|
|
502
507
|
completionMode: "agent-command";
|
|
503
|
-
|
|
508
|
+
wrapAllowance?: DurationSeconds;
|
|
504
509
|
} | {
|
|
505
510
|
completionMode: "provider-automatic";
|
|
506
|
-
|
|
511
|
+
wrapAllowance: DurationSeconds;
|
|
507
512
|
};
|
|
508
513
|
/**
|
|
509
514
|
* A consultation in progress on a task: who is being consulted, and since when where the provider
|
|
@@ -520,7 +525,7 @@ export interface TaskConsultation {
|
|
|
520
525
|
* `requested` while nobody has joined; `joined`, with `leadId`, once somebody has.
|
|
521
526
|
*/
|
|
522
527
|
export interface TaskLead {
|
|
523
|
-
|
|
528
|
+
stage: "requested" | "joined";
|
|
524
529
|
leadId?: UserId;
|
|
525
530
|
note?: string;
|
|
526
531
|
since: IsoTimestamp;
|
|
@@ -535,21 +540,21 @@ export interface TaskAssisting {
|
|
|
535
540
|
since: IsoTimestamp;
|
|
536
541
|
}
|
|
537
542
|
/**
|
|
538
|
-
* A
|
|
539
|
-
* a typical organisation has, `
|
|
540
|
-
* never describes the chain: which
|
|
543
|
+
* A level of the organisation's structure, by the id its manifest declares -- or one of the four
|
|
544
|
+
* a typical organisation has, `DEFAULT_LEVELS`, when the manifest declares no ladder. The protocol
|
|
545
|
+
* never describes the chain: which levels a person passes through is the structure's to know.
|
|
541
546
|
*/
|
|
542
|
-
export type
|
|
543
|
-
/** A
|
|
544
|
-
export interface
|
|
545
|
-
id:
|
|
547
|
+
export type Level = string;
|
|
548
|
+
/** A level the structure has, with the label a desk shows for "who decided". */
|
|
549
|
+
export interface LevelDeclaration {
|
|
550
|
+
id: Level;
|
|
546
551
|
label: string;
|
|
547
552
|
}
|
|
548
553
|
/**
|
|
549
|
-
* The
|
|
550
|
-
* `
|
|
554
|
+
* The levels a typical organisation has: the ladder in force when a manifest declares no
|
|
555
|
+
* `orgLevels`. A manifest that declares any states its whole ladder outright.
|
|
551
556
|
*/
|
|
552
|
-
export declare const
|
|
557
|
+
export declare const DEFAULT_LEVELS: readonly [{
|
|
553
558
|
readonly id: "org";
|
|
554
559
|
readonly label: "Your organisation";
|
|
555
560
|
}, {
|
|
@@ -563,14 +568,14 @@ export declare const DEFAULT_TIERS: readonly [{
|
|
|
563
568
|
readonly label: "You";
|
|
564
569
|
}];
|
|
565
570
|
/** The ladder in force for a manifest: exactly what it declares, or the defaults when it declares none. */
|
|
566
|
-
export declare function
|
|
571
|
+
export declare function effectiveLevels(declared: readonly LevelDeclaration[] | undefined): LevelDeclaration[];
|
|
567
572
|
/**
|
|
568
|
-
* Something the queue could allow, locked above the person: the
|
|
569
|
-
* and why if they said. `person` never locks their own value, and the queue is not a
|
|
573
|
+
* Something the queue could allow, locked above the person: the level that made it unchangeable,
|
|
574
|
+
* and why if they said. `person` never locks their own value, and the queue is not a level --
|
|
570
575
|
* what the queue does not allow at all is simply absent.
|
|
571
576
|
*/
|
|
572
577
|
export interface Locked {
|
|
573
|
-
lockedBy:
|
|
578
|
+
lockedBy: Level;
|
|
574
579
|
reason?: string;
|
|
575
580
|
}
|
|
576
581
|
/**
|
|
@@ -586,7 +591,8 @@ export type Task<C extends Channel = Channel> = {
|
|
|
586
591
|
taskType: string;
|
|
587
592
|
capabilities: TaskCapabilities<C>;
|
|
588
593
|
browsers: TaskBrowser[];
|
|
589
|
-
|
|
594
|
+
/** The person or entity on the other end of this task. Who the task is with; `contacts` on the snapshot is the directory. */
|
|
595
|
+
party?: Contact;
|
|
590
596
|
phase: TaskPhase;
|
|
591
597
|
/** The identifier an agent reads back to a customer, where the provider has one. */
|
|
592
598
|
reference?: string;
|
|
@@ -782,7 +788,7 @@ export interface BreakReason {
|
|
|
782
788
|
label: string;
|
|
783
789
|
group?: string;
|
|
784
790
|
kind?: BreakKind;
|
|
785
|
-
/** Survives `
|
|
791
|
+
/** Survives `mayAsk: false`: a mandatory rest is not something a busy hour can cancel. */
|
|
786
792
|
alwaysAvailable?: true;
|
|
787
793
|
}
|
|
788
794
|
export interface BreakRequest {
|
|
@@ -809,8 +815,8 @@ export type ImposedBreak = {
|
|
|
809
815
|
export interface BreakState {
|
|
810
816
|
approval: BreakApproval;
|
|
811
817
|
/** Whether the agent may ask at all. Distinct from the fate of a request already made. */
|
|
812
|
-
|
|
813
|
-
/** Shown when `
|
|
818
|
+
mayAsk: boolean;
|
|
819
|
+
/** Shown when `mayAsk` is false, such as "Busy hours". */
|
|
814
820
|
refusedReason?: string;
|
|
815
821
|
decisionReason?: string;
|
|
816
822
|
retryAfterMs?: number;
|
|
@@ -883,7 +889,7 @@ export interface TeamRoster {
|
|
|
883
889
|
export type PolicyKey = Exclude<keyof TaskCapabilities<"voice">, keyof SharedTaskCapabilities> | Exclude<keyof TaskCapabilities<"chat">, keyof SharedTaskCapabilities> | Exclude<keyof TaskCapabilities<"email">, keyof SharedTaskCapabilities> | "dial" | `skill:${string}`;
|
|
884
890
|
/** On for everyone, off for everyone, or the agent's own choice. Only `hold`, `mute` and skills may be `agent`. */
|
|
885
891
|
export type TeamPolicySetting = "on" | "off" | "agent";
|
|
886
|
-
/** One policy as the lead sees it: the setting, who set it, and `lockedBy` when a
|
|
892
|
+
/** One policy as the lead sees it: the setting, who set it, and `lockedBy` when a level above the team made it theirs to keep. */
|
|
887
893
|
export interface TeamPolicy extends Resolved {
|
|
888
894
|
setting: TeamPolicySetting;
|
|
889
895
|
}
|
|
@@ -937,7 +943,7 @@ export interface TeamBreakCommandRequest {
|
|
|
937
943
|
* `ended` once primary handling's audio ended, and the field omitted while none should be. The
|
|
938
944
|
* provider's word -- a desk attaches and renders audio from it, never from its own senses.
|
|
939
945
|
*/
|
|
940
|
-
export type TaskMediaState = "
|
|
946
|
+
export type TaskMediaState = "started" | "ended";
|
|
941
947
|
export interface VoiceMediaSession {
|
|
942
948
|
remoteAudio: MediaStream;
|
|
943
949
|
setMuted(muted: boolean): void;
|
|
@@ -959,27 +965,27 @@ export type OpenMediaResult = {
|
|
|
959
965
|
/**
|
|
960
966
|
* What the team may leave to the person: a capability by its own name -- `hold`, `mute` -- or a
|
|
961
967
|
* skill by its provider id. The same key as in `Task.capabilities`, because it is the same
|
|
962
|
-
* capability seen at another
|
|
968
|
+
* capability seen at another level. Callback and new call are never the person's; they are the
|
|
963
969
|
* team's, on or off, within what the queue allows.
|
|
964
970
|
*/
|
|
965
971
|
export type PreferenceId = "hold" | "mute" | `skill:${string}`;
|
|
966
972
|
/**
|
|
967
|
-
* Who stated a value as it stands: a
|
|
968
|
-
* protocol's own word for "no
|
|
973
|
+
* Who stated a value as it stands: a level -- `person` among them -- or `provisioning`, the
|
|
974
|
+
* protocol's own word for "no level has said anything and the provider's default applies".
|
|
969
975
|
* Nothing is hidden for want of a row.
|
|
970
976
|
*/
|
|
971
|
-
export type SetBy =
|
|
977
|
+
export type SetBy = Level | "provider";
|
|
972
978
|
/** What every resolved value carries: who set it, and who locked it if anyone did. */
|
|
973
979
|
export interface Resolved {
|
|
974
980
|
setBy: SetBy;
|
|
975
|
-
lockedBy?:
|
|
981
|
+
lockedBy?: Level;
|
|
976
982
|
/** Given with `lockedBy`, where whoever locked it said why. */
|
|
977
983
|
reason?: string;
|
|
978
984
|
}
|
|
979
985
|
/**
|
|
980
986
|
* One choice the team may leave to the person, with where it stands and who set it. The provider
|
|
981
987
|
* keeps it: it is the person's across sessions, written through `setPreference`. Listed whether
|
|
982
|
-
* or not anyone has stated it, and even when a
|
|
988
|
+
* or not anyone has stated it, and even when a level above has since locked it.
|
|
983
989
|
*/
|
|
984
990
|
export interface AgentPreference extends Resolved {
|
|
985
991
|
id: PreferenceId;
|
|
@@ -1001,8 +1007,8 @@ export type PreferenceResult = {
|
|
|
1001
1007
|
failure: ProtocolFailure;
|
|
1002
1008
|
};
|
|
1003
1009
|
export interface Snapshot<C extends Channel = Channel> {
|
|
1004
|
-
|
|
1005
|
-
|
|
1010
|
+
transport: TransportStatus;
|
|
1011
|
+
loginId: string;
|
|
1006
1012
|
break: BreakState;
|
|
1007
1013
|
/** Every task currently owned by this agent for this provider. */
|
|
1008
1014
|
tasks: Task<C>[];
|
|
@@ -1027,7 +1033,7 @@ export interface SummaryMetric {
|
|
|
1027
1033
|
label: string;
|
|
1028
1034
|
value: string;
|
|
1029
1035
|
}
|
|
1030
|
-
export interface
|
|
1036
|
+
export interface QueueSummary {
|
|
1031
1037
|
title: string;
|
|
1032
1038
|
subtitle?: string;
|
|
1033
1039
|
waitingCount: number;
|
|
@@ -1039,13 +1045,13 @@ export type ProviderEvent<C extends Channel = Channel> = {
|
|
|
1039
1045
|
reason: "reconnected" | "provider-requested";
|
|
1040
1046
|
snapshot: Snapshot<C>;
|
|
1041
1047
|
} | {
|
|
1042
|
-
type: "
|
|
1048
|
+
type: "transport-status";
|
|
1043
1049
|
status: "connecting" | "active";
|
|
1044
1050
|
message?: string;
|
|
1045
1051
|
} | {
|
|
1046
|
-
type: "
|
|
1052
|
+
type: "transport-status";
|
|
1047
1053
|
status: "error";
|
|
1048
|
-
recovery:
|
|
1054
|
+
recovery: TransportRecovery;
|
|
1049
1055
|
message?: string;
|
|
1050
1056
|
} | {
|
|
1051
1057
|
type: "break-state";
|
|
@@ -1060,7 +1066,7 @@ export type ProviderEvent<C extends Channel = Channel> = {
|
|
|
1060
1066
|
type: "task-updated";
|
|
1061
1067
|
task: Task<C>;
|
|
1062
1068
|
} | {
|
|
1063
|
-
type: "task-media-
|
|
1069
|
+
type: "task-media-started";
|
|
1064
1070
|
taskId: TaskId;
|
|
1065
1071
|
} | {
|
|
1066
1072
|
type: "task-media-ended";
|
|
@@ -1076,8 +1082,8 @@ export type ProviderEvent<C extends Channel = Channel> = {
|
|
|
1076
1082
|
announcedAt: IsoTimestamp;
|
|
1077
1083
|
expiresAt?: IsoTimestamp;
|
|
1078
1084
|
} | {
|
|
1079
|
-
type: "
|
|
1080
|
-
summary:
|
|
1085
|
+
type: "queue-summary";
|
|
1086
|
+
summary: QueueSummary;
|
|
1081
1087
|
} | {
|
|
1082
1088
|
type: "team-updated";
|
|
1083
1089
|
team: TeamRoster;
|
|
@@ -1096,7 +1102,7 @@ export type ProviderEvent<C extends Channel = Channel> = {
|
|
|
1096
1102
|
export interface ProviderEventEnvelope<C extends Channel = Channel> {
|
|
1097
1103
|
id: string;
|
|
1098
1104
|
/** The login this belongs to. */
|
|
1099
|
-
|
|
1105
|
+
loginId: string;
|
|
1100
1106
|
occurredAt: IsoTimestamp;
|
|
1101
1107
|
event: ProviderEvent<C>;
|
|
1102
1108
|
}
|
|
@@ -1221,11 +1227,11 @@ export interface BrowserSessionKeyInput {
|
|
|
1221
1227
|
* guard -- comparing the identity -- never fires on a demotion, because the thing that changed is
|
|
1222
1228
|
* not the thing being compared. Key order does not matter, and `team: {}` is not `team` absent.
|
|
1223
1229
|
*/
|
|
1224
|
-
export declare function sameCapabilities(a:
|
|
1230
|
+
export declare function sameCapabilities(a: UserCapabilities, b: UserCapabilities): boolean;
|
|
1225
1231
|
/**
|
|
1226
1232
|
* The storage-profile key a reusing browser shares, or `undefined` where it shares nothing.
|
|
1227
1233
|
*
|
|
1228
|
-
* Fails closed. A browser with `
|
|
1234
|
+
* Fails closed. A browser with `sharedSession: false` has no key; nor does a reusing one whose scheme is
|
|
1229
1235
|
* missing or unknown -- the type forbids that, but an adapter compiled against another version can
|
|
1230
1236
|
* still send it, and the safe reading is "do not share", never "share with everyone named the
|
|
1231
1237
|
* same". Every part is encoded, separator included, before joining, so a tab called `a.b`
|
package/dist/index.js
CHANGED
|
@@ -51,18 +51,18 @@ export function isAllowedBrowserUrl(url) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
-
* The
|
|
55
|
-
* `
|
|
54
|
+
* The levels a typical organisation has: the ladder in force when a manifest declares no
|
|
55
|
+
* `orgLevels`. A manifest that declares any states its whole ladder outright.
|
|
56
56
|
*/
|
|
57
|
-
export const
|
|
57
|
+
export const DEFAULT_LEVELS = [
|
|
58
58
|
{ id: "org", label: "Your organisation" },
|
|
59
59
|
{ id: "site", label: "Your site" },
|
|
60
60
|
{ id: "team", label: "Your team" },
|
|
61
61
|
{ id: "person", label: "You" },
|
|
62
62
|
];
|
|
63
63
|
/** The ladder in force for a manifest: exactly what it declares, or the defaults when it declares none. */
|
|
64
|
-
export function
|
|
65
|
-
return [...(declared ??
|
|
64
|
+
export function effectiveLevels(declared) {
|
|
65
|
+
return [...(declared ?? DEFAULT_LEVELS)];
|
|
66
66
|
}
|
|
67
67
|
// ---------------------------------------------------------------------------
|
|
68
68
|
// Task commands.
|
|
@@ -157,7 +157,7 @@ export function sameCapabilities(a, b) {
|
|
|
157
157
|
/**
|
|
158
158
|
* The storage-profile key a reusing browser shares, or `undefined` where it shares nothing.
|
|
159
159
|
*
|
|
160
|
-
* Fails closed. A browser with `
|
|
160
|
+
* Fails closed. A browser with `sharedSession: false` has no key; nor does a reusing one whose scheme is
|
|
161
161
|
* missing or unknown -- the type forbids that, but an adapter compiled against another version can
|
|
162
162
|
* still send it, and the safe reading is "do not share", never "share with everyone named the
|
|
163
163
|
* same". Every part is encoded, separator included, before joining, so a tab called `a.b`
|
|
@@ -165,7 +165,7 @@ export function sameCapabilities(a, b) {
|
|
|
165
165
|
*/
|
|
166
166
|
export function browserSessionKey(input) {
|
|
167
167
|
const { providerId, taskId, taskType, browser } = input;
|
|
168
|
-
if (browser.
|
|
168
|
+
if (browser.sharedSession !== true)
|
|
169
169
|
return undefined;
|
|
170
170
|
// `encodeURIComponent` leaves `.` untouched, and `.` is the separator: a raw join would let
|
|
171
171
|
// provider `Acme.Voice` with type `Support` forge the key of `Acme` with `Voice.Support`.
|
package/dist/testing.d.ts
CHANGED
|
@@ -120,7 +120,7 @@ export declare class BreakStream {
|
|
|
120
120
|
export declare function assertBreakFollowsItsRequests(envelopes: readonly ProviderEventEnvelope[], snapshot?: Snapshot): void;
|
|
121
121
|
/**
|
|
122
122
|
* The media follows the task and never decides it. Given a provider's stream -- optionally seeded
|
|
123
|
-
* with the snapshot it began from -- every task is introduced once, `task-media-
|
|
123
|
+
* with the snapshot it began from -- every task is introduced once, `task-media-started` and
|
|
124
124
|
* `task-media-ended` alternate on work that has begun, media ends only where it arrived, and what
|
|
125
125
|
* follows the media ending is `completing` or `task-ended`.
|
|
126
126
|
*/
|
|
@@ -162,11 +162,11 @@ export declare function assertBreakBeginsAfterTask(steps: readonly BreakOnTaskSt
|
|
|
162
162
|
* nothing down when the provider set a clock. `toleranceMs` absorbs scheduler jitter in a real
|
|
163
163
|
* implementation; pass 0 to demand an exact match.
|
|
164
164
|
*/
|
|
165
|
-
export declare function assertWrapTimeout(task: Pick<TaskCompletion, "completionMode" | "
|
|
165
|
+
export declare function assertWrapTimeout(task: Pick<TaskCompletion, "completionMode" | "wrapAllowance">, mediaEndedAt: string, observedDeadline: string | undefined, toleranceMs?: number): void;
|
|
166
166
|
/** One browser in one task of one provider. `providerId` is `Manifest.id`, never `displayName`. */
|
|
167
167
|
export type BrowserIsolationScenario = BrowserSessionKeyInput;
|
|
168
168
|
/** Validates whether two task-browser definitions should share one browser session. */
|
|
169
|
-
export declare function
|
|
169
|
+
export declare function assertBrowserSessionIsolation(left: BrowserIsolationScenario, right: BrowserIsolationScenario, expectedReuse: boolean): void;
|
|
170
170
|
/**
|
|
171
171
|
* Asserts that no two distinct scenarios in `scenarios` derive the same session key.
|
|
172
172
|
* Feed it adversarial names — a provider called `A.B` against a task type called
|