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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export {
APPLICATION_IDENTIFIER_PLACEHOLDER,
buildAgentChatPrompt,
buildAgentChatTuiCommand,
buildAgentChatTuiCommandForDisplay,
NOVU_CONNECT_AGENT_CHAT_TUI_COMMAND,
SUBSCRIBER_ID_PLACEHOLDER,
} from '@novu/shared';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function AgentChatSetupGuide({
const stepsColumn = (
<AgentChatSetupSteps
prompt={prompt}
agentIdentifier={agent.identifier}
stepOffset={stepOffset}
firstIncompleteStep={firstIncompleteStep}
onOpenChat={() => openPreview(agent.identifier)}
Expand Down
13 changes: 11 additions & 2 deletions apps/dashboard/src/components/agents/agent-chat-setup-steps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AGENT_CHAT_DOCS_URL,
AgentChatEmbedResources,
buildAgentChatTuiCommand,
buildAgentChatTuiCommandForDisplay,
} from '@/components/agents/agent-chat-setup-content';
import { CopyableTerminalBlock } from '@/components/primitives/copyable-terminal-block';
import { ExternalLink } from '@/components/shared/external-link';
Expand All @@ -12,6 +13,7 @@ import { deriveStepStatus } from './setup-guide-step-utils';

type AgentChatSetupStepsProps = {
prompt: string;
agentIdentifier: string;
stepOffset?: number;
/** Omit to show all steps as completed (connected recap). */
firstIncompleteStep?: number;
Expand All @@ -20,12 +22,19 @@ type AgentChatSetupStepsProps = {

export function AgentChatSetupSteps({
prompt,
agentIdentifier,
stepOffset = 1,
firstIncompleteStep,
onOpenChat,
}: AgentChatSetupStepsProps) {
const base = stepOffset;
const tuiCommand = buildAgentChatTuiCommand(apiHostnameManager.getHostname());
const tuiCommandOptions = {
apiUrl: apiHostnameManager.getHostname(),
agentIdentifier,
connectDashboardUrl: window.location.origin,
};
const tuiDisplayCommand = buildAgentChatTuiCommandForDisplay(tuiCommandOptions);
const tuiCopyCommand = buildAgentChatTuiCommand(tuiCommandOptions);

return (
<>
Expand Down Expand Up @@ -66,7 +75,7 @@ export function AgentChatSetupSteps({
</ExternalLink>
</>
}
extraContent={<CopyableTerminalBlock displayCommand={tuiCommand} copyCommand={tuiCommand} />}
extraContent={<CopyableTerminalBlock displayCommand={tuiDisplayCommand} copyCommand={tuiCopyCommand} />}
rightContent={<AgentChatEmbedResources prompt={prompt} />}
/>
</>
Expand Down
18 changes: 15 additions & 3 deletions apps/dashboard/src/components/agents/agent-code-setup-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,46 +44,55 @@ function resolveConnectRuntime(connectorId: ConnectorId | undefined): ConnectRun
function buildConnectScaffoldParts({
secretKey,
apiUrl,
connectDashboardUrl,
runtime,
}: {
secretKey: string;
apiUrl: string;
connectDashboardUrl: string;
runtime: ConnectRuntimeFlag;
}): string[] {
return [
`${getNovuConnectInvocation(apiUrl)} --runtime ${runtime}`,
`--secret-key ${secretKey}`,
...getNovuConnectTargetFlags(apiUrl),
...getNovuConnectTargetFlags({
apiUrl,
connectDashboardUrl,
}),
'--channel skip',
];
}

function buildConnectScaffoldCommand({
secretKey,
apiUrl,
connectDashboardUrl,
runtime,
masked,
}: {
secretKey: string;
apiUrl: string;
connectDashboardUrl: string;
runtime: ConnectRuntimeFlag;
masked: boolean;
}): string {
const key = masked ? maskSecretKey(secretKey) : secretKey;

return buildConnectScaffoldParts({ secretKey: key, apiUrl, runtime }).join(' \\\n ');
return buildConnectScaffoldParts({ secretKey: key, apiUrl, connectDashboardUrl, runtime }).join(' \\\n ');
}

function buildConnectScaffoldCopyCommand({
secretKey,
apiUrl,
connectDashboardUrl,
runtime,
}: {
secretKey: string;
apiUrl: string;
connectDashboardUrl: string;
runtime: ConnectRuntimeFlag;
}): string {
return buildConnectScaffoldParts({ secretKey, apiUrl, runtime }).join(' ');
return buildConnectScaffoldParts({ secretKey, apiUrl, connectDashboardUrl, runtime }).join(' ');
}

function getProviderSlackMessage(agentName: string): string {
Expand Down Expand Up @@ -361,6 +370,7 @@ export function AgentCodeSetupSection({
const connectRuntime = resolveConnectRuntime(connectorId);

const currentApiUrl = apiHostnameManager.getHostname();
const connectDashboardUrl = window.location.origin;

const bridgeConnected = useBridgeConnectionPolling(agent, onBridgeConnected);

Expand Down Expand Up @@ -402,12 +412,14 @@ export function AgentCodeSetupSection({
displayCommand={buildConnectScaffoldCommand({
secretKey,
apiUrl: currentApiUrl,
connectDashboardUrl,
runtime: connectRuntime,
masked: true,
})}
copyCommand={buildConnectScaffoldCopyCommand({
secretKey,
apiUrl: currentApiUrl,
connectDashboardUrl,
runtime: connectRuntime,
})}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ function AgentChatConnectedRecap({

<SetupGuideCard label="Setup steps" persistKey={persistKey} defaultExpanded={false}>
<SetupStepperRail className="gap-8 py-6 pb-3 pr-3 md:pr-6">
<AgentChatSetupSteps prompt={prompt} onOpenChat={handleOpenChat} />
<AgentChatSetupSteps prompt={prompt} agentIdentifier={agent.identifier} onOpenChat={handleOpenChat} />
</SetupStepperRail>
</SetupGuideCard>
</div>
Expand Down
6 changes: 3 additions & 3 deletions packages/js/scripts/size-limit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ const modules = [
{
name: 'UMD minified',
filePath: umdPath,
// Raised for headless agentChat on Novu. Split to ./agent-chat later if needed.
limitInBytes: 225_000,
// Raised for agent conversation runtime (NV-8640). Split to ./agent-chat later if needed.
limitInBytes: 235_000,
},
{
name: 'UMD gzip',
filePath: umdGzipPath,
limitInBytes: 62_000,
limitInBytes: 64_000,
},
];

Expand Down
58 changes: 58 additions & 0 deletions packages/js/src/agent-chat/agent-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import type { Result } from '../types';
import { NovuError } from '../utils/errors';
import type { BaseSocketInterface } from '../ws/base-socket';
import { AgentChatStore, type ConversationEntry, createLocalConversationKey } from './agent-chat-store';
import { AgentConversationRuntime } from './agent-conversation-runtime';
import { type AgentMessage, derivePendingActions } from './agent-message.types';
import type { ConversationArgs, ConversationResult } from './conversation-runtime.types';
import { runtimeCacheKey } from './runtime-cache-key';
import type {
AgentChatMessagesUpdated,
FetchMoreArgs,
FetchMoreResult,
LoadConversationArgs,
Expand All @@ -25,6 +29,7 @@ export class AgentChat extends BaseModule {
#store: AgentChatStore;
#socket: Pick<BaseSocketInterface, 'connect'>;
#liveSubscriberCount = 0;
#runtimes = new Map<string, AgentConversationRuntime>();
/**
* Non-null while a reconnect catch-up is in flight: live envelopes are buffered here
* and applied after the HTTP page is absorbed. Serialized via `#catchUpChain`.
Expand Down Expand Up @@ -93,8 +98,60 @@ export class AgentChat extends BaseModule {
}

clearCache(): void {
for (const runtime of [...this.#runtimes.values()]) {
runtime.dispose();
}

this.#store.clear();
this.#catchUpBuffer = null;
this.#runtimes.clear();
}

/**
* Return a stable conversation runtime for one agent thread.
* Resume sessions (`conversationId` set) are reused across calls with the same identity.
*/
conversation(args: ConversationArgs): ConversationResult<AgentConversationRuntime> {
const cacheKey = args.conversationId ? runtimeCacheKey(args.agentId, args.conversationId) : undefined;
if (cacheKey) {
const existing = this.#runtimes.get(cacheKey);
if (existing) {
return { ok: true, data: existing };
}
}

const runtime = new AgentConversationRuntime(this, args);
if (cacheKey) {
this.#runtimes.set(cacheKey, runtime);
}

return { ok: true, data: runtime };
}

/** @internal */
onMessagesUpdated(listener: (data: AgentChatMessagesUpdated) => void): () => void {
return this._emitter.on('agent_chat.messages.updated', ({ data }) => {
listener(data);
});
}

/** @internal */
registerRuntime(cacheKey: string, runtime: AgentConversationRuntime): void {
const existing = this.#runtimes.get(cacheKey);
if (existing && existing !== runtime) {
existing.dispose();
}

this.#runtimes.set(cacheKey, runtime);
}

/** @internal */
unregisterRuntime(runtime: AgentConversationRuntime): void {
for (const [key, value] of this.#runtimes.entries()) {
if (value === runtime) {
this.#runtimes.delete(key);
}
}
}

getConversation({ agentId, conversationId, key }: { agentId: string; conversationId?: string; key?: string }):
Expand Down Expand Up @@ -276,6 +333,7 @@ export class AgentChat extends BaseModule {
text: args.text,
conversationId,
agentHash: args.agentHash,
metadata: args.metadata,
});

this.#store.markSent(entry, {
Expand Down
Loading
Loading