Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const WORKFLOWS_UI_SETTING_ID = 'workflows:ui:enabled';
export const WORKFLOWS_UI_VISUAL_EDITOR_SETTING_ID = 'workflows:ui:visualEditor:enabled';
export const WORKFLOWS_UI_EXECUTION_GRAPH_SETTING_ID = 'workflows:ui:executionGraph:enabled';
export const WORKFLOWS_UI_SHOW_EXECUTOR_SETTING_ID = 'workflows:ui:showExecutor:enabled';
export const WORKFLOWS_AI_AGENT_SETTING_ID = 'workflows:aiAgent:enabled';

/**
* Feature flag ID for enabling / disabling the workflow execution stats bar UI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
*/

import * as settings from '@kbn/management-settings-ids';
import {
WORKFLOWS_AI_AGENT_SETTING_ID,
WORKFLOWS_UI_SETTING_ID,
} from '@kbn/workflows/common/constants';
import { WORKFLOWS_UI_SETTING_ID } from '@kbn/workflows/common/constants';

export const OBSERVABILITY_PROJECT_SETTINGS = [
settings.DEFAULT_ROUTE_ID,
Expand Down Expand Up @@ -41,10 +38,8 @@ export const OBSERVABILITY_PROJECT_SETTINGS = [
export const OBSERVABILITY_STREAMS_TIERED_PROJECT_SETTINGS = [
settings.OBSERVABILITY_STREAMS_ENABLE_SIGNIFICANT_EVENTS,
settings.OBSERVABILITY_STREAMS_ENABLE_SIGNIFICANT_EVENTS_DISCOVERY,
// These settings are only registered in complete tier. They're temporary, will be removed on 9.4.0 release.
// WORKFLOWS_AI_AGENT_SETTING_ID only works when WORKFLOWS_UI_SETTING_ID is enabled.
// This setting is only registered in complete tier. It's temporary, will be removed on 9.4.0 release.
WORKFLOWS_UI_SETTING_ID,
WORKFLOWS_AI_AGENT_SETTING_ID,
];

export const OBSERVABILITY_AI_ASSISTANT_PROJECT_SETTINGS = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ import {
GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY,
} from '@kbn/management-settings-ids';
import { ENABLE_DOCKED_CONSOLE_UI_SETTING_ID } from '@kbn/dev-tools-plugin/common';
import {
WORKFLOWS_AI_AGENT_SETTING_ID,
WORKFLOWS_UI_SETTING_ID,
} from '@kbn/workflows/common/constants';
import { WORKFLOWS_UI_SETTING_ID } from '@kbn/workflows/common/constants';

export const SEARCH_PROJECT_SETTINGS = [
COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX_ID,
Expand All @@ -35,8 +32,6 @@ export const SEARCH_PROJECT_SETTINGS = [
AGENT_BUILDER_PRE_PROMPT_WORKFLOW_IDS,
GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR,
GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY,
// These settings are temporary, will be removed on 9.4.0 release.
// WORKFLOWS_AI_AGENT_SETTING_ID only works when WORKFLOWS_UI_SETTING_ID is enabled.
// This setting is temporary, will be removed on 9.4.0 release.
WORKFLOWS_UI_SETTING_ID,
WORKFLOWS_AI_AGENT_SETTING_ID,
];
Original file line number Diff line number Diff line change
Expand Up @@ -539,10 +539,6 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
type: 'boolean',
_meta: { description: 'Whether Elastic Workflows and related experiences are enabled.' },
},
'workflows:aiAgent:enabled': {
type: 'boolean',
_meta: { description: 'Whether AI-powered workflow authoring assistance is enabled.' },
},
'banners:placement': {
type: 'keyword',
_meta: { description: 'Non-default value of setting.' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export interface UsageStats {
'agentBuilder:externalMcp': boolean;
'agentBuilder:experimentalFeatures': boolean;
'workflows:ui:enabled': boolean;
'workflows:aiAgent:enabled': boolean;
'visualization:heatmap:maxBuckets': number;
'visualization:regionmap:showWarnings': boolean;
'visualization:tileMap:maxPrecision': number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11473,12 +11473,6 @@
"description": "Whether Elastic Workflows and related experiences are enabled."
}
},
"workflows:aiAgent:enabled": {
"type": "boolean",
"_meta": {
"description": "Whether AI-powered workflow authoring assistance is enabled."
}
},
"banners:placement": {
"type": "keyword",
"_meta": {
Expand Down
1 change: 1 addition & 0 deletions src/platform/plugins/shared/workflows_management/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ dependsOn:
- '@kbn/agent-builder-browser'
- '@kbn/share-plugin'
- '@kbn/core-security-server'
- '@kbn/management-settings-ids'
tags:
- plugin
- prod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ describe('WorkflowsPlugin', () => {
});

it('should register the workflows app when workflows UI is enabled', () => {
// First call: WORKFLOWS_UI_SETTING_ID -> true, second call: WORKFLOWS_AI_AGENT_SETTING_ID -> false
coreSetup.uiSettings.get.mockReturnValueOnce(true).mockReturnValueOnce(false);
coreSetup.uiSettings.get.mockReturnValueOnce(true);

const result = plugin.setup(coreSetup, setupDeps as any);

Expand Down
61 changes: 31 additions & 30 deletions src/platform/plugins/shared/workflows_management/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { Subject } from 'rxjs';
import { first, Subject } from 'rxjs';
import {
type AppDeepLinkLocations,
type AppMountParameters,
Expand All @@ -19,10 +19,8 @@ import {
type Plugin,
} from '@kbn/core/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import {
WORKFLOWS_AI_AGENT_SETTING_ID,
WORKFLOWS_UI_SETTING_ID,
} from '@kbn/workflows/common/constants';
import { AGENT_BUILDER_EXPERIMENTAL_FEATURES_SETTING_ID } from '@kbn/management-settings-ids';
import { WORKFLOWS_UI_SETTING_ID } from '@kbn/workflows/common/constants';
import { TelemetryService } from './common/lib/telemetry/telemetry_service';
import { triggerSchemas } from './trigger_schemas';
import type {
Expand Down Expand Up @@ -129,36 +127,39 @@ export class WorkflowsPlugin
public stop() {}

/**
* Sets up AI authoring features: resolves the Agent Builder contract and
* registers workflow attachment renderers. Eagerly kicks off the dynamic
* import so the chunk downloads in parallel with onStart resolution,
* minimising the window where renderers are not yet registered.
* Sets up AI authoring features: subscribes to `agentBuilder:experimentalFeatures`
* reactively via `get$` so that toggling the setting registers renderers without
* a page reload. Once registered, renderers stay (addAttachmentType is idempotent-safe
* via the guard flag) — this is fine because the server-side tools independently gate
* on the same setting and won't create attachments when it's off.
*/
private setupAiIntegration(
core: CoreSetup<WorkflowsPublicPluginStartDependencies, WorkflowsPublicPluginStart>
): void {
const isAiAgentEnabled = core.uiSettings.get<boolean>(WORKFLOWS_AI_AGENT_SETTING_ID, false);
if (!isAiAgentEnabled) {
return;
}
const register = async () => {
const aiIntegrationModule = import('./features/ai_integration');

this.agentBuilderPromise = core.plugins
.onStart<{ agentBuilder: AgentBuilderPluginStartContract }>('agentBuilder')
.then(async ({ agentBuilder }) => {
if (agentBuilder.found) {
const [coreStart] = await core.getStartServices();
const { registerWorkflowAttachmentRenderers } = await aiIntegrationModule;
registerWorkflowAttachmentRenderers(agentBuilder.contract.attachments, {
core: coreStart,
telemetry: this.telemetryService.getClient(),
});
return agentBuilder.contract;
}
return undefined;
})
.catch(() => undefined);
};

const aiIntegrationModule = import('./features/ai_integration');

this.agentBuilderPromise = core.plugins
.onStart<{ agentBuilder: AgentBuilderPluginStartContract }>('agentBuilder')
.then(async ({ agentBuilder }) => {
if (agentBuilder.found) {
const [coreStart] = await core.getStartServices();
const { registerWorkflowAttachmentRenderers } = await aiIntegrationModule;
registerWorkflowAttachmentRenderers(agentBuilder.contract.attachments, {
core: coreStart,
telemetry: this.telemetryService.getClient(),
});
return agentBuilder.contract;
}
return undefined;
})
.catch(() => undefined);
core.uiSettings
.get$<boolean>(AGENT_BUILDER_EXPERIMENTAL_FEATURES_SETTING_ID)
.pipe(first((enabled) => enabled))
.subscribe(() => register());
}

/** Creates the start services to be used in the Kibana services context of the workflows application */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import { act, renderHook } from '@testing-library/react';
import { useUiSetting } from '@kbn/kibana-react-plugin/public';
import { useAgentBuilderIntegration } from './use_agent_builder_integration';
import { WORKFLOW_YAML_ATTACHMENT_TYPE } from '../../../../../common/agent_builder/constants';
import { useKibana } from '../../../../hooks/use_kibana';
Expand All @@ -18,6 +19,11 @@ jest.mock('react-redux', () => ({
useDispatch: () => mockDispatch,
}));
jest.mock('../../../../hooks/use_kibana');
jest.mock('@kbn/kibana-react-plugin/public', () => ({
...jest.requireActual('@kbn/kibana-react-plugin/public'),
useUiSetting: jest.fn(),
}));
const useUiSettingMock = useUiSetting as jest.MockedFunction<typeof useUiSetting>;
jest.mock('uuid', () => ({ v4: () => 'mock-uuid-1234' }));
jest.mock('../../../../features/ai_integration', () => ({
AttachmentBridge: jest.fn().mockImplementation(() => ({
Expand Down Expand Up @@ -114,6 +120,7 @@ describe('useAgentBuilderIntegration', () => {
beforeEach(() => {
jest.useFakeTimers();
mockModel = createMockModel(INITIAL_YAML);
useUiSettingMock.mockReturnValue(true);
});

afterEach(() => {
Expand Down Expand Up @@ -513,9 +520,10 @@ describe('useAgentBuilderIntegration', () => {
});

describe('isAgentBuilderAvailable', () => {
it('returns true when agentBuilder is available', () => {
it('returns true when agentBuilder is available and experimental features enabled', () => {
const agentBuilder = createMockAgentBuilder();
setupKibanaMock(agentBuilder);
useUiSettingMock.mockReturnValue(true);
const editor = createMockEditor(mockModel);

const { result } = renderHook(() =>
Expand All @@ -541,5 +549,21 @@ describe('useAgentBuilderIntegration', () => {

expect(result.current.isAgentBuilderAvailable).toBe(false);
});

it('returns false when experimental features are disabled', () => {
const agentBuilder = createMockAgentBuilder();
setupKibanaMock(agentBuilder);
useUiSettingMock.mockReturnValue(false);
const editor = createMockEditor(mockModel);

const { result } = renderHook(() =>
useAgentBuilderIntegration({
editorRef: { current: editor },
isEditorMounted: true,
})
);

expect(result.current.isAgentBuilderAvailable).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { useCallback, useEffect, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { v4 } from 'uuid';
import { isConversationIdSetEvent } from '@kbn/agent-builder-common/chat/events';
import { useUiSetting } from '@kbn/kibana-react-plugin/public';
import { AGENT_BUILDER_EXPERIMENTAL_FEATURES_SETTING_ID } from '@kbn/management-settings-ids';
import type { monaco } from '@kbn/monaco';
import { WORKFLOW_YAML_ATTACHMENT_TYPE } from '../../../../../common/agent_builder/constants';
import { setAiAssisted } from '../../../../entities/workflows/store/workflow_detail/slice';
Expand Down Expand Up @@ -50,6 +52,9 @@ export const useAgentBuilderIntegration = ({
}: UseAgentBuilderIntegrationParams): UseAgentBuilderIntegrationReturn => {
const { workflowsManagement } = useKibana().services;
const agentBuilder = workflowsManagement?.agentBuilder;
const isExperimentalEnabled = useUiSetting<boolean>(
AGENT_BUILDER_EXPERIMENTAL_FEATURES_SETTING_ID
);
const telemetry = useTelemetry();
const dispatch = useDispatch();
const proposalManagerRef = useRef<ProposalManager | null>(null);
Expand All @@ -68,7 +73,7 @@ export const useAgentBuilderIntegration = ({

useEffect(() => {
const editor = editorRef.current;
if (!isEditorMounted || !editor || !agentBuilder) {
if (!isEditorMounted || !editor || !agentBuilder || !isExperimentalEnabled) {
return;
}

Expand Down Expand Up @@ -253,11 +258,20 @@ export const useAgentBuilderIntegration = ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (window as any).__wfTestBridge;
};
}, [isEditorMounted, editorRef, agentBuilder, attachmentId, workflowId, telemetry, dispatch]);
}, [
isEditorMounted,
editorRef,
agentBuilder,
isExperimentalEnabled,
attachmentId,
workflowId,
telemetry,
dispatch,
]);

const openAgentChat = useCallback(
(options?: OpenAgentChatOptions) => {
if (!agentBuilder) {
if (!agentBuilder || !isExperimentalEnabled) {
return;
}

Expand Down Expand Up @@ -288,12 +302,21 @@ export const useAgentBuilderIntegration = ({
chatOpenedReportedRef.current = true;
}
},
[agentBuilder, editorRef, attachmentId, workflowId, workflowName, validationErrors, telemetry]
[
agentBuilder,
isExperimentalEnabled,
editorRef,
attachmentId,
workflowId,
workflowName,
validationErrors,
telemetry,
]
);

return {
openAgentChat,
isAgentBuilderAvailable: agentBuilder != null,
isAgentBuilderAvailable: agentBuilder != null && isExperimentalEnabled,
proposalManager: proposalManagerRef.current,
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ jest.mock('../lib/autocomplete/intercept_monaco_yaml_provider', () => ({
interceptMonacoYamlProvider: jest.fn(),
}));

jest.mock('./hooks/use_agent_builder_integration', () => ({
useAgentBuilderIntegration: jest.fn(() => ({
openAgentChat: jest.fn(),
isAgentBuilderAvailable: false,
proposalManager: null,
})),
}));

jest.mock('@kbn/monaco', () => ({
monaco: {
editor: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { ToolType } from '@kbn/agent-builder-common';
import { WORKFLOWS_AI_AGENT_SETTING_ID } from '@kbn/workflows/common/constants';
import { AGENT_BUILDER_EXPERIMENTAL_FEATURES_SETTING_ID } from '@kbn/management-settings-ids';
import { z } from '@kbn/zod/v4';
import { workflowTools } from '../../../common/agent_builder/constants';
import type { WorkflowsManagementApi } from '../../api/workflows_management_api';
Expand Down Expand Up @@ -45,7 +45,9 @@ The connector \`id\` is what you put in the \`connector-id\` field of a workflow
tags: ['workflows', 'connectors'],
availability: {
handler: async ({ uiSettings }) => {
const isEnabled = await uiSettings.get<boolean>(WORKFLOWS_AI_AGENT_SETTING_ID);
const isEnabled = await uiSettings.get<boolean>(
AGENT_BUILDER_EXPERIMENTAL_FEATURES_SETTING_ID
);
return isEnabled
? { status: 'available' }
: { status: 'unavailable', reason: 'AI workflow authoring is disabled' };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
*/

import { ToolType } from '@kbn/agent-builder-common';
import { AGENT_BUILDER_EXPERIMENTAL_FEATURES_SETTING_ID } from '@kbn/management-settings-ids';
import { getWorkflowExamples, WORKFLOW_EXAMPLE_IDS } from '@kbn/workflows';
import { WORKFLOWS_AI_AGENT_SETTING_ID } from '@kbn/workflows/common/constants';
import { loadWorkflowExampleContent } from '@kbn/workflows/server';
import { z } from '@kbn/zod/v4';
import { workflowTools } from '../../../common/agent_builder/constants';
Expand Down Expand Up @@ -45,7 +45,9 @@ Supports keyword search across names, descriptions, and tags.`,
tags: ['workflows', 'yaml', 'examples'],
availability: {
handler: async ({ uiSettings }) => {
const isEnabled = await uiSettings.get<boolean>(WORKFLOWS_AI_AGENT_SETTING_ID);
const isEnabled = await uiSettings.get<boolean>(
AGENT_BUILDER_EXPERIMENTAL_FEATURES_SETTING_ID
);
return isEnabled
? { status: 'available' }
: { status: 'unavailable', reason: 'AI workflow authoring is disabled' };
Expand Down
Loading
Loading