Start here → root
AGENTS.md(agent entry) · routerSPEC_INDEX.md· systemARCHITECTURE.md. This is the module's canonical spec: orientation, requirements, design, flows, and tests. Context-efficiency: link to canonical docs — don't duplicate them. Load specs on demand perSPEC_INDEX.md.
| Field | Value |
|---|---|
| Module id | ai-assistant |
| Source path(s) | packages/contact-center/ai-assistant/src/ |
| Doc kind | Module spec |
| Coverage score | Pending coverage assessment |
| Generated from | module-spec @ SDLC template library 0.1.0-draft |
| generated_by / approved_by / updated_at | generated_by ai-assistant feature work / approved_by pending / updated_at 2026-07-29 |
| Validation status | not-run |
Every requirement below cites concrete source evidence using file path. Source evidence, test evidence,
and gaps are kept separate so validators and future agents can distinguish truth from context.
ai-assistant is the AI Assistant widget for Webex Contact Center, published as @webex/cc-ai-assistant.
It owns the assistant's chrome (launcher → open → minimized → fullscreen) and the Real-time Assist
feature: on request, the SDK returns suggestion Adaptive Cards for the active interaction, which the agent
can refine with extra context and act on with like / dislike / copy.
The package is the container layer only. All UI lives in @webex/cc-components
(AIAssistantComponent, RealTimeAssist, AdaptiveCardRenderer), all SDK access goes through
@webex/cc-store, and the Web Component wrapper lives in @webex/cc-widgets. A maintainer should start
at src/ai-assistant/index.tsx (observer container), then src/helper.ts (the two hooks that hold all
behavior), then src/ai-assistant.types.ts (public props).
Owns: assistant chrome state, the real-time assist request lifecycle, chat transcript assembly from
store payloads, and forwarding agent feedback actions to the SDK. Does NOT own: card rendering, panel
markup, SDK transport, or the store's realTimeAssist event plumbing.
TypeScript 5.6.3, React (peer >=18.3.1) function components + hooks, MobX via mobx-react-lite
observer, react-error-boundary. Tests: Jest 29.7.0 + React Testing Library 16.0.1, jsdom. Build:
Webpack 5 to dist/. Source of truth: package.json.
packages/contact-center/ai-assistant/
├── src/
│ ├── index.ts # Barrel: AIAssistant + public types
│ ├── ai-assistant/index.tsx # observer container + ErrorBoundary
│ ├── helper.ts # useAIAssistantChrome, useRealTimeAssist, useAiAssistant
│ └── ai-assistant.types.ts # IAIAssistantProps + hook input types
├── tests/
│ ├── ai-assistant/index.tsx # container render/integration tests
│ ├── ai-assistant/feedback.tsx# like/dislike/copy → SDK tests
│ └── helper.ts # hook unit tests
└── package.json / tsconfig* / webpack.config.js / jest.config.js
| File | Holds |
|---|---|
packages/contact-center/ai-assistant/src/index.ts |
Public export barrel (AIAssistant + types). |
packages/contact-center/ai-assistant/src/ai-assistant/index.tsx |
Store wiring: feature flag, active interaction, logger, deprecated-callback bridge, ErrorBoundary. |
packages/contact-center/ai-assistant/src/helper.ts |
All behavior: chrome transitions, request lifecycle, chat assembly, feedback dispatch, REAL_TIME_ASSIST_FLAG. |
packages/contact-center/ai-assistant/src/ai-assistant.types.ts |
IAIAssistantProps — the host-facing contract. |
packages/contact-center/cc-widgets/src/wc.ts |
widget-cc-ai-assistant custom element and its prop map. |
| Contract ID | Type | Surface | Purpose | Compatibility | Detail |
|---|---|---|---|---|---|
ai-assistant.AIAssistant |
SDK | <AIAssistant {...IAIAssistantProps} /> |
React entry point for the widget. | stable | src/ai-assistant/index.tsx |
ai-assistant.IAIAssistantProps |
SDK | Host callbacks + className |
Opt-in host notifications. | additive props = minor | src/ai-assistant.types.ts |
widget-cc-ai-assistant |
Custom element | r2wc wrapper | Web Component host integration. | prop map must mirror IAIAssistantProps |
packages/contact-center/cc-widgets/src/wc.ts |
onSuggestionReceived is deprecated in favour of onRealTimeAssistReceived; the container accepts either
(src/ai-assistant/index.tsx) and both are registered on the custom element.
@webex/cc-store—store.cc.apiAIAssistant(getRealTimeAssistance,sendRealTimeAssistanceUserAction),store.realTimeAssist,store.currentTask,store.agentId,store.agentProfile,store.featureFlags,store.logger.@webex/cc-components—AIAssistantComponentand its types.@webex/cc-ui-logging—withMetrics(applied insidecc-components).mobx-react-lite,react-error-boundary.
| ID | WHAT | WHY | Source Evidence | Test Evidence | Confidence |
|---|---|---|---|---|---|
ai-assistant-R-001 |
Chrome moves closed → open → minimized → open and fires the matching host callback on each transition; closing preserves chat state. | The agent must be able to park the panel mid-interaction without losing context. | src/helper.ts (useAIAssistantChrome) |
tests/helper.ts, tests/ai-assistant/index.tsx |
PRESENT |
ai-assistant-R-002 |
The landing view is shown when the feature flag is off or no interaction is active, and requestStatus stays idle in that case (never error). |
Absence of a call is not a failure; showing an error would be misleading. | packages/contact-center/cc-components/src/components/AIAssistant/ai-assistant.tsx, src/helper.ts (early return in requestRealTimeAssist) |
packages/contact-center/cc-components/tests/components/AIAssistant/ai-assistant.tsx |
PRESENT |
ai-assistant-R-003 |
The opening prompt stays until the first getRealTimeAssistance call resolves: in-flight shows a spinner, failure keeps the prompt plus an error message, success switches to listening mode permanently. |
A failed first request must be retryable; later failures must not unwind an established session. | src/helper.ts (hasInitialRequestSucceeded set after await), packages/contact-center/cc-components/src/components/AIAssistant/RealTimeAssist/real-time-assist.tsx |
packages/contact-center/cc-components/tests/components/AIAssistant/ai-assistant.tsx, tests/helper.ts |
PRESENT |
ai-assistant-R-004 |
Concurrent requests are suppressed: while a getRealTimeAssistance call is in flight, further requests are ignored until it settles. |
Double-clicking a request control must not fan out duplicate SDK calls. | src/helper.ts (inFlightRef) |
tests/helper.ts |
PRESENT |
ai-assistant-R-005 |
Every unseen realTimeAssist payload invokes onRealTimeAssistReceived once, even when several land before React commits. |
Hosts mirror the transcript; a dropped payload is unrecoverable. | src/helper.ts (response effect loops from the previous cursor) |
tests/helper.ts |
PRESENT |
ai-assistant-R-006 |
Changing interactionId resets request status, error, context draft, pending flag, first-success flag, user messages, and the payload cursor. |
Session state from one customer must never surface on the next. | src/helper.ts (reset effect keyed on interactionId) |
tests/helper.ts |
PRESENT |
ai-assistant-R-007 |
chatEntries is a chronological transcript — greeting (only after first success), then user and assistant entries ordered by timestamp, user first on ties. |
The agent reads context and suggestion in the order they happened. | src/helper.ts (chatEntries memo) |
tests/helper.ts |
PRESENT |
ai-assistant-R-008 |
Like / dislike / copy call sendRealTimeAssistanceUserAction and return its promise; a rejection is logged and reverts the card's optimistic state. |
The UI must not claim feedback was recorded when it was not. | src/helper.ts (handleRealTimeAssistAction), packages/contact-center/cc-components/src/components/AIAssistant/AdaptiveCardRenderer/adaptive-card-renderer.tsx (captureControlState) |
tests/ai-assistant/feedback.tsx |
PRESENT |
ai-assistant-R-009 |
When the action cannot be sent (missing adaptiveCardId, ids, or SDK method), the reason is logged via store.logger.warn and the returned promise rejects. |
A silent no-op leaves both agent and support blind. | src/helper.ts |
tests/ai-assistant/feedback.tsx |
PRESENT |
ai-assistant-R-010 |
A render failure is contained by the widget's ErrorBoundary and reported through store.onErrorCallback('AIAssistant', error). |
A crashing assistant must not take the agent desktop down. | src/ai-assistant/index.tsx |
tests/ai-assistant/index.tsx |
PRESENT |
src/ai-assistant/index.tsx is a thin observer container: it reads the store, derives
isFeatureEnabled from featureFlags[REAL_TIME_ASSIST_FLAG] and the active interaction from
currentTask.data.interactionId, resolves the deprecated callback alias, and hands everything to
useAiAssistant, whose result is spread straight onto AIAssistantComponent.
src/helper.ts holds two independent hooks composed by useAiAssistant:
useAIAssistantChrome— chrome state machine plus host callbacks. Pure UI state, no SDK.useRealTimeAssist— the request lifecycle.requestRealTimeAssist(context?)is the single entry point for both the initial request and extra-context refinements; the presence ofcontextdistinguishes them. It early-returns toidlewhen the feature is off or there is no interaction, guards against overlapping calls withinFlightRef, and only flipshasInitialRequestSucceededafter the SDK call resolves. Store payloads arrive asynchronously throughstore.realTimeAssist, so a separate effect watches that array, advances a cursor, and replays every unseen entry to the host.
Refs (pendingRequestRef, requestStatusRef, onRealTimeAssistReceivedRef) keep the response effect
dependent on realTimeAssist alone, so unrelated state changes don't replay payloads.
graph LR
Agent[Agent] -->|Get Assistance / Send context| RTA[RealTimeAssist UI]
RTA -->|requestRealTimeAssist| Hook[useRealTimeAssist]
Hook -->|getRealTimeAssistance| API["store.cc.apiAIAssistant"]
API --> SDK[(Contact Center SDK)]
SDK -->|SUGGESTED_RESPONSE event| SW["storeEventsWrapper"]
SW -->|"store.realTimeAssist[interactionId]"| Container[AIAssistant container]
Container --> Hook
Hook -->|chatEntries| RTA
RTA -->|like / dislike / copy| Hook
Hook -->|sendRealTimeAssistanceUserAction| API
| Operation group | Diagram | Failure coverage |
|---|---|---|
| First request → suggestion → feedback | "Real-time assist round trip" | request rejection keeps the opening prompt; feedback rejection reverts the card |
sequenceDiagram
participant A as Agent
participant UI as RealTimeAssist
participant H as useRealTimeAssist
participant S as store / SDK
A->>UI: Get Assistance
UI->>H: requestRealTimeAssist()
H->>H: inFlight guard, status = listening
H->>S: getRealTimeAssistance({agentId, interactionId, actionTimeStamp})
alt resolves
S-->>H: ok
H->>H: hasInitialRequestSucceeded = true
S-->>H: SUGGESTED_RESPONSE payload(s)
H->>UI: chatEntries + status ready
else rejects
S-->>H: error
H->>UI: stay on prompt, show error, keep button
end
A->>UI: Like
UI->>H: onRealTimeAssistAction
H->>S: sendRealTimeAssistanceUserAction
alt rejects
S-->>H: error
H->>UI: log + reject → card reverts icon
end
- UC-1 Ask for a suggestion: agent opens the panel during a call and clicks "Get Assistance" → spinner → listening mode with the assistant greeting; cards stream in as the conversation progresses.
- UC-2 Refine with context: agent types context and sends → an
ADD_SUGGESTIONS_EXTRA_CONTEXTrequest goes out, the message appears in the transcript, and the panel remains in listening mode whether or not it succeeds. - UC-3 Give feedback: agent likes/dislikes/copies a card → the SDK records the action; a failure reverts the icon.
- UC-4 No active call: agent opens the panel with no interaction or the feature disabled → landing view listing AI features, no error state.
| Condition | Signal | Recovery |
|---|---|---|
| Feature disabled / no interaction | requestStatus stays idle; landing view |
None needed — informational state |
First getRealTimeAssistance rejects |
requestStatus = 'error', message under the prompt |
Agent clicks the button again |
| Later request rejects | Panel stays in listening mode | Next successful push resumes the transcript |
| Overlapping request | Ignored while one is in flight | Wait for the current call to settle |
| Feedback action fails | store.logger.error, promise rejects, card reverts |
Agent can click again |
| Render crash | ErrorBoundary renders nothing, store.onErrorCallback('AIAssistant', error) |
Host decides |
hasInitialRequestSucceededmeans the first request resolved, not a request was fired. Setting it before theawaitwould strand the agent in listening mode after a failure.requestStatus === 'listening'is the steady post-success state, not an "in flight" flag. UseinFlightRef/pendingRequestfor concurrency questions.- The response effect must depend on
realTimeAssistonly; adding state to its dependency array replays payloads to the host. - The widget never imports the SDK directly — everything goes through
store.cc. cc-componentscannot import the store, so failures there (clipboard, card render) surface through theloggerprop this package passes down.
- DO put behavior in
helper.tsand keepai-assistant/index.tsxa thin store-to-props adapter. - DO pass
store.loggerdown socc-componentscan report failures. - DON'T import from
@webex/cc-widgets(circular) or from@webex/contact-centerdirectly. - DON'T add UI markup to this package; it belongs in
cc-components. - DON'T remove
onSuggestionReceivedwithout a major bump; bridge it instead.
tests/helper.ts drives the hooks with renderHook: chrome transitions, request success/failure, the
in-flight guard, interaction reset, batched payload replay, and transcript ordering.
tests/ai-assistant/index.tsx renders the container against a mocked store for the landing/listening
views and the ErrorBoundary path. tests/ai-assistant/feedback.tsx covers like/dislike/copy dispatch,
the missing-adaptiveCardId warning, and revert-on-rejection. UI-level rendering (spinner, error text,
snapshots) is covered in cc-components/tests/components/AIAssistant/.
- Repo architecture:
../../../../ai-docs/ARCHITECTURE.md· Registry:../../../../ai-docs/SPEC_INDEX.md - Coverage state & contracts baseline:
.sdd/manifest.json