Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ai-docs/CONTRACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The React entry of `@webex/cc-widgets` re-exports the widgets, including `AIAssi
| ai-assistant.wellness-types | `@webex/cc-ai-assistant` | `WellnessBreakPhase`, `WellnessBreakErrorCode`, `WellnessBreakError`, `WellnessBreakOverlayTarget` | Exported TypeScript lifecycle, error, and layout contracts | additive minor surface; literals are stable | `packages/contact-center/ai-assistant/ai-docs/ai-assistant-spec.md` | `packages/contact-center/ai-assistant/src/ai-assistant.types.ts` |
| cc-components.WellnessBreak | `@webex/cc-components` | `WellnessBreakRequestCard`, `WellnessBreakOfferCard`, `WellnessBreakModal`, `WellnessBreakError` | Props-only Agent Wellness Break presentation; modal accepts an optional animation loader supplied by AI Assistant and owns no wellness media | monorepo component surface; no SDK/store or Lottie dependency | `packages/contact-center/cc-components/ai-docs/cc-components-spec.md` | `packages/contact-center/cc-components/src/components/AIAssistant/WellnessBreak/` |
| cc-widgets.store | `@webex/cc-store` | `store` | MobX singleton (`Store.getInstance()`); `init(options: InitParams, setupEventListeners): Promise<void>`; sole SDK access point via `store.cc.*` | stable semver | `packages/contact-center/store/ai-docs/store-spec.md` | `packages/contact-center/store/src/index.ts:1,4` |
| store.ai-summary | `@webex/cc-store` | `cc.aiSummaries`, summary request/receiver-feedback/send methods, `AISummaryEntry`, `AISummaryRole`, `AISummaryResponseSubmissionResult` | Entries keyed by interaction and role retain the unchanged SDK `AISummary` in `content` and `AISummaryResponse` in `response`. Requests return `AISummary` or `undefined`; senders return `AISummaryResponseSubmissionResult`. Widget helpers own local editing, copy tracking and deferred feedback through response fields. `setReceiverSummaryFeedback(feedback, expectedSummary, task)` uses the displayed SDK payload to guard its asynchronous acknowledgement. Consumers read enablement from `task.aiSummaryCapabilities`; `task:featureEnablement` refreshes the task view. Wrap-up sends `sendPostCallSummaryResponse(wrapUpCode, originalTask)` before `task.wrapup()`. | additive; uses published `@webex/contact-center` `3.12.0-next.148` | `packages/contact-center/store/ai-docs/store-spec.md` (AI summaries) | `packages/contact-center/store/src/store.types.ts`, `packages/contact-center/store/src/storeEventsWrapper.ts` |
| store.host-init.enableWxBetterTogether | `@webex/cc-store` | `webexConfig.cc.enableWxBetterTogether` → `store.enableWxBetterTogether` | Host init boolean (default `false`), set **before** `store.init()`: read from `webexConfig.cc` on `{ webexConfig, access_token }` init or `webex.config.cc` on `{ webex }` init; read-only observable after init (Phase 1: re-init to change). Gates wxApp Mute/Keypad visibility and wxApp mute seed; SDK owns mute/DTMF API routing. | stable semver; renaming or removing the init key or store getter is breaking | `packages/contact-center/store/ai-docs/store-spec.md` (`STORE-R-030`); `packages/contact-center/task/ai-docs/task-spec.md` (WXCC-6026) | `packages/contact-center/store/src/store.ts`, `packages/contact-center/store/src/store.types.ts` (`WebexCcInitConfig`) |
| store.types | `@webex/cc-store` | Existing queue/entry-point request, response, and entity type re-exports plus `TaskUIControls` | TypeScript exports describing the SDK-backed domain surface; established entity rows and Task destination controls pass through without a widget destination abstraction | stable semver; SDK-shaped types track SDK | `@webex/contact-center` types (`node_modules/@webex/contact-center/dist/types/index.d.ts`) (SDK source); `packages/contact-center/store/ai-docs/store-spec.md` | `packages/contact-center/store/src/store.types.ts` |
| cc-components.consult-transfer-lists | `@webex/cc-components` | `CallControl` consult/transfer fetch props plus `action`, `availableDestinations`, and optional list-item `presence` | `FetchPaginatedList<ContactServiceQueue>` / `FetchPaginatedList<EntryPointRecord>` and the SDK-ordered destination array from `TaskUIControls`; UI preserves list/category order, passes buddy availability to Momentum Avatar as semantic presence, shows `AddressBookEntry.number` and `EntryPointRecord.number` subtitles, and may only apply host hide overrides | stable semver; entity and control types track store/SDK contracts; optional presentation prop is additive | `packages/contact-center/cc-components/ai-docs/cc-components-spec.md`; `ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md` | `packages/contact-center/cc-components/src/components/task/task.types.ts` |
Expand Down
93 changes: 91 additions & 2 deletions packages/contact-center/store/ai-docs/store-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Owns Contact Center client-side state and the SDK boundary: initialize/register

## Stack

TypeScript 5.6.3, MobX 6.13.5 (`makeAutoObservable`, `observable.ref`, `runInAction`). Consumed in React 18 via `mobx-react-lite` `observer()` in downstream packages (not a dependency of this package itself). SDK dependency `@webex/contact-center` 3.12.0-next.131. Tests: Jest 29 + ts compile (`tsc --project tsconfig.test.json && jest --coverage`). Build target: `dist/index.js` (Webpack). Evidence: `packages/contact-center/store/package.json`.
TypeScript 5.6.3, MobX 6.13.5 (`makeAutoObservable`, `observable.ref`, `runInAction`). Consumed in React 18 via `mobx-react-lite` `observer()` in downstream packages (not a dependency of this package itself). SDK dependency `@webex/contact-center` 3.12.0-next.148. Tests: Jest 29 + ts compile (`tsc --project tsconfig.test.json && jest --coverage`). Build target: `dist/index.js` (Webpack). Evidence: `packages/contact-center/store/package.json`.

## Folder / Package Structure

Expand Down Expand Up @@ -94,7 +94,7 @@ Compatibility notes:

## Requires (dependencies)

- `@webex/contact-center` SDK (pinned in `package.json` at `3.12.0-next.131`) — the entire CC runtime: `Webex.init()`, `webex.cc.*` methods, the CC/task event stream, agent `Profile`, `webex.credentials.getUserToken()`. Consumed ONLY through the store. Fallback on unavailability: `Store.init()` rejects after a 6000ms timeout (`src/store.ts:140-142`); the wrapper wraps the rejection and invokes `onErrorCallback('Store', err)` (`src/storeEventsWrapper.ts:442-452`).
- `@webex/contact-center` SDK (pinned in `package.json` at `3.12.0-next.148`) — the entire CC runtime: `Webex.init()`, `webex.cc.*` methods, the CC/task event stream, agent `Profile`, `webex.credentials.getUserToken()`. Consumed ONLY through the store. Fallback on unavailability: `Store.init()` rejects after a 6000ms timeout (`src/store.ts:140-142`); the wrapper wraps the rejection and invokes `onErrorCallback('Store', err)` (`src/storeEventsWrapper.ts:442-452`).
- `mobx` ^6.13.5 — observable state and `runInAction` for all mutations.
- Internal: none upstream. The store is the lowest widget-layer dependency (`cc-components → widget packages → store → SDK`); it imports no widget package.

Expand Down Expand Up @@ -397,6 +397,95 @@ Unit tests are split by source file. `tests/store.ts` covers the singleton defau
| `STORE-R-030` | `tests/store.ts` (`enableWxBetterTogether` init paths and default) | none |
| `STORE-R-031` | `tests/storeEventsWrapper.ts` ("should remove task callback even when task is absent from store.taskList", legacy string-id fallback cases) | none |

## AI summaries

`StoreWrapper` is the SDK boundary for summaries. `cc.aiSummaries[interactionId][role]`
keeps the SDK summary, SDK response record and request state for `initiator`, `receiver`,
and `post-call`. Store requests and receiver events update these records inside
`runInAction`. Widget helpers own editing, copy tracking and deferred initiator/post-call
feedback through the SDK response fields; the store exposes no local UI action helpers.
A new SDK result replaces the unchanged `content` and initializes a fresh response.
Refreshing or failing a request retains
content already displayed and its response record; task removal and logout clear them.

The store observes `cc` by reference. `registerCC` initializes the widget-owned
`cc.aiSummaries` map when the SDK does not provide one; it does not make SDK fields
observable. SDK events and request results update the map, while current consumers
read it through the summary APIs. A component that directly observes this map in the
future will need an observable state path.

An agent can receive a transferred call and then consult or transfer it again. The
received card remains in AI Assistant while the initiator summary is used in the
consult/transfer modal, so their content, copies and feedback stay independent.
The post-call entry holds the wrap-up summary. Grouping these entries under the task's
interaction ID lets task cleanup remove them together; entries are created as needed.

- `requestMidCallSummary(action, task)` and `requestPostCallSummary(task)` each call their
corresponding Task SDK method directly when `task.aiSummaryCapabilities` enables that
summary kind, and return the SDK `AISummary` or `undefined`. Like suggested responses,
summary requests and responses check the SDK flags directly, with no additional media-type
restriction. Disabled requests are quiet UI no-ops rather than SDK validation errors.
The SDK owns validation, transport correlation and timeouts. The store keeps a request
generation only to stop an older local Promise settlement, including a delayed SDK
publish failure, from overwriting a newer UI request. It does not correlate backend replies.
- `TASK_FEATURE_ENABLEMENT` (`task:featureEnablement`) refreshes the task view after the
SDK updates `task.aiSummaryCapabilities`. The current task is refreshed so observers
read the latest flags, including enablement arriving after the task and later disabling
the feature. The SDK owns the flags; the store adds no capability map or enablement helper.
`TASK_MID_CALL_SUMMARY_RECEIVED` supplies the receiver summary. Listeners are rebound
together when a task object is replaced, registered once per task object, and detached
on task removal and logout.
- Every role stores the complete SDK `AISummary` in `content`, including sections,
summary text, cards and SDK metadata. The store does not filter sections, trim text,
choose a display format or reject a received payload because of its format. Components
choose what they display from the SDK fields. Widget helpers keep edits in
`response.summary` and retain the original SDK content as their comparison baseline.
Consumers use the request status and SDK `areTranscriptsAvailable` flag to select a
display state. A failed refresh keeps previously received content visible. Invalid
transport payloads belong to the SDK boundary.
- Each entry stores an SDK `AISummaryResponse` in `response`. Widget helpers update its
copy, edit and deferred-feedback fields; senders forward that record to the SDK.
The SDK sends these fields verbatim and supplies no runtime defaults. Received
summaries start with `DEFAULT` and one view. Requests without content start with
`IGNORED`, including retries; failure without content changes the state to `NOT_RECEIVED`.
Structured responses start with `{}` and plain-text responses start with SDK summary text.
- `setReceiverSummaryFeedback(feedback, expectedSummary, task)` submits receiver feedback
immediately. It accepts the displayed SDK payload itself, with no revision counter.
The store blocks overlapping sends and applies the acknowledgement only while that
same SDK payload remains current. Replacement events and task removal invalidate it.
A successful send selects the feedback; a failed send preserves the previous selection.
Receiver responses use SDK `summaryText`, including when the payload also has sections
or an adaptive card; absent text is sent as `''`.
- `sendMidCallSummaryResponse(action, task)` and `sendPostCallSummaryResponse(wrapUpCode, task)`
return `AISummaryResponseSubmissionResult`: `submitted` after SDK acknowledgement,
`response-failed` after a failed send, or `not-applicable` when the required task or
summary capability is absent. Failed sends are logged and resolve with an outcome so
the initiating consult, transfer or wrap-up action can continue.
- `sendPostCallSummaryResponse(wrapUpCode, task)` adds the wrap-up code to the SDK response
record and sends it on that task before `task.wrapup()` runs. This keeps the exact task
context available and removes the need for callers to capture and later submit a response.
If no summary entry exists, it sends an ignored response without creating an entry. It
resolves to `submitted` or `response-failed`; disabled post-call summaries return
`not-applicable`.

The integration consumes `AISummary`, `AISummarySections`, `AISummaryResponse`,
`AISummaryAction`, and `AISummaryFeedback` from `@webex/contact-center`.
These APIs come from published SDK `3.12.0-next.148`. The store and React sample pin
that version; the root resolution also selects it for the sample's transitive Webex
SDK dependency. Three scoped resolutions replace unavailable transitive versions:
Device `3.12.0-next.55` uses published `next.54`, and Metrics `3.12.0-next.56` uses
published `next.55`; Mercury `3.12.0-next.62` uses published `next.61`. Remove these
resolutions once the referenced versions are published. No local SDK archive is used.
The React sample uses the SDK-supported `User requested logout` reason for page-unload logout.
`AISummary` is the common incoming payload type; `AISummaryResponse` is the distinct
feedback payload sent back to the SDK. The store retains both types directly. The UI
entry adds request lifecycle, local request generation, the pending receiver-feedback
flag and the consult/transfer action. Copy counts, edits and feedback
use SDK response fields rather than duplicate widget fields.

Evidence: `src/storeEventsWrapper.ts`, `src/store.types.ts`, `src/task-utils.ts`;
tests: the AI summary cases in `tests/storeEventsWrapper.ts` and `tests/task-utils.ts`.

## Traceability

- Repo architecture: [`ARCHITECTURE.md`](../../../../ai-docs/ARCHITECTURE.md) · Registry: [`SPEC_INDEX.md`](../../../../ai-docs/SPEC_INDEX.md) · Contracts: [`CONTRACTS.md`](../../../../ai-docs/CONTRACTS.md)
Expand Down
2 changes: 1 addition & 1 deletion packages/contact-center/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"deploy:npm": "yarn npm publish"
},
"dependencies": {
"@webex/contact-center": "3.12.0-next.133",
"@webex/contact-center": "3.12.0-next.148",
"mobx": "6.13.5",
"typescript": "5.6.3"
},
Expand Down
1 change: 1 addition & 0 deletions packages/contact-center/store/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class Store implements IStore {
registerCC(webex?: WithWebex['webex']): Promise<void> {
if (webex) {
this.cc = webex.cc;
this.cc.aiSummaries ??= {};
}

if (typeof webex === 'undefined' && typeof this.cc === 'undefined') {
Expand Down
48 changes: 48 additions & 0 deletions packages/contact-center/store/src/store.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ import {
WellnessBreakEvent,
RespondToWellnessBreakParams,
} from '@webex/contact-center';
import type {
AISummary,
AISummaryAction,
AISummaryFeedback,
AISummaryResponse,
AISummarySections,
} from '@webex/contact-center';
import type {RealTimeAssistanceParams} from 'node_modules/@webex/contact-center/dist/types/types';
import {
OutdialAniEntriesResponse,
Expand All @@ -52,6 +59,8 @@ import {

// To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
interface IContactCenter {
/** Widget-owned AI summary state associated with this SDK contact-center instance. */
aiSummaries: Record<string, Partial<Record<AISummaryRole, AISummaryEntry>>>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on: (event: string, callback: (data: any) => void) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -319,6 +328,7 @@ interface IStore {
wellnessEventSequence: number;
legacyAgentState: string;
legacyAuxCodeId: string;

init(params: InitParams, callback: (ccSDK: IContactCenter) => void): Promise<void>;
registerCC(webex?: WithWebex['webex']): Promise<void>;
loadWellbeingBreakIdleCode(): Promise<void>;
Expand Down Expand Up @@ -371,6 +381,16 @@ interface IStoreWrapper extends IStore {
setWellnessBreakState(state: WellnessBreakState): void;
submitBehavioralMetric(metric: WidgetsBehavioralMetric): void;
resetWellnessSession(): void;

requestMidCallSummary(action: AISummaryAction, task?: ITask): Promise<AISummary | undefined>;
requestPostCallSummary(task?: ITask): Promise<AISummary | undefined>;
setReceiverSummaryFeedback(
feedback: Exclude<AISummaryFeedback, 'none'>,
expectedSummary: AISummary,
task?: ITask
): Promise<void>;
sendMidCallSummaryResponse(action: AISummaryAction, task?: ITask): Promise<AISummaryResponseSubmissionResult>;
sendPostCallSummaryResponse(wrapUpCode: string, task: ITask): Promise<AISummaryResponseSubmissionResult>;
}

interface IWrapupCode {
Expand Down Expand Up @@ -524,6 +544,10 @@ export type {
WidgetsBehavioralMetric,
WidgetsBehavioralMetricAgent,
WidgetsBehavioralMetricVerb,
AISummaryAction,
AISummaryFeedback,
AISummaryResponse,
AISummarySections,
};

export {
Expand Down Expand Up @@ -649,3 +673,27 @@ export const CAMPAIGN_PREVIEW_OUTBOUND_TYPES = ['STANDARD_PREVIEW_CAMPAIGN', 'DI

/** Campaign type values (from callProcessingDetails) that identify a campaign preview task. */
export const CAMPAIGN_PREVIEW_CAMPAIGN_TYPES = ['preview_standard', 'preview_direct'];

/** The consult/transfer initiator's summary, the receiving agent's summary, or the wrap-up summary. */
export type AISummaryRole = 'initiator' | 'receiver' | 'post-call';

/** One summary of an interaction, stored at `cc.aiSummaries[interactionId][role]`. */
export type AISummaryEntry = {
/** Lifecycle of the latest request, or of the pushed receiver summary. */
status: 'loading' | 'ready' | 'error';
/**
* The unchanged SDK summary, retained while a newer request loads or after it fails.
*/
content?: AISummary;
/** SDK submission record; widget helpers own local edits, copy counts and deferred feedback. */
response: AISummaryResponse;
/** Orders local request promise settlements; SDK transport correlation remains SDK-owned. */
requestGeneration?: number;
/** The receiving agent's feedback response is being sent. */
feedbackPending?: boolean;
/** The consult or transfer a mid-call summary was generated for. */
action?: AISummaryAction;
};

/** Outcome of sending a mid-call or post-call summary response through the SDK. */
export type AISummaryResponseSubmissionResult = 'submitted' | 'response-failed' | 'not-applicable';
Loading
Loading