@touchcastllc/napster-companion-api-dev 1.6.0-alpha.8 → 2.0.0-alpha.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.
@@ -19,6 +19,18 @@ export interface MicGateOptions {
19
19
  onGranted: () => void | Promise<void>;
20
20
  /** Reported once, on the FIRST failure only — never on a refused retry. */
21
21
  onError?: (error: Error) => void;
22
+ /**
23
+ * Fires when a native permission prompt is ignored for 30s and the attempt
24
+ * collapses back to the entry point — first attempt or retry. Distinct from
25
+ * onError: a dismiss is not a failure, it is a clean, silent return to idle,
26
+ * so nothing on this channel may reach the host's own onError.
27
+ *
28
+ * Exists because gateOnMicrophone()'s own promise resolves identically for
29
+ * every outcome (it returns a bare { dismiss } handle), so a caller awaiting
30
+ * it cannot otherwise tell a dismissed attempt from a live one — button mode
31
+ * latched a fake "connected" instance on exactly that ambiguity.
32
+ */
33
+ onDismissed?: () => void;
22
34
  /** Fires immediately before the recovery card mounts. */
23
35
  onBeforeRecovery?: () => void;
24
36
  /**
@@ -28,6 +40,13 @@ export interface MicGateOptions {
28
40
  * detached root, and must not drive the session that replaced it.
29
41
  */
30
42
  isStale?: () => boolean;
43
+ /**
44
+ * Which avatar shape to render the Permission States recovery card as —
45
+ * threaded straight through to createMicPermission(). See
46
+ * MicPermissionOptions.view's own comment for why this can't be inferred
47
+ * from CSS cascade.
48
+ */
49
+ view: string | undefined;
31
50
  }
32
51
  /**
33
52
  * Gate a session on microphone access (CA-622): try once, and on denial keep
@@ -3,4 +3,6 @@ export type { RootState } from "./store";
3
3
  export { setOnFeaturesUpdateCallback } from "./middleware/featuresUpdateMiddleware";
4
4
  export * from "./selectors";
5
5
  export { setSessionId, setTransport, setStopInteraction, setMuted, setAutoMicActive, setVolume, setAvatarSpeaking, setScreenSharing, setUserTalking, setConnectionToken, setCloseConnectionHandler, setSessionTerminatedByServer, resetAvatar, } from "./slices/avatarSlice";
6
+ export { setSessionStatus, resetSession } from "./slices/sessionSlice";
7
+ export type { SessionStatus } from "./slices/sessionSlice";
6
8
  export { setFeatures, resetFeatures, setDebugMode } from "./slices/appSlice";
@@ -1,6 +1,7 @@
1
1
  import { RootState } from "./index";
2
2
  import { FeatureConfig } from "../types";
3
3
  import type { Transport } from "../services/transport";
4
+ import type { SessionStatus } from "./slices/sessionSlice";
4
5
  /**
5
6
  * Avatar state selectors
6
7
  */
@@ -14,6 +15,10 @@ export declare const selectConnectionToken: (state: RootState) => string | undef
14
15
  export declare const selectStopInteraction: (state: RootState) => boolean;
15
16
  export declare const selectCloseConnectionHandler: (state: RootState) => (() => void) | undefined;
16
17
  export declare const selectSessionTerminatedByServer: (state: RootState) => boolean;
18
+ /**
19
+ * Session-lifecycle selectors (CA-834)
20
+ */
21
+ export declare const selectSessionStatus: (state: RootState) => SessionStatus;
17
22
  /**
18
23
  * App state selectors
19
24
  */
@@ -0,0 +1,7 @@
1
+ import type { SessionStatus } from "../../types";
2
+ export type { SessionStatus };
3
+ export interface SessionSliceState {
4
+ status: SessionStatus;
5
+ }
6
+ export declare const setSessionStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<SessionStatus, "session/setSessionStatus">, resetSession: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"session/resetSession">;
7
+ export declare const sessionReducer: import("redux").Reducer<SessionSliceState>;
@@ -1,10 +1,12 @@
1
1
  export declare const store: import("@reduxjs/toolkit").EnhancedStore<{
2
2
  avatar: import("./slices/avatarSlice").AvatarState;
3
3
  app: import("./slices/appSlice").AppState;
4
+ session: import("./slices/sessionSlice").SessionSliceState;
4
5
  }, import("redux").UnknownAction, import("@reduxjs/toolkit").Tuple<[import("redux").StoreEnhancer<{
5
6
  dispatch: import("redux-thunk").ThunkDispatch<{
6
7
  avatar: import("./slices/avatarSlice").AvatarState;
7
8
  app: import("./slices/appSlice").AppState;
9
+ session: import("./slices/sessionSlice").SessionSliceState;
8
10
  }, undefined, import("redux").UnknownAction>;
9
11
  }>, import("redux").StoreEnhancer]>>;
10
12
  export type RootState = ReturnType<typeof store.getState>;
@@ -117,6 +117,16 @@ export type FunctionCallOutputCommand = {
117
117
  export type DataChannelCommand = SendMessageCommand | CancelCommand | SetSettingsCommand | StartVideoCommand | StopVideoCommand | FunctionCallOutputCommand;
118
118
  export declare const maxInactiveTimeoutDuration = 180000;
119
119
  export declare const defaultCountdownDuration = 30;
120
+ /**
121
+ * The session-lifecycle state (CA-834). `muted` is a separate, orthogonal
122
+ * flag (read via `getMutedState()`/`onAvatarReady`, not part of this union) —
123
+ * it composes with `readyAudio`/`readyVideo` rather than adding its own
124
+ * members here. `permissionDismissed` fires when the user never answers the
125
+ * browser's native microphone prompt within 30s (micGate.ts's acquire()) —
126
+ * immediately followed by `entry`, collapsing the UI back to the Entry
127
+ * Point bubble.
128
+ */
129
+ export type SessionStatus = "entry" | "loading" | "permissionPending" | "permissionDismissed" | "permissionBlocked" | "readyAudio" | "readyVideo";
120
130
  export interface EventMessage {
121
131
  event: string;
122
132
  data?: {
@@ -126,6 +136,16 @@ export interface EventMessage {
126
136
  content?: string;
127
137
  role?: string;
128
138
  };
139
+ /**
140
+ * Present on `avatar_state_changed` events for a deferred-video session
141
+ * (CA-593/CA-834): the server sends `ready` twice, `details.mode`
142
+ * distinguishing the first (audio-only) from the second (audio+video).
143
+ */
144
+ details?: {
145
+ mode?: string;
146
+ stage?: string;
147
+ video_type?: string;
148
+ };
129
149
  [key: string]: unknown;
130
150
  };
131
151
  }
@@ -136,7 +156,20 @@ export interface EventMessage {
136
156
  * (for example `inactiveTimeout.duration`).
137
157
  */
138
158
  export interface FeatureConfig {
139
- /** Background removal feature. When enabled, the avatar background should be removed. */
159
+ /**
160
+ * Background removal (green-screen cutout) feature.
161
+ *
162
+ * @deprecated `enabled` is no longer settable — it's driven entirely by
163
+ * `style.view` (CA-834, manager feedback): `"silhouette"` turns it on,
164
+ * any other view turns it off, unconditionally. `enableFeature`/
165
+ * `disableFeature`/`updateFeatureConfig` accept calls for this feature
166
+ * without erroring, but silently resync `enabled` back to whatever the
167
+ * current view dictates rather than applying the requested value. Kept
168
+ * on `FeatureConfig` only so existing config objects that set it don't
169
+ * become a type error; the value itself is ignored on the way in and
170
+ * only meaningful as a read of the current derived state (e.g. via
171
+ * `onFeaturesUpdate`).
172
+ */
140
173
  backgroundRemoval?: {
141
174
  enabled: boolean;
142
175
  };
@@ -165,18 +198,6 @@ export interface FeatureConfig {
165
198
  text?: string;
166
199
  color?: string;
167
200
  };
168
- /** Loader overlay shown while the SDK/Avatar is loading. */
169
- showSDKLoader?: {
170
- enabled: boolean;
171
- /** Optional background color for the loader overlay. */
172
- bgColor?: string;
173
- /** Optional color for the loader animation. */
174
- color?: string;
175
- /** Optional loader animation type. */
176
- type?: "spinner" | "pulse";
177
- /** Optional CSS class name(s) to add to the loader spinner element. */
178
- className?: string;
179
- };
180
201
  /** Document Picture-in-Picture. When enabled, the avatar automatically
181
202
  * opens in a PiP window when the user switches browser tabs (Chrome/Edge 116+). */
182
203
  pictureInPicture?: {
@@ -271,25 +292,60 @@ export interface FaceTrackingController {
271
292
  /** Full teardown: stop + dispose model + null all refs. */
272
293
  destroy(): void;
273
294
  }
274
- export interface avatarStyleConfig {
295
+ export interface StyleConfig {
275
296
  /** Avatar visual style: "round" | "rectangle" | "silhouette" (Default: "round").
276
297
  *
277
298
  * Use "rectangle" when applying custom styling that requires a rectangular container.
278
299
  */
279
300
  view: "round" | "rectangle" | "silhouette";
280
- /** Border width in pixels. */
281
- borderWidth?: CSSStyleDeclaration["borderWidth"];
282
- /** Border color as a CSS color string. */
283
- borderColor?: CSSStyleDeclaration["borderColor"];
284
- /** Border style as a CSS border-style string (e.g., "solid", "dashed"). */
285
- borderStyle?: CSSStyleDeclaration["borderStyle"];
301
+ /**
302
+ * Brand accent color, any CSS color string. Default: `"#be369d"`. Replaces
303
+ * that default everywhere it's currently used as the SDK's one accent hue:
304
+ * the control bar's live-mic button fill, the mic-permission recovery
305
+ * card's buttons/links (`--np-mic-permission-accent`), and the loading
306
+ * photo bubble's own ring. Does NOT affect the muted-mic button (a fixed
307
+ * warning color, not brand-tied) or `persistence.navigationProgress`'s
308
+ * own color (set separately — see {@link NavigationProgressOptions}).
309
+ */
310
+ accentColor?: string;
311
+ /** CSS class name(s) added to the SDK root container. Useful for theming. */
312
+ className?: string;
313
+ }
314
+ export interface LayoutConfig {
315
+ /**
316
+ * Layout mode.
317
+ *
318
+ * - `"fixed"` (default): the avatar floats over the page as a corner widget,
319
+ * anchored to the viewport via `position`. Always mounts to
320
+ * `document.body`, ignoring `mountContainer` if set.
321
+ * - `"inline"`: the avatar renders inside `mountContainer` and fills it. Size
322
+ * and shape come from how you style that container; `position` is ignored.
323
+ * When the container has no explicit height, an internal 4:5 ratio is used
324
+ * so the avatar is never invisible.
325
+ */
326
+ type?: "fixed" | "inline";
327
+ /** Position of the avatar on screen. Meaningful only when `type` is
328
+ * `"fixed"` (the default). See `Position` enum. */
329
+ position?: Position;
330
+ /**
331
+ * Mount target for the SDK. Provide an actual HTMLElement or a selector
332
+ * string; defaults to `document.body` when omitted. Meaningful only under
333
+ * `type: "inline"` — `type: "fixed"` always drops this and mounts to
334
+ * `document.body` regardless of what's passed here, since a floating,
335
+ * viewport-anchored widget has no host container to size or position
336
+ * against.
337
+ */
338
+ mountContainer?: HTMLElement | string | null;
339
+ }
340
+ export interface AgentConfig {
341
+ /**
342
+ * The agent's photo. Single source of truth for every avatar photo shown
343
+ * in the widget — the Entry Point / button card, the loading placeholder,
344
+ * and the mic-permission recovery card. Omitted → each of those falls back
345
+ * to its own bundled placeholder.
346
+ */
347
+ photoUrl?: string;
286
348
  }
287
- /**
288
- * CSS style object that works with vanilla JS.
289
- * Compatible with CSSStyleDeclaration.
290
- * Allows both string and number values for CSS properties.
291
- */
292
- export type StyleObject = Partial<CSSStyleDeclaration>;
293
349
  /**
294
350
  * Styling for the navigation progress bar — see
295
351
  * {@link PersistenceOptions.navigationProgress}. Both map onto CSS custom properties,
@@ -408,7 +464,7 @@ export interface ButtonConfig {
408
464
  enabled?: boolean;
409
465
  /**
410
466
  * Host-owned trigger — an element, or a selector resolved once at `init()`.
411
- * When set the SDK renders no card of its own and `label`, `avatarUrl` and
467
+ * When set the SDK renders no card of its own and `label` and
412
468
  * `description` are ignored. The SDK never moves or reparents the node; it
413
469
  * sets `data-np-state`, toggles `disabled`, and hides it while a session is
414
470
  * live, restoring it on end.
@@ -416,10 +472,17 @@ export interface ButtonConfig {
416
472
  element?: HTMLElement | string;
417
473
  /** Card text. Default: `"Talk to an agent"`. Ignored when `element` is set. */
418
474
  label?: string;
419
- /** Optional companion picture on the card. Ignored when `element` is set. */
420
- avatarUrl?: string;
421
475
  /** Supporting line under the label (Figma entry-point card). Ignored when `element` is set. */
422
476
  description?: string;
477
+ /**
478
+ * Which edge of the host container the SDK-rendered trigger card docks
479
+ * to, for `layout.type: "inline"`. Default: `"right"`. Ignored when `element`
480
+ * is set (a host-owned trigger keeps whatever position the host already
481
+ * gave it) and has no effect in fixed layout either — there, idle's own
482
+ * shrink-to-fit sizing (Button.css) already shrinks the root to exactly
483
+ * the card's own size, leaving no room inside it to dock toward an edge.
484
+ */
485
+ alignment?: "left" | "right";
423
486
  }
424
487
  /**
425
488
  * Returned by `init()` in button mode. Nothing is connected when you receive it:
@@ -469,44 +532,16 @@ export interface NapsterCompanionApiConfig {
469
532
  * session presents is a separate, runtime-changeable axis.
470
533
  */
471
534
  modality?: "video";
472
- /** Position of the avatar on screen. See `Position` enum. */
473
- position?: Position;
474
- /** CSS class name(s) to add to the SDK root container. Useful for theming. */
475
- className?: string;
476
- /** Inline style object applied to the SDK root container. It supports only vanilla JS style objects. */
477
- style?: StyleObject;
478
- /** Avatar visual style configuration. */
479
- avatarStyle?: avatarStyleConfig;
535
+ /** Placement: layout mode, screen position, and (for inline layout) the
536
+ * mount target. See {@link LayoutConfig}. */
537
+ layout?: LayoutConfig;
538
+ /** Visual style: avatar shape, brand accent color, and a CSS class for the
539
+ * root container. See {@link StyleConfig}. */
540
+ style?: StyleConfig;
541
+ /** The agent's identity — currently just its photo. See {@link AgentConfig}. */
542
+ agent?: AgentConfig;
480
543
  /** Per-feature configuration object to toggle features and set options. */
481
544
  features?: FeatureConfig;
482
- /**
483
- * Mount target for the SDK. Provide an actual HTMLElement or a selector
484
- * string; defaults to `document.body` when omitted.
485
- */
486
- mountContainer?: HTMLElement | string | null;
487
- /**
488
- * Layout mode.
489
- *
490
- * - `"fixed"` (default): the avatar floats over the page as a corner widget,
491
- * anchored to the viewport via `position`. This is the original behavior.
492
- * - `"inline"`: the avatar renders inside `mountContainer` and fills it. Size
493
- * and shape come from how you style that container; `position` is ignored.
494
- * When the container has no explicit height, an internal 4:5 ratio is used
495
- * so the avatar is never invisible.
496
- */
497
- layout?: "fixed" | "inline";
498
- /**
499
- * AI companion function definitions for function calling support.
500
- *
501
- * Functions are configured server-side and baked into the connection at
502
- * session-creation time (the connection API / token). To register tools
503
- * mid-session from the page, use the WebMCP bridge, which pushes them as
504
- * `set_settings.inline_functions` after the session reaches "ready".
505
- *
506
- * @deprecated The SDK no longer sends these over the data channel — supplying
507
- * them here has no effect. Configure functions via the connection API instead.
508
- */
509
- functions?: CompanionFunction[];
510
545
  /** Enable debug logging throughout the SDK. When enabled, detailed logs will be output to the console. */
511
546
  debug?: boolean;
512
547
  /** @internal */
@@ -526,7 +561,7 @@ export interface NapsterCompanionApiConfig {
526
561
  * Keep the session alive across page navigation. When enabled, the page is wrapped in
527
562
  * a same-origin iframe so navigation happens inside it while the avatar stays in the
528
563
  * top document, which never reloads.
529
- * When on, `mountContainer` is ignored — the avatar must live in the top document.
564
+ * When on, `layout.mountContainer` is ignored — the avatar must live in the top document.
530
565
  */
531
566
  persistence?: PersistenceOptions;
532
567
  /** Click-to-start configuration. Omit for the default connect-immediately behaviour. */
@@ -542,6 +577,13 @@ export interface NapsterCompanionApiConfig {
542
577
  onAvatarReady?: (isReady?: boolean) => void;
543
578
  /** Called when the avatar inactivity status changes; receives the new status. */
544
579
  onInactivityStatusChange?: (isInactive: boolean) => void;
580
+ /**
581
+ * Called whenever the internal session-lifecycle status changes (CA-834);
582
+ * receives the new status. Intended for hosts that want to drive their own
583
+ * UI off the exact same state the SDK tracks internally, rather than
584
+ * inferring it from `onAvatarReady`/`onError` alone.
585
+ */
586
+ onSessionStatusChange?: (status: SessionStatus) => void;
545
587
  /** Called when the SDK is destroyed via the public API. */
546
588
  onDestroy?: () => void;
547
589
  /** Called whenever feature configuration is changed at runtime. */
@@ -571,14 +613,12 @@ export interface NapsterCompanionApiInstance {
571
613
  * and reset to `undefined` once the connection is closed.
572
614
  */
573
615
  readonly sessionId: string | undefined;
574
- /** Update the inline style object applied to the SDK root container. */
575
- updateStyles: (styles: NapsterCompanionApiConfig["style"]) => void;
576
616
  /** Set the position of the SDK on screen (see `Position`). */
577
617
  setPosition: (position: Position) => void;
578
618
  /** Clear any programmatic position and revert to the configured/default position. */
579
619
  clearPosition: () => void;
580
- /** Update avatar style configuration (e.g., view: round/silhouette/rectangle). */
581
- updateAvatarStyle: (newAvatarStyle: Partial<NonNullable<NapsterCompanionApiConfig["avatarStyle"]>>) => void;
620
+ /** Update style configuration (e.g., view: round/silhouette/rectangle). */
621
+ updateStyle: (newStyle: Partial<NonNullable<NapsterCompanionApiConfig["style"]>>) => void;
582
622
  /** Enable a named feature (one of the keys from `FeatureConfig`).
583
623
  *
584
624
  * e.g. `enableFeature("disclaimer")`
@@ -92,6 +92,11 @@ declare class DebugLogger {
92
92
  * Log cross-page persistence information (iframe wrap/unwrap, break-out, history sync).
93
93
  */
94
94
  persistence(...args: unknown[]): void;
95
+ /**
96
+ * Log SessionStatus transitions (CA-834): entry/loading/permissionPending/
97
+ * permissionDismissed/permissionBlocked/readyAudio/readyVideo.
98
+ */
99
+ session(...args: unknown[]): void;
95
100
  }
96
101
  /**
97
102
  * Global debug logger instance
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The wrap div's own literal class, shared by every `wrapWithGlassFrame`
3
+ * consumer — exported so callers that need to rebuild a wrap's className
4
+ * wholesale (e.g. Avatar/index.ts's `computeControlContainerClass()`, which
5
+ * recomputes `controlWrapEl`'s full class list on every show/hide toggle
6
+ * rather than merging via classList) read it from one place instead of
7
+ * repeating the string literal, which could otherwise drift from this file's
8
+ * own copy.
9
+ */
10
+ export declare const GLASS_WRAP_CLASS = "np_companion-glass-wrap";
11
+ export interface WrapWithGlassFrameOptions {
12
+ /**
13
+ * Extra class(es) for the wrap div — where a caller's own page-position
14
+ * CSS lives, if it needs any (e.g. the control-bar pill's own
15
+ * bottom/left/transform/hover-collapse). Page position was already a
16
+ * separate, per-block concern before this helper existed and stays one.
17
+ */
18
+ wrapClassName?: string;
19
+ }
20
+ /**
21
+ * Wraps `target` (already fully built and styled by its own content
22
+ * builder) in the SDK's one shared wrap div (class `np_companion-glass-wrap`
23
+ * plus any `wrapClassName`) and returns it.
24
+ *
25
+ * Used to carry the translucent glass-frame ring every card in this SDK
26
+ * once rendered (avatar shape, control-bar pill, Entry Point card,
27
+ * MicPermission card) — removed per manager feedback ("треба прибрати всі
28
+ * скляні бордери, не тільки навколо шейпів"), so this now just wraps;
29
+ * kept as the one place every caller's page-position class lands, since
30
+ * that part of the contract is unrelated to the frame that used to live
31
+ * here.
32
+ */
33
+ export declare function wrapWithGlassFrame(target: HTMLElement, options?: WrapWithGlassFrameOptions): HTMLDivElement;
@@ -1,6 +1,7 @@
1
1
  import { FeatureConfig } from "../types";
2
2
  export * from "./classnames";
3
3
  export * from "./domFactory";
4
+ export * from "./glassFrame";
4
5
  export * from "./greenscreen";
5
6
  export * from "./svg";
6
7
  export * from "./sendCommand";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@touchcastllc/napster-companion-api-dev",
3
- "version": "1.6.0-alpha.8",
3
+ "version": "2.0.0-alpha.0",
4
4
  "keywords": [
5
5
  "napster",
6
6
  "companion-api",