feat(mktg-os): add per-agent chat panel with polling#1035
Conversation
Port the guild_embed chat UX into an Angular per-agent panel wired to the existing /api/mktg-agents Guild proxy. Replaces the marketplace chat stub. - MktgAgentsService: thin HTTP client for chat (create/follow-up) + history. - MktgChatSessionService: SSR-safe localStorage persistence for per-agent sessions and active ids (guild_active_session_ids / guild_agent_sessions), keeping each session's owner token for follow-ups. - MktgChatPanelComponent: optimistic send, REST polling of history until a new agent message lands (~1.5s cadence, ~60s deadline), Past Chats drawer with select/delete, new chat, typing indicator, and graceful expired-session recovery. In-flight fetches are cancelled on agent/session switch. - Add MktgChatSession to the shared chat contract. Streaming is deferred (MVP uses polling, no ws dependency). Signed-off-by: Joan Reyero <joan@reyero.io>
Address post-commit reviewer findings on the per-agent chat panel:
- Track the in-flight send POST (take(1) + sendSub) and tear it down in
cancelInFlight()/ngOnDestroy, so a destroy or session switch mid-send can no
longer poll into the wrong session or write to a destroyed component.
- Handle the {success:true} response for a new-session request: fail soft via
handleSendError instead of leaving the input wedged with isTyping stuck on.
- Log before every catchError/error fallback (send, history load, poll retry).
- Extract tagsLabel computed so the header no longer calls join() in template.
Signed-off-by: Joan Reyero <joan@reyero.io>
Address full-branch sweep findings: - handleSendError now rolls back the optimistic user bubble and restores the draft text to the input, so a failed send no longer forces the user to retype. - Move the tagsLabel computed below the signals block (Forms -> Signals -> Computed) per component-organization ordering. - Document why getHistory intentionally omits a catchError fallback (the caller distinguishes expired sessions from transient poll errors). Test coverage for the new component/services is deferred: the app has no component/service unit-test convention (Playwright e2e only), and the chat surface is LaunchDarkly-gated + needs a live Guild backend, so e2e can't run green in CI yet. Tracked as a trade-off. Signed-off-by: Joan Reyero <joan@reyero.io>
…nding Address final full-branch sweep findings: - loadHistory only drops a session (and its ownerToken) on a genuine 404/410; a transient network/5xx error now shows a non-destructive notice and leaves the saved session intact, so the user can still post follow-ups. - Lock the message FormControl in lockstep with the Send button while sending or loading (lfx-input-text has no disabled input), making the draft-restore invariant in handleSendError actually hold instead of just being claimed. Signed-off-by: Joan Reyero <joan@reyero.io>
Replace the constructor effect() that synced the message-control disabled state with setTyping/setHistoryLoading setters that flip the lock alongside the busy signals, per frontend-checklist \xC2\xA75 (no effect() for side effects). Behavior is unchanged; the input is still disabled exactly when the Send button is. Signed-off-by: Joan Reyero <joan@reyero.io>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Adds the real per-agent chat experience to the Marketing OS marketplace by replacing the previous stub with a standalone chat panel that uses REST polling against the existing /api/mktg-agents proxy, plus client-side session persistence for “Past Chats”.
Changes:
- Introduces
MktgChatPanelComponentto handle per-agent chat UX (optimistic send, history polling, typing indicator, past chats UI). - Adds frontend services for chat transport (
MktgAgentsService) and SSR-safe localStorage persistence (MktgChatSessionService). - Extends the shared contract with a persisted-session shape (
MktgChatSession) and wires the marketplace page to render the chat panel.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/interfaces/mktg-chat.interface.ts | Adds MktgChatSession to the shared chat contract for persisted session metadata. |
| apps/lfx-one/src/app/shared/services/mktg-chat-session.service.ts | New SSR-safe localStorage persistence for active session IDs and per-agent session lists. |
| apps/lfx-one/src/app/shared/services/mktg-agents.service.ts | New thin HTTP client for chat send + history fetch via /api/mktg-agents. |
| apps/lfx-one/src/app/modules/mktg-os-agents/mktg-os-agents/mktg-os-agents.component.ts | Registers the new chat panel component for the marketplace page. |
| apps/lfx-one/src/app/modules/mktg-os-agents/mktg-os-agents/mktg-os-agents.component.html | Replaces the marketplace chat stub with <lfx-mktg-chat-panel>. |
| apps/lfx-one/src/app/modules/mktg-os-agents/mktg-chat-panel/mktg-chat-panel.component.ts | Implements the chat panel logic (optimistic send, polling, session handling, expired-session recovery). |
| apps/lfx-one/src/app/modules/mktg-os-agents/mktg-chat-panel/mktg-chat-panel.component.html | Implements the chat panel UI (header, sessions drawer, message list, typing indicator, input). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private readMap<T = string>(key: string): Record<string, T> { | ||
| if (!isPlatformBrowser(this.platformId)) { | ||
| return {}; | ||
| } | ||
| try { | ||
| const raw = localStorage.getItem(key); | ||
| const parsed = raw ? (JSON.parse(raw) as unknown) : null; | ||
| return parsed && typeof parsed === 'object' ? (parsed as Record<string, T>) : {}; | ||
| } catch { | ||
| return {}; | ||
| } | ||
| } |
| switchMap(() => | ||
| this.mktgAgents.getHistory(sessionId).pipe( | ||
| catchError((error) => { | ||
| // Swallow transient poll errors so a single hiccup doesn't abort the | ||
| // wait; the loop retries on the next tick. | ||
| console.warn('[mktg-chat] history poll failed, retrying', error); | ||
| return of(null); | ||
| }) | ||
| ) | ||
| ), |
| /** | ||
| * Recover from a failed send: roll back the optimistic bubble (the server never | ||
| * received it), restore the user's draft so they can resend without retyping, | ||
| * and surface an inline error. The message control is locked while sending (see | ||
| * the constructor effect), so no new input can have accumulated — restoring the | ||
| * draft can't clobber anything the user typed. | ||
| */ |
| const agentMessageBaseline = this.countAgentMessages(); | ||
| const ownerToken = sessionId ? this.sessionStore.getSession(agentId, sessionId)?.ownerToken : undefined; | ||
|
|
||
| this.sendSub = this.mktgAgents |
| @for (message of messages(); track message.id) { | ||
| <div | ||
| class="flex flex-col max-w-[80%]" | ||
| [class.self-end]="message.sender === 'user'" | ||
| [class.items-end]="message.sender === 'user'" | ||
| [attr.data-testid]="'mktg-chat-message-' + message.sender"> | ||
| <div |
Summary
Implements LFXAI-99 — the final story of the LFX Marketing OS Marketplace epic (LFXAI-95). Ports the
guild_embedchat UX into an Angular per-agent chat panel, wired to the existing/api/mktg-agentsGuild proxy (shipped in #1032) with REST polling instead of streaming (MVP, nowsdependency). Replaces the marketplace chat stub.What's included
MktgChatPanelComponent— per-agent chat surface: optimistic send, history polling until a new agent reply (~1.5s cadence, ~60s deadline), typing indicator, Past Chats drawer (select/delete), New Chat, back-to-grid, draft-restore + optimistic rollback on send failure, and non-destructive recovery (drops a session only on a genuine 404/410, not on a transient error).MktgAgentsService— thin HTTP client for chat (create/follow-up) + history.MktgChatSessionService— SSR-safelocalStoragepersistence (guild_active_session_ids/guild_agent_sessions), retaining each session's owner token for follow-ups; a corrupt store degrades to empty.MktgChatSessionadded to the shared chat contract.<lfx-mktg-chat-panel>.Testing
yarn check-types,yarn lint,yarn build(SSR) all green; license headers pass.linux-foundation/marketing-osGuild workspace (agentfoundation-message,READY/ publishedv1.0.4): create → poll → reply round-trip, follow-ups, session switch, new chat, delete, and expired-session recovery. First replies take ~15–25s (well within the 60s poll deadline).Documented trade-offs
data-testids are in place throughout.getHistoryintentionally omits acatchErrorfallback — the component distinguishes an expired session (recover) from a transient poll error (retry); a service-level fallback would make that recovery path dead code.Not included (stays dark-launched)
The local-dev flag-bypass (
MKTG_OS_FORCE_ENABLED) is intentionally not committed — Marketing OS stays gated behind themktg-os-agents-enabledLaunchDarkly flag (default off; visible only via the targeting rule).Relates to: LFXAI-99 · epic LFXAI-95.