Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e6ef4ec
feat(agent-builder-server): add per-attachment max content length
jonwalstedt May 20, 2026
2cabbdc
feat(agent-builder-common): add alert_count field to AddToChat teleme…
jonwalstedt May 20, 2026
64d259b
feat(response-ops-alerts-table): add bulk add-to-chat action
jonwalstedt May 20, 2026
41c3968
feat(security): add bulk alerts attachment type with server-side ES f…
jonwalstedt May 20, 2026
b37f6d4
feat(security): wire bulk add-to-chat into alerts tables
jonwalstedt May 20, 2026
7017d83
fix(agent-builder): fix prettier formatting in bulk_alerts eval spec
jonwalstedt May 21, 2026
205a79c
fix(agent-builder): update prepare_conversation to use per-attachment…
jonwalstedt May 21, 2026
4a356fe
fix(agent-builder): fix attachment_id mismatch between XML format and…
jonwalstedt May 21, 2026
1b57ea0
feat(security-evals): add kbn-evals-suite-security-alert-triage packa…
jonwalstedt May 21, 2026
0b7280d
feat(security-evals): add alert triage quality eval with 100-alert sy…
jonwalstedt May 21, 2026
35c1521
fix(agent-builder): address backend review findings on bulk alerts at…
jonwalstedt May 21, 2026
8b57c88
fix(agent-builder): remove grouped hidden attachments when visible ch…
jonwalstedt May 21, 2026
cbc5b96
fix(agent_builder): add missing groupId to server-side schema
jonwalstedt May 21, 2026
3158426
fix(agent_builder): update the initial prompt when batch adding alert…
jonwalstedt May 22, 2026
73fde51
test(security_solution): add scout test for batch adding alerts to ab…
jonwalstedt May 22, 2026
91a3087
improve test coverage
jonwalstedt May 22, 2026
4d6a5fd
Changes from node scripts/generate codeowners
kibanamachine May 22, 2026
dd5f27f
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine May 22, 2026
1fa8ffe
Changes from make api-docs
kibanamachine May 22, 2026
b9a57a9
refactor(agent-builder): replace groupId shim with AttachmentGroup
jonwalstedt May 22, 2026
6a9cf10
feat(agent-builder): add AttachmentGroupPill for rendering grouped at…
jonwalstedt May 22, 2026
d02d207
refactor(security): migrate bulk alerts producer and all four surface…
jonwalstedt May 22, 2026
840ed43
fix(security): address review findings on bulk alerts attachment
jonwalstedt May 22, 2026
89655f7
fix(agent-builder): mark decorative icon aria-hidden in AttachmentGro…
jonwalstedt May 22, 2026
6d24793
chore: cleanup
jonwalstedt May 22, 2026
18b1e81
test(kbn-evals-suite-security-alert-triage): accommodate the batch si…
jonwalstedt May 22, 2026
0602f08
fix(agent-builder): deduplicate grouped attachment refs in RoundAttac…
jonwalstedt May 22, 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
8 changes: 8 additions & 0 deletions .buildkite/pipelines/evals/evals.suites.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,14 @@
"configPath": "x-pack/solutions/security/packages/kbn-evals-suite-security-esql-generation-regression/playwright.config.ts",
"tags": ["security", "esql-generation"],
"ciLabels": ["evals:security-esql-generation-regression"]
},
{
"id": "security-alert-triage",
"name": "Security Alert Triage",
"slackChannel": "#security-generative-ai-evals",
"configPath": "x-pack/solutions/security/packages/kbn-evals-suite-security-alert-triage/playwright.config.ts",
"tags": ["security", "alert-triage"],
"ciLabels": ["evals:security-alert-triage"]
}
]
}
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,7 @@ x-pack/solutions/security/packages/kbn-evals-suite-endpoint @elastic/security-de
x-pack/solutions/security/packages/kbn-evals-suite-entity-analytics @elastic/security-entity-analytics
x-pack/solutions/security/packages/kbn-evals-suite-pci-compliance @elastic/security-defend-workflows
x-pack/solutions/security/packages/kbn-evals-suite-security-ai-rules @elastic/security-detection-engine
x-pack/solutions/security/packages/kbn-evals-suite-security-alert-triage @elastic/security-threat-hunting
x-pack/solutions/security/packages/kbn-evals-suite-security-automatic-migrations @elastic/security-threat-hunting
x-pack/solutions/security/packages/kbn-evals-suite-security-esql-generation-regression @elastic/security-detection-platform
x-pack/solutions/security/packages/kbn-scout-security @elastic/appex-qa @elastic/security-engineering-productivity
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1750,6 +1750,7 @@
"@kbn/evals-suite-observability-ai": "link:x-pack/solutions/observability/packages/kbn-evals-suite-observability-ai",
"@kbn/evals-suite-pci-compliance": "link:x-pack/solutions/security/packages/kbn-evals-suite-pci-compliance",
"@kbn/evals-suite-security-ai-rules": "link:x-pack/solutions/security/packages/kbn-evals-suite-security-ai-rules",
"@kbn/evals-suite-security-alert-triage": "link:x-pack/solutions/security/packages/kbn-evals-suite-security-alert-triage",
"@kbn/evals-suite-security-automatic-migrations": "link:x-pack/solutions/security/packages/kbn-evals-suite-security-automatic-migrations",
"@kbn/evals-suite-security-esql-generation-regression": "link:x-pack/solutions/security/packages/kbn-evals-suite-security-esql-generation-regression",
"@kbn/evals-suite-significant-events": "link:x-pack/platform/packages/shared/kbn-evals-suite-significant-events",
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,8 @@
"@kbn/evals-suite-pci-compliance/*": ["x-pack/solutions/security/packages/kbn-evals-suite-pci-compliance/*"],
"@kbn/evals-suite-security-ai-rules": ["x-pack/solutions/security/packages/kbn-evals-suite-security-ai-rules"],
"@kbn/evals-suite-security-ai-rules/*": ["x-pack/solutions/security/packages/kbn-evals-suite-security-ai-rules/*"],
"@kbn/evals-suite-security-alert-triage": ["x-pack/solutions/security/packages/kbn-evals-suite-security-alert-triage"],
"@kbn/evals-suite-security-alert-triage/*": ["x-pack/solutions/security/packages/kbn-evals-suite-security-alert-triage/*"],
"@kbn/evals-suite-security-automatic-migrations": ["x-pack/solutions/security/packages/kbn-evals-suite-security-automatic-migrations"],
"@kbn/evals-suite-security-automatic-migrations/*": ["x-pack/solutions/security/packages/kbn-evals-suite-security-automatic-migrations/*"],
"@kbn/evals-suite-security-esql-generation-regression": ["x-pack/solutions/security/packages/kbn-evals-suite-security-esql-generation-regression"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
*/

import type { ComponentType } from 'react';
import type { AttachmentInput, UpdateOriginResponse } from '@kbn/agent-builder-common/attachments';
import type {
AttachmentInput,
ConversationAttachment,
UpdateOriginResponse,
} from '@kbn/agent-builder-common/attachments';
import type { BrowserApiToolDefinition } from './tools/browser_api_tool';
import type {
AgentsServiceStartContract,
Expand Down Expand Up @@ -72,7 +76,7 @@ export interface EmbeddableConversationProps {
* Content will be fetched when starting a new conversation round.
* It will be appended only if it has changed since previous conversation round.
*/
attachments?: AttachmentInput[];
attachments?: ConversationAttachment[];

/**
* Browser API tools that the agent can use to interact with the page.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface Attachment<
type: Type;
/** data bound to the attachment */
data: DataType;
/** Human-readable description of the attachment */
description?: string;
/** should the attachment be hidden from the user - e.g. for screen context */
hidden?: boolean;
/**
Expand All @@ -28,6 +30,13 @@ export interface Attachment<
* Undefined for by-value attachments.
*/
origin?: string;
/**
* Stable identifier for the logical group this attachment belongs to.
* Attachments sharing the same group_id were submitted together as a single
* logical entity (e.g. multiple alert batches from one bulk-add action).
* Undefined for standalone attachments.
*/
group_id?: string;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export type {
AttachmentRefActor,
AttachmentDiff,
AttachmentInput,
AttachmentGroup,
ConversationAttachment,
UpdateOriginResponse,
} from './versioned_attachment';
export {
Expand All @@ -59,7 +61,9 @@ export {
attachmentRefOperationSchema,
attachmentRefActorSchema,
attachmentInputSchema,
attachmentGroupSchema,
attachmentDiffSchema,
isAttachmentGroup,
getLatestVersion,
getVersion,
createVersionId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ import {
versionedAttachmentSchema,
attachmentVersionRefSchema,
attachmentDiffSchema,
attachmentGroupSchema,
isAttachmentGroup,
type VersionedAttachment,
type AttachmentVersion,
type AttachmentGroup,
type AttachmentInput,
attachmentInputSchema,
} from './versioned_attachment';

Expand Down Expand Up @@ -527,5 +531,75 @@ describe('versioned_attachment', () => {
expect(result.success).toBe(false);
});
});

describe('attachmentGroupSchema', () => {
const validGroup = {
type: 'group',
id: 'g1',
label: '3 Alerts',
items: [
{ type: 'security.alerts', data: { alertIds: ['a', 'b'] } },
{ type: 'security.alerts', data: { alertIds: ['c'] } },
],
};

it('validates a correct AttachmentGroup', () => {
expect(attachmentGroupSchema.safeParse(validGroup).success).toBe(true);
});

it('rejects when type is not "group"', () => {
const result = attachmentGroupSchema.safeParse({ ...validGroup, type: 'text' });
expect(result.success).toBe(false);
});

it('rejects when id is missing', () => {
const { id: _omit, ...rest } = validGroup;
expect(attachmentGroupSchema.safeParse(rest).success).toBe(false);
});

it('rejects when label is missing', () => {
const { label: _omit, ...rest } = validGroup;
expect(attachmentGroupSchema.safeParse(rest).success).toBe(false);
});

it('accepts an empty items array', () => {
expect(attachmentGroupSchema.safeParse({ ...validGroup, items: [] }).success).toBe(true);
});
});

describe('attachmentInputSchema — no groupId field', () => {
it('parses a minimal attachment input', () => {
const result = attachmentInputSchema.safeParse({ type: 'text' });
expect(result.success).toBe(true);
});

it('strips unknown fields including groupId', () => {
const parsed = attachmentInputSchema.safeParse({
type: 'text',
groupId: 'should-be-stripped',
});
expect(parsed.success).toBe(true);
if (parsed.success) {
expect(parsed.data).not.toHaveProperty('groupId');
}
});
});
});

describe('isAttachmentGroup', () => {
it('returns true for an AttachmentGroup', () => {
const g: AttachmentGroup = {
type: 'group',
id: 'g1',
label: '2 Alerts',
items: [{ type: 'security.alerts', data: {} }],
};
expect(isAttachmentGroup(g)).toBe(true);
});

it('returns false for an AttachmentInput', () => {
const a: AttachmentInput = { type: 'text', data: {} };
expect(isAttachmentGroup(a)).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ export interface VersionedAttachment<
readonly?: boolean;
/** The client-provided ID if this attachment was created with one (e.g., via flyout configuration) */
client_id?: string;
/**
* Stable identifier for the logical group this attachment belongs to.
* Attachments sharing the same group_id were submitted together as a single
* logical entity (e.g. multiple alert batches from one bulk-add action).
* Undefined for standalone attachments.
*/
group_id?: string;
/**
* Origin/reference info for attachments created from external sources.
* For saved-object-backed types this is the saved object ID.
Expand Down Expand Up @@ -153,6 +160,16 @@ export interface AttachmentInput<
hidden?: boolean;
/** Whether the attachment should be read-only */
readonly?: boolean;
/**
* Stable identifier for the logical group this attachment belongs to.
* Attachments sharing the same group_id were submitted together as a single
* logical entity (e.g. multiple alert batches from one bulk-add action).
* Undefined for standalone attachments.
*
* When this input is part of an AttachmentGroup, flattenAttachments always
* stamps this field with the group's id, overriding any value set here.
*/
group_id?: string;
}

// Zod schemas for validation
Expand Down Expand Up @@ -198,6 +215,7 @@ export const versionedAttachmentSchema = z.object({
client_id: z.string().optional(),
origin: z.string().optional(),
origin_snapshot_at: z.string().optional(),
group_id: z.string().optional(),
});

export const attachmentInputSchema = z.object({
Expand All @@ -208,8 +226,41 @@ export const attachmentInputSchema = z.object({
description: z.string().optional(),
hidden: z.boolean().optional(),
readonly: z.boolean().optional(),
group_id: z.string().optional(),
});

/**
* A named group of attachments that appears as a single chip in the UI.
* The group is a client-side-only concept — it is flattened to individual
* AttachmentInput items at the serialization boundary before being sent to the server.
*/
export interface AttachmentGroup {
type: 'group';
/** Stable identifier for the group */
id: string;
/** Display label shown on the chip, e.g. "5 Alerts" */
label: string;
/** The individual attachment items that make up this group */
items: AttachmentInput[];
}

export const attachmentGroupSchema = z.object({
type: z.literal('group'),
id: z.string(),
label: z.string(),
items: z.array(attachmentInputSchema),
});

export const isAttachmentGroup = (a: ConversationAttachment): a is AttachmentGroup =>
a.type === 'group';

/**
* Union of a single attachment or a group of attachments.
* This is the type used in client-side conversation state.
* Groups are flattened to AttachmentInput[] before being sent to the server.
*/
export type ConversationAttachment = AttachmentInput | AttachmentGroup;

export const attachmentDiffSchema = z.object({
change_type: z.enum(['create', 'update', 'delete', 'restore']),
summary: z.string(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export interface ReportOptOutParams {
export interface ReportAddToChatClickedParams {
pathway: string;
attachments?: string[];
/** Number of alerts added when using bulk add-to-chat. Absent for single-alert pathways. */
alert_count?: number;
}

export type AgentBuilderUiClickElementKind =
Expand Down Expand Up @@ -480,6 +482,14 @@ const ADD_TO_CHAT_CLICKED_EVENT: AgentBuilderTelemetryEvent = {
optional: true,
},
},
alert_count: {
type: 'integer',
_meta: {
description:
'Number of alerts added via bulk add-to-chat. Absent for single-alert pathways.',
optional: true,
},
},
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,13 +349,14 @@ class AttachmentStateManagerImpl implements AttachmentStateManager {
versions: [version],
current_version: 1,
active: true,
...(input.description && { description: input.description }),
...(input.description !== undefined && { description: input.description }),
...(input.hidden !== undefined && { hidden: input.hidden }),
readonly: input.readonly ?? this.getDefaultReadonly(input.type),
...(input.origin !== undefined && { origin: input.origin }),
// When created with origin (by-reference), record snapshot time for isStale comparison.
// By-value attachments leave this undefined.
...(input.origin !== undefined && { origin_snapshot_at: now }),
...(input.group_id !== undefined && { group_id: input.group_id }),
};

this.attachments.set(id, attachment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ export interface AttachmentTypeDefinition<TType extends string = string, TConten
* Whether attachments of this type are read-only. Defaults to false.
*/
isReadonly?: boolean;
/**
* Maximum content length (in characters) for attachments of this type when presented inline
* to the LLM. Applied per-attachment — each attachment is truncated to its own type's limit.
* Defaults to the global DEFAULT_MAX_CONTENT_LENGTH (10 000).
*/
maxContentLength?: number;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test-results/
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const AlertsDataGrid = typedMemo(
cellActionsOptions,
pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
height,
bulkAddToChatConfig,
...euiDataGridProps
} = props;
const {
Expand All @@ -98,7 +99,14 @@ export const AlertsDataGrid = typedMemo(
refresh: refreshQueries,
columns,
dataGridRef,
services: { http, notifications, application, cases: casesService, settings },
services: {
http,
notifications,
application,
cases: casesService,
agentBuilder: agentBuilderService,
settings,
},
} = renderContext;

const { colorMode, euiTheme } = useEuiTheme();
Expand Down Expand Up @@ -126,6 +134,8 @@ export const AlertsDataGrid = typedMemo(
notifications,
application,
casesService,
agentBuilderService,
bulkAddToChatConfig,
});

const refresh = useCallback(() => {
Expand Down
Loading
Loading