EnxDev opened a new issue, #41059:
URL: https://github.com/apache/superset/issues/41059

   ## [SIP-214]  Introducing a Chat extension in Superset
   ### 1. Introduction
   
   This SIP proposes a new extension point that enables third-party chat 
integrations to be embedded directly into the Superset user interface through 
the existing extension framework.
   The goal is to provide a stable, supported mechanism for chat providers to 
integrate with Superset without requiring direct access to internal application 
state, Redux stores, or implementation-specific frontend modules. 
   Chat extensions should interact with Superset through the same 
extension-oriented principles already established for other extension surfaces, 
such as SQL Lab.
   The proposal focuses on two core concerns:
   
   - Defining how chat extensions are registered and rendered.
   - Defining how chat extensions receive contextual information about the 
currently active application surface.
   
   This SIP intentionally does not prescribe any specific chat implementation, 
user experience, LLM provider, or backend architecture.
   
   <details>
   <summary>1.1 Motivation</summary>
   AI-powered assistants are becoming a common way for users to interact with 
analytical applications. Superset should provide a standardized extension 
mechanism that allows community-built chat integrations to participate in the 
platform without depending on internal frontend implementation details.
   Today, chat integrations must either be embedded through custom application 
modifications or rely on unsupported access to internal application state. Both 
approaches create maintenance challenges and make integrations fragile when the 
architecture evolves.
   
   #### This SIP introduces a stable extension contract that:
   
   - Enables chat integrations to be distributed as standard Superset 
extensions.
   - Preserves separation between host and extension responsibilities.
   - Allows chat implementations to access contextual information about the 
current page and entity being viewed.
   - Keeps authorization and permission enforcement aligned with existing 
Superset APIs.
   - Remains compatible with future architecture changes.
   
   </details>
   <details>
   <summary>1.2 Goals</summary>
   
   #### The goals of this SIP are:
   
   Introduce a dedicated chat extension point within the Superset application.
   Provide chat extensions with host-managed, permission-aligned context.
   Establish stable extension-facing APIs for dashboard, explore, dataset, and 
navigation context.
   Maintain isolation between chat implementations and Superset internals.
   Preserve compatibility with future extension capabilities and AI-related 
initiatives.
   </details>
   
   <details>
   <summary> 1.3 Out of Scope </summary>
   
   The following capabilities are explicitly out of scope for this SIP.
   Client Actions and Agentic UI Manipulation
   This SIP defines how chat extensions are mounted and how they receive 
context from the host application.
   
   It does not define how a chat performs actions within the user interface, 
such as:
   
   - Modifying chart configuration.
   - Updating dashboard layouts.
   - Editing SQL queries.
   - Triggering frontend workflows.
   
   These capabilities are deferred to the proposed Client Actions SIP.
   Chat User Experience
   The chat user interface remains entirely owned by the extension.
   #### This SIP does not prescribe:
   
   -  Visual design.
   -  Conversation experience.
   -  Streaming behavior.
   -  Message persistence.
   -  Prompting strategy.
   -  Accessibility implementation details.
   -  Branding or styling.
   
   #### LLM and Backend Infrastructure
   The following concerns remain extension-specific:
   - Model providers.
   - MCP implementations.
   - Agent frameworks.
   - Tool execution systems.
   - Prompt orchestration.
   - Backend services.
    
   Superset acts only as the host application and context provider.
   User Preferences
   Per-user chat preferences are considered an important future capability but 
are intentionally out of scope for this SIP.
   </details>
   
   
   ### 2. Requirements
   <details>
   <summary>2.1 Functional Requirements</summary>
   
   #### Registration and Rendering
   The platform must allow extensions to register the Chat providers through 
the standard extension system.
   
   The host must:
    
   -  Support registration of chat extensions.
   - Render a chat UI contributed by an extension.
   - Maintain a single active chat instance at any given time.
   - Make the chat available across supported application surfaces.
   - Support fully custom chat user interfaces.
   
   #### Context Sharing
   Context is obtained through the same set of APIs used by other extensions. 
   These APIs are organized by namespaces and provide the methods and events 
that will be used by the chat to collect application state.
   
   - Extensions must not be required to:
   - Read Redux state.
   - Access internal application modules.
   - Depend on component-level implementation details.
   - Reconstruct semantic context from frontend internals.
   
   Instead, extensions consume stable namespace APIs provided by the host.
   
   #### Conversation State
   The conversation state remains entirely owned by the chat extension.
   
   This includes:
   - Message history.
   - Tool execution state.
   - Streaming buffers.
   - Conversation persistence.
   - Session management.
   The host is responsible only for exposing contextual information.
   </summary>
   </details>
   
   <details>
   <summary>2.2 Non-Functional Requirements</summary>
   
   Security and Authorization
   Context shared with chat extensions must remain aligned with Superset's 
existing authorization model.
   
   The host must not expose:
   - Entities the current user cannot access.
   - Metadata outside the user's permission scope.
   - Datasource-derived information unavailable through existing APIs.
   The extension-facing APIs defined by this SIP operate on data that has 
already been scoped to the current user.
   
   #### Fault Isolation
   
   Failures within chat extensions must not affect the stability of the host 
application.
   Errors originating from third-party chat implementations should be isolated 
to the chat mount boundary.
   
   #### Extensibility
   The architecture should support future:
   • Application surfaces.
   • AI-related capabilities.
   • Extension APIs.
   • Context providers.
   without requiring redesign of the chat extension model.
   
   #### Vendor Neutrality
   The architecture must remain independent of any specific:
   • LLM provider.
   • AI platform.
   • Agent framework.
   • Backend implementation.
   </details>
   
   ### 3. Default resolution and first-run behavior 
   
   <details>
   <summary>3.1 Selection logic</summary>
   The host resolves the active chat deterministically using the following 
policy:
   
   - No chat extension is registered. No chat is rendered and the chat surface 
remains empty. This is not an error state; the host simply has nothing to mount.
   - When multiple chat extensions are registered, the host should resolve to 
the last one registered.
   - The host should clearly state in the server logs what's the selected chat 
extension alongside the ignored ones when multiple extensions are provided.
   </details>
   
   ### 4. Proposed New Contribution Type
   <details>
   <summary>4.1 Overview</summary>
   This SIP introduces a new contribution type that allows chat providers to 
integrate directly into the Superset application. 
   <br>
   
   | Contribution Type | Registration API     | Cardinality |
   |------------------|----------------------|-------------|
   | `core.chat`      | `chat.registerChat()` | Singleton   |
   
   </details>
   
   <details>
   <summary>4.2 Singleton Rendering Model </summary>
   
   Unlike most contribution types, which allow multiple contributions to be 
rendered simultaneously, the chat is intentionally exclusive and renders a 
single active provider.
   
   #### Motivation
   
   Chat interactions are inherently conversational and user-focused. Rendering 
multiple chat providers simultaneously would:
   
   - Create competing user experiences.
   - Introduce ambiguity regarding which chat should respond.
   - Increase UI complexity.
   - Reduce discoverability.
   
   For these reasons, chat rendering is treated as a deployment-level selection 
rather than a multi-provider composition model.
   </details>
   
   <details>
   <summary>4.3 Chat Lifecycle</summary>
   
   Host Responsibilities
   The host is responsible for:
   • Providing the chat mount point.
   • Resolving the active chat provider.
   • Loading chat extensions.
   • Managing chat lifecycle integration.
   • Maintaining fault isolation boundaries.
   • Preserving chat availability across route changes.
   • Providing APIs defined by this SIP.
   • Persisting the current selected display mode.
   The host also provides fixed positioning and layering behavior to ensure 
chat visibility remains consistent throughout the application.
   We propose 2 display modes for the chat:
   Bubble mode
   
   <b>Bubble mode</b>
   
   <img width="1586" height="992" alt="Image" 
src="https://github.com/user-attachments/assets/ac6db8a7-fba4-48dd-97f3-5d82d429c209";
 />
   
   <img width="1586" height="914" alt="Image" 
src="https://github.com/user-attachments/assets/f3e1a147-e5bb-47bd-b430-d3bff0db6e5e";
 />
   
   <b>Panel Mode</b>
   
   <img width="1597" height="904" alt="Image" 
src="https://github.com/user-attachments/assets/d3e82da5-dae4-4a9d-890f-f34883f7fd5a";
 />
   
   <br>
   
   #### Fault Isolation
   
   Chat providers execute within a host-managed boundary.
   Failures originating from a chat extension must not affect the rest of the 
application.
   
   Examples include:
    
   - Module Federation loading failures.
   - Runtime exceptions.
   - Provider initialization errors.
   
   If a chat fails to load, the host logs the failure, surfaces an appropriate 
notification, and continues operating normally.
   
   The application shell remains functional even when the chat provider is 
unavailable.
   </details>
   
   <details>
   <summary>4.4 Extension Responsibilities</summary>
   The registered chat component owns the complete chat experience.
   The extension is responsible for:
   
   #### User Interface
   
   - Collapsed bubble UI.
   - Expanded panel UI.
   - Providing a toggle to switch between display modes by invoking the 
setDisplayMode API.
   - Branding.
   - Icons and badges.
   - Layout.
   - Responsiveness.
   
   #### Interaction Model
   - Open and close behavior.
   - Keyboard shortcuts.
   - Focus management.
   - Accessibility behavior.
   - Conversation navigation.
   
   #### Conversation Runtime
   - Message history.
   - Streaming state.
   - Tool execution.
   - Persistence.
   - Session management.
   #### Backend Integration
   - LLM communication.
   - MCP integration.
   - Agent orchestration.
   - Tool invocation.
   The host does not manage any chat-specific runtime state.
   </details>
   
   <details>
   <summary>4.5 Registration Example</summary>
   
   Chat extensions register a single provider through the existing view 
registration API.
   
   ``` typescript
   import { chat } from '@apache-superset/core';
   import { Panel, Trigger }  from 'components/Chat';
   
   chat.registerChat(
     chat: { id: 'acme.chat', name: 'Acme Chat' },
     trigger: () => Trigger,
     panel: () => Panel,
   );
   ```
   The registration process remains consistent with existing extension 
contribution patterns.
   The only difference is that the host applies singleton resolution before 
selecting the provider to render.
   
   Runtime UI state such as:
    
   - Notification indicators.
   - Unread counts.
   - Loading states.
   - Thinking indicators.
   belongs to the chat component itself rather than the registration descriptor.
   This keeps the registry simple while allowing chat implementations complete 
control over their user experience.
   
   </details>
   
   ### 5. Context and Namespace Model
   <details>
   <summary>5.1 Overview</summary>
   
   Chat extensions require access to contextual information about the user's 
current activity within Superset. 
   This SIP introduces a namespace-based context model that allows extensions 
to consume stable, host-managed APIs rather than depending on internal frontend 
implementation details.
   The host exposes context through a set of surface-specific namespaces. Each 
namespace owns the context for a particular application surface and provides:
   • Synchronous state getters.
   • Event-based change notifications.
   • Stable extension-facing contracts.
   • Context aligned with the current user's authorized application view.
   Extensions consume these namespaces and compose them into higher-level 
context models tailored to their own use cases.
   </details>
   
   <details>
   <summary>5.2 Design Principles</summary>
   
   The namespace model is guided by the following principles.
   
   #### Stable Extension Contracts
   Extensions must depend on documented APIs rather than frontend 
implementation details.
   In particular, extensions must not depend on:
   
   - Redux slices.
   - Store shape.
   - Selectors.
   - Component-local state.
   - Routing implementation details.
   
   This allows Superset to evolve its frontend architecture without breaking 
extension integrations.
   
   #### Host-Owned Context Normalization
   The host is responsible for transforming application state into semantic 
extension-facing contracts.
   Extensions consume normalized context rather than deriving it from raw 
frontend state.
   
   #### Backend-Authorized Context
   Authorization remains a backend responsibility.
   Namespaces expose context that has already been scoped by backend APIs 
according to the current user's permissions.
   Namespaces do not implement authorization logic themselves and should not be 
considered security boundaries.
   
   #### Event-Driven Updates
   Context changes are propagated through events rather than polling.
   Extensions can subscribe to context updates and react immediately when 
relevant application state changes.
   </details>
   <details>
   <summary>5.3 Namespaces</summary>
   Following the same pattern we did in SQL Lab, we're going to define global 
and page-specific namespaces that can be accessed by the chat. In this phase, 
we're going to introduce the following namespaces:
   <br>
   
   | Namespace    | Purpose                                                     
|
   |-------------|-------------------------------------------------------------|
   | `chat`      | APIs to register the chat contribution and interact with the 
host |
   | `navigation`| APIs about routing and navigation context                   |
   
   <br>
   In subsequent phases, we're going to introduce other namespaces like 
dashboard, explore, dataset, etc tied to other SIPs that are already in 
discussion.
   
   #### Chat Namespace
   ``` typescript
   // packages/superset-core/src/chat/index.ts
   
   import type { Disposable, Event } from "../common";
   
   export interface Chat {
     /** The unique identifier for the chat. */
     id: string;
     /** The display name of the chat. */
     name: string;
     /** Optional description of the chat, for display in contribution 
manifests. */
     description?: string;
   }
   
   export type DisplayMode = "floating" | "panel";
   
   /**
    * Registers a chat provider. The host applies singleton resolution —
    * only one chat is active at a time per RFC §4.3.
    * Disposing the returned Disposable unregisters the chat.
    *
    * @param chat The chat descriptor (id, name).
    * @param trigger A function returning the collapsed bubble element. Owned by
    *   the extension — dynamic state such as unread counts and badges lives 
here.
    *   Hidden by the host when in panel mode.
    * @param panel A function returning the chat panel element. Mounted by the
    *   host as a floating overlay in 'floating' mode, or as a layout slot 
between
    *   the header and footer in 'panel' mode. Same component in both modes.
    * @returns A Disposable that unregisters the chat when disposed.
    *
    * @example
    * ```typescript
    * chat.registerChat(
    *   { id: 'acme.chat', name: 'Acme Chat' },
    *   () => <AcmeTrigger />,
    *   () => <AcmePanel />,
    * );
    * ```
    */
   export declare function registerChat(
     chat: Chat,
     trigger: () => ReactElement,
     panel: () => ReactElement,
   ): Disposable;
   
   /**
    * Returns the active chat descriptor.
    *
    * @returns The registered Chat descriptor, or undefined if none is 
registered.
    */
   export declare function getChat(): Chat | undefined;
   
   /**
    * Event fired when a chat is registered.
    */
   export declare const onDidRegisterChat: Event<Chat>;
   
   /**
    * Event fired when a chat is unregistered.
    */
   export declare const onDidUnregisterChat: Event<Chat>;
   
   /**
    * Opens the chat panel.
    */
   export declare function open(): void;
   
   /**
    * Closes the chat panel.
    */
   export declare function close(): void;
   
   /**
    * Returns whether the chat panel is currently open.
    *
    * @returns True if the chat panel is open.
    */
   export declare function isOpen(): boolean;
   
   /**
    * Event fired when the chat panel opens.
    */
   export declare const onDidOpen: Event<void>;
   
   /**
    * Event fired when the chat panel closes.
    */
   export declare const onDidClose: Event<void>;
   
   /**
    * Returns the current display mode.
    *
    * @returns The current display mode.
    */
   export declare function getDisplayMode(): DisplayMode;
   
   /**
    * Sets the display mode.
    *
    * The host supports both modes on all pages. The host also exposes a mode
    * toggle in its chrome — use onDidChangeMode to react to changes initiated
    * outside the extension.
    *
    * @param displayMode The display mode to switch to.
    */
   export declare function setDisplayMode(displayMode: DisplayMode): void;
   
   /**
    * Event fired when the display mode changes, whether triggered by the
    * extension via setDisplayMode() or by the user through host-provided 
controls.
    */
   export declare const onDidChangeDisplayMode: Event<DisplayMode>;
   
   /**
    * Event fired when the user resizes the panel in panel mode.
    *
    * The host owns the resizer handle and drag interaction. Listen to this
    * event to adapt internal layout to the available width.
    */
   export declare const onDidResizePanel: Event<{ width: number }>;
   
   // TODO: client actions API — tool availability functions will be added here
   // once the client_actions SIP is finalized. The chat namespace is the
   // intended integration point between the two SIPs.
   ```
   
   #### Navigation Namespace
   ``` typescript
   /**
    * Licensed to the Apache Software Foundation (ASF) under one
    * or more contributor license agreements.  See the NOTICE file
    * distributed with this work for additional information
    * regarding copyright ownership.  The ASF licenses this file
    * to you under the Apache License, Version 2.0 (the
    * "License"); you may not use this file except in compliance
    * with the License.  You may obtain a copy of the License at
    *
    *   http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing,
    * software distributed under the License is distributed on an
    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    * KIND, either express or implied.  See the License for the
    * specific language governing permissions and limitations
    * under the License.
    */
   
   /**
    * @fileoverview Navigation namespace for Superset.
    *
    * Exposes the current application surface so extensions can react to route
    * changes without polling.
    */
   
   import { Event } from '../common';
   
   /**
    * The set of top-level application surfaces.
    */
   export type Page =
     | 'dashboard'
     | 'dashboard_list'
     | 'explore'
     | 'chart_list'
     | 'sqllab'
     | 'query_history'
     | 'saved_queries'
     | 'dataset'
     | 'dataset_list'
     | 'home';
   
   /**
    * Returns the current page surface.
    *
    * @example
    * ```typescript
    * const page = navigation.getPage();
    * if (page === 'dashboard') {
    *   // do something
    * }
    * ```
    */
   export declare function getPage(): Page;
   
   /**
    * Event fired whenever the user navigates to a different surface.
    * Use the surface-specific namespace to read entity context after the event.
    *
    * @example
    * ```typescript
    * const sub = navigation.onDidChangePage(page => {
    *   if (page === 'dashboard') {
    *     // do something
    *   }
    * });
    * // later:
    * sub.dispose();
    * ```
    */
   export declare const onDidChangePage: Event<Page>;
   ```
   </details>
   
   <details>
   <summary>5.4 Context Composition</summary>
   
   This SIP intentionally does not introduce a host-owned aggregate context 
object. Instead, extensions compose the context they require from individual 
namespaces.
   For example:
   ``` typescript
   const pageContext = {
     pageType: navigation.getPageType(),
     sqlLab: sqlLab.getCurrentTab(),
     // Extensions can still access additional information that is not 
available yet in @apache-superset/core using Redux
     // We'll continue to increase the available namespaces over time while 
other pages become extendable
   };
   ```
   
   The extension assembles a higher-level context tailored to its own 
requirements.
   
   The host remains responsible for:
   
   - Context ownership.
   - Context normalization.
   - Authorization alignment.
   The extension remains responsible for:
   - Context composition.
   - Prompt construction.
   - Application-specific interpretation.
   This separation avoids introducing a centralized context abstraction while 
allowing new surfaces to be added incrementally over time.
   
   </details>
   
   #### 6. Page-Scoped Namespace Guard
   <details>
   <summary> 6.1 Enforcing API context safety </summary>
   
   To enforce that surface namespaces (**sqlLab, dashboard, explore, dataset**) 
are not called from the wrong page, we'll wrap page-specific namespaces
   with a **Proxy** at startup via **registerPageNamespace**. Every function 
call and event subscription checks the current page before executing. Misuse 
throws immediately at the call site rather than returning **undefined** 
silently.
   
   ``` typescript
   function registerPageNamespace<T extends object>(pageType: PageType, impl: 
T): T {
     return new Proxy(impl, {
       get(target, prop) {
         const value = target[prop as keyof T];
         if (typeof value !== 'function') return value;
         return (...args: any[]) => {
           if (navigation.getPageType() !== pageType) {
             throw new Error(
               `'${String(prop)}' is not available outside the '${pageType}' 
surface`
             );
           }
           return (value as Function)(...args);
         };
       },
     });
   }
   ```
   **Registration at startup:**
   
   ``` typescript
   // Page-specific namespaces are guarded
   export const sqlLab    = registerPageNamespace('sqllab',    sqlLabImpl);
   export const dashboard = registerPageNamespace('dashboard', dashboardImpl);
   export const explore   = registerPageNamespace('explore',   exploreImpl);
   export const dataset   = registerPageNamespace('dataset',   datasetImpl);
   
   // Global namespaces are exported directly — no guard
   export const commands       = commandsImpl;
   export const authentication = authenticationImpl;
   export const navigation     = navigationImpl;
   export const chat        = chatImpl;
   ```
   
   Subscribing to a page-specific event from the wrong page will throw at 
subscription time in the same way as other functions
   </details>
   
   <details>
   <summary> 7. Future Work </summary>
   <br>
   
   This SIP establishes the foundational chat extension model and the initial 
set of extension-facing APIs.
   
   #### Future work may include:
   
   - Additional page-specific namespaces (dashboard, explore, dataset, SQL Lab).
   - Integration with the proposed Client Actions SIP to enable host-mediated 
UI actions.
   - User preference APIs for chat-specific settings and personalization.
   - Richer context contracts and additional application surfaces.
   - Support for multiple chat providers and alternative resolution strategies 
if future use cases require them.
   </details>
   
   ### 8. Rejected Alternatives
   <details>
   <summary>List</summary>
   
   #### Direct Redux Access
   
   Allowing chat extensions to access Redux state directly was considered and 
rejected.
   This approach would tightly couple extensions to Superset's internal 
implementation details, making integrations fragile as frontend architecture 
evolves.
   #### Host-Owned Aggregate Context Object
   A single host-provided context object containing all application state was 
considered and rejected.
   Instead, this SIP adopts a namespace-based model where extensions compose 
the context they need from stable, surface-specific APIs. This avoids 
introducing a large centralized contract that would be difficult to evolve over 
time.
   #### Multi-Provider Rendering
   Allowing multiple chat providers to render simultaneously was considered and 
rejected.
   Chat interactions are inherently conversational and user-focused. Rendering 
multiple providers would create competing experiences, increase UI complexity, 
and introduce ambiguity regarding which chat should respond.
   #### Host-Managed Conversation State
   Having the host manage message history, streaming state, and conversation 
persistence was considered and rejected.
   Conversation state remains fully owned by the chat extension, preserving 
separation of responsibilities and maintaining vendor neutrality.
   </details>
   
   ### 9. Proof of Concept (POC)
   <details>
   <summary>poc</summary>
   
   A proof of concept has been implemented to validate the proposed chat 
extension model and its integration with the existing Superset extension 
framework, available at 
[test/chat-local](https://github.com/apache/superset/tree/test/chatbot-local) 
branch.
   
   #### The prototype demonstrates:
   
   - Registration of a chat provider through the proposed chat.registerChat() 
API.
   - Singleton provider resolution.
   - Rendering of both floating and panel display modes.
   - Persistence of display mode across navigation.
   - Chat availability across supported application surfaces.
   - Integration with the extension loading and registration lifecycle.
   - Fault isolation between the host application and the chat implementation.
   
   The proof of concept was used to iterate on the proposed APIs, validate the 
singleton rendering model, and confirm that the extension framework can support 
a chat integration while preserving separation between host and extension 
responsibilities.
   
   The prototype implementation is intended solely for validation purposes and 
should not be considered the final production implementation.
   
   </details>
   
   ### 10. Related Documents
   
   - [Contribution 
types](https://superset.apache.org/developer-docs/extensions/contribution-types2.%20Proposed%20Extension%20Points)
   - [Client 
actions](https://docs.google.com/document/d/1T0-03Z-3hhXHKJtQtPU5Oq1H1wSJ_SQ7sEOa93I9aFI/edit?tab=t.y1l6igjiow6c)
   Add storage API  for extensions [[SIP-127] User 
Preferences](https://github.com/apache/superset/issues/28047)
   
   ### 11. Migration Plan
   
   <details>
   <summary>Plan</summary>
   
   Base branch 
[enxdev/chat-prototype](https://github.com/apache/superset/tree/enxdev/chat-prototype)
   Reference implementation PR: [Introducing a Chat extension in 
Superset](https://github.com/apache/superset/pull/41006) 
   
   The migration for introducing namespaces to the chat is divided into 
multiple phases. The main reason for breaking the work in this way is that when 
designing the APIs we need to consider all consumers (host, extensions, chat) 
to avoid breaking changes. This means that each API needs discussion and more 
time. Breaking the work in this way, allows us to deliver value along the way 
and at the same time keep the APIs consistent between consumers.
   </details>
   
   ### Special thanks
   
   A heartfelt thank you to the following people for their invaluable feedback, 
support, and guidance:
   
   - Mehmet Salih Yavuz @msyavuz 
   - Evan Rusackas @rusackas 
   - Diego Pucci @geido
   - Ville brofeldt @villebro
   - Justin Park @justinpark 
   
   ✍🏼 Enzo, Michael Molina (@michael-s-molina) 
   
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to