Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
22ac94c
feat(support): add Kevin AI support chat to the studio footer
AnkhHeart Aug 20, 2026
5e67963
feat(v2): point Kevin support at /v2 and expose OBS tools to the agent
AnkhHeart Aug 21, 2026
b5d24dc
fix(support): render the knowledge-source link footer
AnkhHeart Aug 22, 2026
1c9fc08
style(v2): let Prettier own the local protocol.ts copy
AnkhHeart Aug 22, 2026
e74da0c
chore(v2): sync protocol contract to tool version 2
AnkhHeart Aug 22, 2026
3f5a7fd
feat(v2): update tool protocol version to 3 with poll management enha…
AnkhHeart Aug 24, 2026
7c8b7b9
Add the agent's support tools: mic filters, health, diagnostics, ticket
AnkhHeart Aug 27, 2026
58d7852
Show the interaction quota in support chat, and offer Ultra at the limit
AnkhHeart Aug 27, 2026
f379d14
Redesign Kevin support chat to match Figma: reposition footer icon, r…
AnkhHeart Sep 4, 2026
0573c08
refactor: remove unused sendTrigger method from StreamAvatarApiService
AnkhHeart Sep 4, 2026
0327ccf
Address PR review: a11y, safe links, and error/log handling in Kevin …
AnkhHeart Sep 4, 2026
5bfbb8d
feat(tool): add watch_replay
AnkhHeart Sep 9, 2026
d9a33c3
feat(bubble): add kevin bubble when window is not visible for approvals
AnkhHeart Sep 9, 2026
3523aee
fix(pr): copilot review fixes
AnkhHeart Sep 9, 2026
66325af
chore(protocol): remove outdated comments and documentation
AnkhHeart Sep 9, 2026
a4c069a
fix(pr): comments implemented
AnkhHeart Sep 10, 2026
b7bc7d6
feat(analytics): track generic events
AnkhHeart Sep 10, 2026
27cb76c
fix(kevin-support): reset error state on run start
AnkhHeart Sep 10, 2026
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
6 changes: 6 additions & 0 deletions app/app-services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export { StreamAvatarService } from 'services/stream-avatar/stream-avatar-servic
export { StreamAvatarApiService } from 'services/stream-avatar/stream-avatar-api-service';
export { AutomationsService } from 'services/stream-avatar/automations-service';
export { AutomationsEngineService } from 'services/stream-avatar/automations-engine-service';
export { KevinSupportService } from 'services/stream-avatar/kevin-support-service';
export { AgentToolsService } from 'services/stream-avatar/v2/agent-tools';
export { OnboardingV2Service } from 'services/onboarding/onboarding-v2';

// ONLINE SERVICES
Expand Down Expand Up @@ -214,6 +216,8 @@ import { VirtualWebcamService } from 'services/virtual-webcam';
import { StreamAvatarApiService } from 'services/stream-avatar/stream-avatar-api-service';
import { AutomationsService } from 'services/stream-avatar/automations-service';
import { AutomationsEngineService } from 'services/stream-avatar/automations-engine-service';
import { KevinSupportService } from 'services/stream-avatar/kevin-support-service';
import { AgentToolsService } from 'services/stream-avatar/v2/agent-tools';

export const AppServices = {
AppService,
Expand Down Expand Up @@ -305,4 +309,6 @@ export const AppServices = {
StreamAvatarApiService,
AutomationsService,
AutomationsEngineService,
KevinSupportService,
AgentToolsService,
};
328 changes: 328 additions & 0 deletions app/components-react/agent/KevinSupport.m.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
@import '../../styles/index';
@import '../../styles/ultra';
// index.less doesn't pull in the raw color atoms — @navy and @white below need
Comment thread
AnkhHeart marked this conversation as resolved.
Outdated
// this directly.
@import '../../styles/colors';

// Figma tokens map onto the night-theme palette 1:1:
// background/base #17242D -> --background (content area)
// background/dark #09161D -> --section (composer strip)
// stroke/default #2B383F -> --border
// surface/dropdown #2B383F -> --section-alt (bot bubble fill, @dark-4)
// navy/navy #233A4A -> @navy (user bubble fill, fixed dark)
.window {
// Nothing inside may make the page itself scrollable: the window's title bar
// sits above this element in normal flow, so a scrolled document hides it.
overflow: hidden;
}

.body {
display: flex;
flex-direction: column;
padding: 0 !important;
height: 100%;
overflow: hidden;
background-color: var(--background);
}

.content {
display: flex;
flex: 1 0 0;
flex-direction: column;
gap: 20px;
min-height: 0;
padding: 20px 24px;
}

// -- empty state -------------------------------------------------------------

.empty-state {
display: flex;
flex: 1 0 0;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 0;
padding: 16px 0;
text-align: center;
}

.empty-title {
.weight(@bold);

margin: 6px 0 0;
color: var(--title);
font-size: 18px;
}

.empty-body {
max-width: 420px;
margin: 0;
color: var(--paragraph);
font-size: 13px;
line-height: 1.5;
}

// -- suggested prompts -------------------------------------------------------

.suggestions {
display: flex;
flex-direction: column;
gap: 12px;
}

.suggestions-title {
.weight(@bold);

color: var(--paragraph);
font-size: 11px;
text-transform: uppercase;
}

.prompt-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
}

.prompt {
.weight(@medium);

padding: 8px 12px;
color: var(--title);
font-size: 12px;
background-color: var(--section);
border: 1px solid var(--border);
border-radius: 100px;
cursor: pointer;

&:hover {
border-color: var(--teal);
}

&:focus {
outline: none;
}
}

// -- transcript --------------------------------------------------------------

.messages {
flex: 1 0 0;
min-height: 0;
}

.message {
display: flex;
gap: 10px;
align-items: flex-start;
margin-bottom: 20px;
}

.from-user {
justify-content: flex-end;
}

.from-agent {
justify-content: flex-start;
}

// The Figma badge draws at roughly 16x14 inside its 28px frame; the top margin
// nudges it down onto the bubble's first line of text instead of its own top.
.avatar {
flex-shrink: 0;
width: 16px;
height: 14px;
margin-top: 7px;
}

.bubble {
max-width: 550px;
padding: 10px 14px;
color: var(--title);
font-size: 14px;
line-height: 1.5;
white-space: pre-wrap;
overflow-wrap: anywhere;
background-color: var(--section-alt);
border: 1px solid var(--border);
border-radius: 0 12px 12px 12px;

.from-user & {
// @navy is a fixed dark fill in every theme (the codebase already does
// this for --info-badge), so `color` has to be a literal white too --
// inheriting var(--title) would go unreadable in the day theme.
background-color: @navy;
border-color: #587070; // Figma stroke/focus -- no palette atom for it
color: @white;
border-radius: 12px 12px 0 12px;
}
}

.link {
color: var(--teal);
text-decoration: underline;
cursor: pointer;
}

// -- errors ------------------------------------------------------------------

.error {
padding: 8px 24px;
color: var(--warning);
font-size: 12px;
}

// -- composer ----------------------------------------------------------------

.composer {
display: flex;
flex-shrink: 0;
gap: 12px;
align-items: flex-end;
padding: 12px 16px;
background-color: var(--section);
border-top: 1px solid var(--border);
}

.input {
flex: 1 0 0;
min-width: 0;
padding: 8px 12px !important;
color: var(--title);
font-size: 13px;
background-color: var(--background) !important;
border: 1px solid var(--border) !important;
border-radius: 6px !important;
resize: none;

&:focus {
border-color: var(--focus-border) !important;
box-shadow: none !important;
}
}

.send {
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
height: 36px;
padding: 8px 16px;
}

/* Inline tool-approval card. Shares the agent bubble, but wider and with a
left accent so it reads as a decision point rather than another reply. */
.approval {
.bubble {
max-width: 100%;
// The bubble's default radius rounds its bottom-left corner, which would
// clip the straight warning accent below -- square both left corners here.
border-radius: 0 12px 12px 0;
border-left: 3px solid var(--warning);
}
}

.approvalSummary {
font-weight: 600;
margin-bottom: 4px;
}

.approvalWarning {
color: var(--warning);
font-size: 12px;
margin-bottom: 8px;
}

.approvalActions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;

button {
margin: 0;
}
}

/* Shown when an approval is the only thing in the window, so the card is not
floating with no explanation of where it came from. */
.approvalContext {
color: var(--paragraph);
font-size: 12px;
text-align: center;
padding: 12px 16px 4px;
}

// -- interaction quota --------------------------------------------------------
// Mirrors the Automations usage meter (EditAutomations.m.less) so the two read
// as the same control. Right-aligned and out of the flex flow so it sits in the
// window's top-right corner without taking height from the conversation.

.usage-meter {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6px;
flex-shrink: 0;
margin-bottom: -8px;
font-size: 13px;
white-space: nowrap;
}

.usage-row {
display: flex;
align-items: center;
gap: 8px;
}

.usage-text {
color: var(--title);
}

.usage-info {
color: var(--icon);
cursor: help;
}

.usage-track {
width: 140px;
height: 6px;
border-radius: 3px;
background: var(--border);
overflow: hidden;
}

.usage-fill {
height: 100%;
border-radius: 3px;
background: var(--teal);
transition: width 150ms ease;
}

// Spent, not merely nearly spent. The bar is the thing seen at a glance, so it
// should not still read as healthy on the request that gets refused.
.usage-fill-full {
background: var(--warning);
}

.upgrade-link {
display: inline-flex;
align-items: center;
gap: 6px;
font-weight: 600;
cursor: pointer;

&:hover .upgrade-text {
opacity: 0.85;
}
}

.upgrade-text {
.ultra-text('warm');
}

.at-cap-note {
color: var(--paragraph);
}
Loading
Loading