[Agent Builder] Agent plugins: initial installation support#256478
[Agent Builder] Agent plugins: initial installation support#256478pgayvallet merged 39 commits intoelastic:mainfrom
Conversation
📝 WalkthroughWalkthroughThis pull request introduces comprehensive plugin management functionality for the agent builder. It adds OpenAPI-documented endpoints for listing, retrieving, deleting, and installing plugins; implements a full PluginsService with storage, archive parsing, and URL resolution; and extends skill management to support plugin-managed skills with read-only protection. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Router
participant PluginsService
participant PluginClient
participant Elasticsearch as Elasticsearch<br/>(Storage)
participant Archive as Archive<br/>(Zip)
participant SkillService
Client->>Router: POST /plugins/install<br/>(url, pluginName?)
Router->>PluginsService: installPlugin(request, source)
alt URL Source
PluginsService->>Archive: parsePluginFromUrl(url)
Archive->>Archive: downloadAndOpenArchive()
Archive->>Archive: parsePluginZipFile()
Archive-->>PluginsService: ParsedPluginArchive
else File Source
PluginsService->>Archive: parsePluginFromFile(filePath)
Archive-->>PluginsService: ParsedPluginArchive
end
PluginsService->>PluginClient: findByName(name)
PluginClient->>Elasticsearch: query by name
Elasticsearch-->>PluginClient: result or undefined
alt Duplicate Check Passes
PluginsService->>SkillService: bulkCreate(skills)
SkillService->>Elasticsearch: bulk index skills
Elasticsearch-->>SkillService: skillIds
PluginsService->>PluginClient: create(pluginRequest)
PluginClient->>Elasticsearch: index plugin doc
Elasticsearch-->>PluginClient: PersistedPluginDefinition
PluginClient-->>PluginsService: plugin
PluginsService-->>Router: PluginDefinition
Router-->>Client: 200 OK
else Duplicate Found
PluginsService-->>Router: 400 Bad Request
Router-->>Client: Error
end
sequenceDiagram
participant Client
participant Router
participant PluginsService
participant PluginClient
participant SkillClient
participant Elasticsearch as Elasticsearch
Client->>Router: DELETE /plugins/{pluginId}
Router->>PluginsService: deletePlugin(request, pluginId)
PluginsService->>PluginClient: get(pluginId)
PluginClient->>Elasticsearch: fetch plugin doc
Elasticsearch-->>PluginClient: plugin
PluginsService->>SkillClient: deleteByPluginId(pluginName)
SkillClient->>Elasticsearch: deleteByQuery(plugin_id=...)
Elasticsearch-->>SkillClient: acknowledged
PluginsService->>PluginClient: delete(pluginId)
PluginClient->>Elasticsearch: delete plugin doc
Elasticsearch-->>PluginClient: success
PluginsService-->>Router: void
Router-->>Client: 200 OK {success: true}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (14)
x-pack/platform/packages/shared/agent-builder/agent-builder-server/skills/internal.ts (1)
62-65: Consider using camelCase for the property name.The property
plugin_iduses snake_case, but the coding guidelines specify camelCase for object keys. If this naming is intentional for consistency with persistence or API contracts across the codebase, please disregard.♻️ Suggested naming change
/** - * If this skill was installed from a plugin, the plugin name. + * If this skill was installed from a plugin, the plugin ID. */ - plugin_id?: string; + pluginId?: string;As per coding guidelines: "Use
camelCasefor functions, variables, and object keys."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/packages/shared/agent-builder/agent-builder-server/skills/internal.ts` around lines 62 - 65, The property plugin_id should be renamed to camelCase pluginId across the code to follow coding guidelines; update the declaration in internal.ts (replace plugin_id with pluginId), then update all references/usages (type annotations, object literals, destructuring, assignments) to use pluginId and adjust any serialization/deserialization or API mapping code that expects snake_case (add mapping from plugin_id → pluginId or update serializers) and update affected tests/types to preserve backward compatibility where needed.x-pack/platform/packages/shared/agent-builder/agent-builder-common/plugins/plugin_definition.ts (1)
21-57: Make the plugin DTOs readonly.These interfaces represent persisted, read-only plugin state, but all of the arrays are mutable today. Mark the exported fields/arrays as
readonly/ReadonlyArrayso consumers cannot accidentally mutateskill_idsorunmanaged_assetsin memory.As per coding guidelines, "Prefer
readonlyandas constfor immutable structures in TypeScript."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/packages/shared/agent-builder/agent-builder-common/plugins/plugin_definition.ts` around lines 21 - 57, The DTOs are mutable; make UnmanagedPluginAssets, PluginManifestMetadata, and PluginDefinition use readonly fields and ReadonlyArray for all arrays so consumers cannot mutate them: mark each property in UnmanagedPluginAssets (commands, agents, hooks, mcp_servers, output_styles, lsp_servers) as readonly ReadonlyArray<string>, mark PluginManifestMetadata.keywords as readonly ReadonlyArray<string> (and other optional fields readonly), and update PluginDefinition to use readonly for all top-level properties (id, name, version, description, manifest, source_url, skill_ids as ReadonlyArray<string>, unmanaged_assets, created_at, updated_at) so the DTOs are fully immutable in memory.x-pack/platform/plugins/shared/agent_builder/server/services/skills/utils.ts (1)
15-30: Omitplugin_idwhen it is unset.Right now this helper always creates the
plugin_idkey, so plain-object consumers still see it as present withundefined. If the public contract is “only include this for plugin-managed skills,” build it conditionally instead.♻️ Proposed change
export const internalToPublicDefinition = async ( skill: InternalSkillDefinition ): Promise<PublicSkillDefinition> => ({ id: skill.id, name: skill.name, description: skill.description, content: skill.content, referenced_content: skill.referencedContent?.map((rc) => ({ name: rc.name, relativePath: rc.relativePath, content: rc.content, })), tool_ids: await skill.getRegistryTools(), readonly: skill.readonly, - plugin_id: skill.plugin_id, + ...(skill.plugin_id !== undefined ? { plugin_id: skill.plugin_id } : {}), });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/plugins/shared/agent_builder/server/services/skills/utils.ts` around lines 15 - 30, internalToPublicDefinition always includes the plugin_id key even when undefined; change it to only add plugin_id when skill.plugin_id is set (e.g., check for null/undefined) so the returned plain object omits the key for non-plugin-managed skills—update the internalToPublicDefinition return construction to conditionally include plugin_id (keeping the rest of the fields and tool_ids await logic intact).x-pack/platform/test/agent_builder_api_integration/apis/plugins/index.ts (1)
10-14: Add an explicit return type and prefer a typed const export here.This entrypoint is new exported TS code, so I’d annotate it with
: voidand switch to the preferred const-arrow form.♻️ Proposed change
-export default function ({ loadTestFile }: FtrProviderContext) { +const registerPluginsApiTests = ({ loadTestFile }: FtrProviderContext): void => { describe('Plugins API', function () { loadTestFile(require.resolve('./installation')); }); -} +}; + +export default registerPluginsApiTests;As per coding guidelines, "Prefer explicit return types for public APIs and exported functions in TypeScript" and "Prefer const arrow functions in TypeScript."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/test/agent_builder_api_integration/apis/plugins/index.ts` around lines 10 - 14, The exported unnamed function lacks an explicit return type and should be a typed const-arrow export; change the default export to a named const arrow like e.g. const pluginsApi = ({ loadTestFile }: FtrProviderContext): void => { describe('Plugins API', function () { loadTestFile(require.resolve('./installation')); }); }; and then export default pluginsApi; ensuring the parameter is typed with FtrProviderContext and the function return type is explicitly void.x-pack/platform/plugins/shared/agent_builder/server/services/skills/skill_service.test.ts (1)
30-33: Give the new persisted-client mocks async defaults.
bulkCreateanddeleteByPluginIdare async on the real client. Leaving them as barejest.fn()makes the default mock returnundefined, which can hide contract mismatches or produce misleading failures once these paths are exercised.♻️ Proposed fix
- bulkCreate: jest.fn(), + bulkCreate: jest.fn().mockResolvedValue([]), ... - deleteByPluginId: jest.fn(), + deleteByPluginId: jest.fn().mockResolvedValue(undefined),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/plugins/shared/agent_builder/server/services/skills/skill_service.test.ts` around lines 30 - 33, The mocks for the persisted-client need async defaults: change the plain jest.fn() for bulkCreate and deleteByPluginId to resolved mocks so they return a Promise (e.g., jest.fn().mockResolvedValue(undefined) or an appropriate resolved value) instead of undefined synchronously; update the mock declarations for bulkCreate and deleteByPluginId in the test file so they use mockResolvedValue(...) while leaving update and delete as-is.x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/download_plugin.test.ts (1)
11-19: Replace theunknown[]passthrough mocks with typed signatures.These mocks work, but they lose the
openZipArchive/parsePluginZipFilecontract and introduce newunknownusage in TS test code. Typing the forwarded parameters directly keeps the mock surface aligned with the real functions.As per coding guidelines, `**/*.{ts,tsx}`: Use TypeScript for all new code; avoid `any` and `unknown`.♻️ Proposed fix
jest.mock('../archive', () => ({ - openZipArchive: (...args: unknown[]) => mockOpenZipArchive(...args), + openZipArchive: (filePath: string) => mockOpenZipArchive(filePath), })); const mockParsePluginZipFile = jest.fn(); jest.mock('../parsing', () => ({ - parsePluginZipFile: (...args: unknown[]) => mockParsePluginZipFile(...args), + parsePluginZipFile: (archive: ZipArchive) => mockParsePluginZipFile(archive), PluginArchiveError: class extends Error {}, }));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/download_plugin.test.ts` around lines 11 - 19, The test mocks currently forward parameters as (...args: unknown[]) which loses the real function signatures for openZipArchive and parsePluginZipFile; replace the unknown[] passthroughs with the actual parameter types by using Parameters<typeof openZipArchive> and Parameters<typeof parsePluginZipFile> (or import the functions and use their parameter types) when defining the mock factories so the mockOpenZipArchive and mockParsePluginZipFile preserve the real contracts; keep the PluginArchiveError class export as-is.x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/create_scoped_archive.test.ts (1)
11-23: Consider extractingcreateMockArchiveinto a shared test helper.The same
ZipArchivetest double is duplicated inx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/parsing/parse_plugin_zip_file.test.tsat Line 11-Line 23. Pulling it into one helper would make future interface changes less error-prone.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/create_scoped_archive.test.ts` around lines 11 - 23, Extract the duplicated ZipArchive test double into a single test helper by moving the createMockArchive function (which returns an object implementing hasEntry, getEntryPaths, getEntryContent and close) into a shared test utilities module and replace the local definitions in both tests with imports of that helper; ensure the helper exports createMockArchive and uses the same function signature (files: Record<string,string>) so existing tests referencing createMockArchive, ZipArchive, getEntryContent, hasEntry and close continue to work without further changes.x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/open_zip_archive.test.ts (2)
14-18: Let the test helper generate raw-byte fixtures.
ZipArchive.getEntryContent()returnsBuffer, but this helper only builds UTF-8 text entries. That means the “byte-for-byte” case still passes if the implementation accidentally round-trips through strings.♻️ Suggested change
-const createTestZip = async (files: Record<string, string>, destPath: string): Promise<void> => { +const createTestZip = async ( + files: Record<string, string | Buffer>, + destPath: string +): Promise<void> => { return new Promise((resolve, reject) => { const zipFile = new yazl.ZipFile(); for (const [filePath, content] of Object.entries(files)) { - zipFile.addBuffer(Buffer.from(content, 'utf-8'), filePath); + zipFile.addBuffer( + typeof content === 'string' ? Buffer.from(content, 'utf-8') : content, + filePath + ); }Then use one binary fixture in the byte-preservation test, e.g.
Buffer.from([0x00, 0xff, 0x0a]).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/open_zip_archive.test.ts` around lines 14 - 18, The helper createTestZip currently encodes every entry as UTF-8 text which prevents true byte-for-byte tests; modify createTestZip so it accepts file contents as either strings or Buffers and when adding entries to the yazl.ZipFile call zipFile.addBuffer(...) with the raw Buffer when the value is a Buffer, otherwise convert strings with Buffer.from(value, 'utf-8'); reference createTestZip and ZipArchive.getEntryContent in the test and then add a binary fixture like Buffer.from([0x00, 0xff, 0x0a]) to the byte-preservation test so the test verifies exact bytes are preserved.
98-104: Assert the missing-entry path as a rejected promise.
getEntryContent()is aPromise<Buffer>API, buttoThrow()only covers synchronous exceptions. This test currently locks in a sync-throw implementation detail instead of the async contract.✅ Suggested change
- archive = await openZipArchive(zipPath); - - expect(() => archive!.getEntryContent('does-not-exist.txt')).toThrow(/not found in archive/); + const zipArchive = await openZipArchive(zipPath); + archive = zipArchive; + + await expect( + Promise.resolve().then(() => zipArchive.getEntryContent('does-not-exist.txt')) + ).rejects.toThrow(/not found in archive/);Run this to confirm the contract and current implementation shape:
#!/bin/bash set -euo pipefail file="$(fd 'open_zip_archive\.ts$' x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive | head -n1)" rg -n -C4 '\binterface ZipArchive\b|\bgetEntryContent\s*\(' "$file"Expected: the contract shows
Promise<Buffer>; if the current implementation throws before returning a promise, the wrapper above still keeps the test aligned with the public async API.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/open_zip_archive.test.ts` around lines 98 - 104, The test is asserting a synchronous throw but getEntryContent() returns a Promise; change the assertion to expect the promise to reject: call archive!.getEntryContent('does-not-exist.txt') and use Jest's .rejects.toThrow(/not found in archive/) (keeping the same regex) so the test verifies the async contract of getEntryContent from openZipArchive; update the test invocation accordingly to await or return the expect(...).rejects chain.oas_docs/output/kibana.yaml (2)
3236-3245: Code sample placeholders should match the parameter name.The path parameter is named
pluginId, but the code samples use{id}as the placeholder. For clarity and consistency, consider using{pluginId}in the examples.📝 Suggested fix
x-codeSamples: - lang: curl source: | curl \ - -X DELETE "${KIBANA_URL}/api/agent_builder/plugins/{id}" \ + -X DELETE "${KIBANA_URL}/api/agent_builder/plugins/{pluginId}" \ -H "Authorization: ApiKey ${API_KEY}" \ -H "kbn-xsrf: true" - lang: Console source: | - DELETE kbn://api/agent_builder/plugins/{id} + DELETE kbn://api/agent_builder/plugins/{pluginId}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@oas_docs/output/kibana.yaml` around lines 3236 - 3245, The code samples under x-codeSamples use the placeholder {id} but the OpenAPI path parameter is named pluginId; update both examples (the curl and Console entries) to use {pluginId} so the placeholder matches the parameter name (look for x-codeSamples, the curl "-X DELETE" sample and the Console "DELETE kbn://api/agent_builder/plugins/{id}" sample and replace {id} with {pluginId}).
3303-3311: Code sample placeholders should match the parameter name.Same issue as the DELETE endpoint - the code samples use
{id}but the path parameter ispluginId.📝 Suggested fix
x-codeSamples: - lang: curl source: | curl \ - -X GET "${KIBANA_URL}/api/agent_builder/plugins/{id}" \ + -X GET "${KIBANA_URL}/api/agent_builder/plugins/{pluginId}" \ -H "Authorization: ApiKey ${API_KEY}" - lang: Console source: | - GET kbn://api/agent_builder/plugins/{id} + GET kbn://api/agent_builder/plugins/{pluginId}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@oas_docs/output/kibana.yaml` around lines 3303 - 3311, The code samples under x-codeSamples for the GET operation use the placeholder "{id}" but the path parameter is named "pluginId"; update the sample sources in the GET x-codeSamples (both the curl sample and the Console sample) to use "{pluginId}" so they match the operation parameter name (e.g., curl -X GET "${KIBANA_URL}/api/agent_builder/plugins/{pluginId}" and GET kbn://api/agent_builder/plugins/{pluginId}).x-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/converters.test.ts (1)
84-87: Avoidanyin the fixture mutation.This edge case can be modeled with the helper input instead of dropping type safety. If
createPluginDocument({ skill_ids: undefined })does not type-check under the current compiler flags, widen the helper input rather than casting here.As per coding guidelines, "Use TypeScript for all new code; avoid `any` and `unknown`."💡 Suggested fix
- const doc = createPluginDocument(); - (doc._source as any).skill_ids = undefined; + const doc = createPluginDocument({ skill_ids: undefined });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/converters.test.ts` around lines 84 - 87, The test is mutating the fixture with an any cast; instead update the fixture helper to accept an optional skill_ids so you can call createPluginDocument({ skill_ids: undefined }) instead of casting, or widen the helper's input type to include skill_ids?: string[] | undefined; modify the createPluginDocument signature (and its input type/interface) used by the test so the edge-case can be modeled directly and remove the (doc._source as any) mutation in the test.x-pack/platform/plugins/shared/agent_builder/server/services/plugins/plugin_service.ts (1)
22-23: Remove theeslint-disablecomment and address the empty interface.Per coding guidelines, linting errors should not be suppressed. Consider using a type alias or adding a placeholder comment property if the interface will be extended later.
♻️ Suggested fix
-// eslint-disable-next-line `@typescript-eslint/no-empty-interface` -export interface PluginsServiceSetup {} +export type PluginsServiceSetup = Record<string, never>;Alternatively, if the interface will gain members soon, add a TODO comment explaining the planned additions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/plugin_service.ts` around lines 22 - 23, Remove the eslint-disable comment and address the empty interface PluginsServiceSetup: either convert the empty interface to an equivalent type alias (e.g., type PluginsServiceSetup = {}) or keep the interface but add a self-documenting placeholder (a TODO comment plus a non-exported placeholder property or JSDoc indicating future members) so linting is satisfied without suppressing the rule; update the declaration for PluginsServiceSetup accordingly and ensure no eslint-disable comment remains.x-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/client.ts (1)
150-153: Avoid non-null assertion on_source.Per coding guidelines, non-null assertions (
!) should be avoided unless locally justified. The_sourcefield could theoretically be undefined. Add a guard to handle this case.♻️ Suggested fix
async update(pluginId: string, update: PluginUpdateRequest): Promise<PersistedPluginDefinition> { const document = await this._getById(pluginId); if (!document) { throw createPluginNotFoundError({ pluginId }); } + if (!document._source) { + throw createPluginNotFoundError({ pluginId }); + } const updatedAttributes = updateRequestToEs({ - current: document._source!, + current: document._source, update, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/client.ts` around lines 150 - 153, The code calls updateRequestToEs with document._source using a non-null assertion; instead, guard against a missing source by checking document._source before calling updateRequestToEs (e.g., if (!document._source) { /* handle: throw a descriptive Error or return a failed Result/log and exit early */ }), then only call updateRequestToEs({ current: document._source, update }) when present; update references to updatedAttributes and any downstream logic to handle the early-failure path accordingly so you don't rely on the non-null assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@oas_docs/output/kibana.serverless.yaml`:
- Around line 3165-3174: Update the x-codeSamples entries so the path parameter
matches the documented operation parameter name: replace the occurrences of
"{id}" with "{pluginId}" in the curl and Console samples under the x-codeSamples
block (the DELETE sample for agent_builder/plugins) and apply the same
replacement for the other duplicate sample block later; ensure both the curl "-X
DELETE" URL and the Console "DELETE kbn://..." sample use "{pluginId}" so the
sample route shape matches the operation definition.
- Around line 3247-3345: The OpenAPI entry for operationId
post-agent-builder-plugins-install only documents JSON URL installs; add an
alternative multipart/form-data requestBody variant to support direct ZIP
uploads by including a file field (e.g., name: file, type: string, format:
binary) and the optional plugin_name field, update the content examples to
include a zipUploadExample (showing multipart form with a .zip file and optional
plugin_name), and ensure the schema allows either application/json (url +
optional plugin_name) or multipart/form-data (file + optional plugin_name) so
generated docs/clients cover both install modes.
In
`@x-pack/platform/packages/shared/agent-builder/agent-builder-common/skills/definition.ts`:
- Around line 51-54: The comment and field name are inconsistent: the field
plugin_id currently says "the plugin name"—decide whether this property should
be an identifier or a human-readable name; then update the TypeScript definition
and JSDoc accordingly (either rename plugin_id to plugin_name everywhere in this
file or change the comment to state "the plugin id/unique identifier") and apply
the same change to the other occurrence around lines 85-88 so consumers see a
consistent property name and description; reference the property symbol
plugin_id (or plugin_name if renaming) when making the edits.
In
`@x-pack/platform/plugins/shared/agent_builder/server/routes/examples/plugins_install.yaml`:
- Around line 1-18: Add a new example documenting the direct-upload (ZIP)
install flow in the application/json examples block by creating an
installPluginFromUploadExample entry that shows the expected payload for uploads
(e.g., a form-data file field named "file" containing the ZIP or, if the API
accepts base64 JSON, a "file_base64" field) and include the optional
"plugin_name" override; update the corresponding examples section referenced in
lines 51-67 as well so the direct-upload example appears alongside
installPluginFromGithubExample, installPluginFromZipExample, and
installPluginWithNameOverrideExample.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/plugin_service.ts`:
- Around line 140-143: The delete flow is non-atomic: calling
skillClient.deleteByPluginId(plugin.name) before pluginClient.delete(pluginId)
can leave skills removed but the plugin present if the latter fails; update the
deletion to remove the plugin first using pluginClient.delete(pluginId) and then
delete associated skills, or wrap both calls in a try/catch to rollback or retry
on failure to keep state consistent; also rename the confusing
skillClient.deleteByPluginId to deleteByPluginName (and update its usages) since
it accepts plugin.name, and ensure getScopedClients callers and references
(pluginClient, skillClient, deleteByPluginId, pluginId, plugin.name) are updated
accordingly.
- Around line 116-130: Wrap the skill creation + plugin creation sequence in a
try/catch: call skillClient.bulkCreate(createRequests) as before, then in the
try block build createRequest via parsedArchiveToCreateRequest({ parsedArchive,
sourceUrl, skillIds, nameOverride: pluginNameOverride }) and call await
pluginClient.create(createRequest); in the catch block, delete the previously
created skills using the skillIds (e.g., call skillClient.delete / bulkDelete
with skillIds) to clean up orphaned skills, then rethrow the original error so
callers see the failure; ensure this touches the createRequests/skillIds,
skillClient.bulkCreate, and pluginClient.create sites only.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/create_scoped_archive.ts`:
- Around line 23-25: The normalization in createScopedArchive makes an empty
prefix become '/' which breaks flat archives; change createScopedArchive so that
if the provided prefix is an empty string (as returned by
detectArchiveRootPrefix) it returns the original archive unchanged, otherwise
normalize non-empty prefixes to ensure they end with '/' and construct the
ScopedZipArchive; reference createScopedArchive, ScopedZipArchive and
detectArchiveRootPrefix when making this conditional return.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/open_zip_archive.ts`:
- Around line 20-35: The zip enumeration lacks handling for zip-level errors: in
the yauzl.open callback (where yauzl.open is called and ZipArchiveImpl is
constructed from entries), add a zipFile.on('error', ...) listener that closes
the zipFile (call zipFile.close()) and rejects the surrounding Promise with the
received error (or a typed wrapper) so readEntry() failures or archive
corruption won't leave the handle open or leave the promise pending; ensure this
handler is registered before calling zipFile.readEntry() and removed/ignored on
normal resolution.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/parsing/parse_skill_file.ts`:
- Around line 16-28: The frontmatter parsing fails on Windows CRLF endings
because frontmatterRegex uses LF-only "\n"; update parsing in parseSkillFile to
handle CRLF by either normalizing rawContent (replace \r\n with \n) before
matching or changing frontmatterRegex to use "\r?\n" (e.g.,
/^---\s*\r?\n([\s\S]*?)---\s*\r?\n?([\s\S]*)$/) so ParsedSkillFileResult
correctly extracts meta and content for files with CRLF line endings.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/download_plugin.ts`:
- Around line 111-127: downloadToFile currently calls fetch(url, { redirect:
'follow' }) allowing unvalidated redirects and making the endpoint SSRFable;
update downloadToFile to validate the initial URL and every redirect hop against
a public-address policy before following. Implement or call a helper like
isPublicAddress(hostOrIp) to reject loopback, link-local, and private addresses,
change fetch to redirect: 'manual' and perform a controlled redirect loop (with
a max hop limit), validate each Location header URL with isPublicAddress before
continuing, and only stream the final response body after all hops pass
validation (keep getSafePath/createWriteStream/pipeline usage unchanged once URL
is approved).
- Around line 102-108: parsePluginFromFile currently opens the zip and calls
parsePluginZipFile on the raw root, which fails for GitHub-style archives
wrapped under a single top-level folder (e.g., repo-ref/); update
parsePluginFromFile to inspect the archive entries from openZipArchive(…) and if
all entries share a common single-directory prefix (like "repo-ref/"), set that
as the root scope and pass the scoped view into parsePluginZipFile (or call
parsePluginZipFile with that prefix) so plugin.json can be found; ensure you
still close archive in the finally block and preserve the
Promise<ParsedPluginArchive> return.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/parse_github_url.test.ts`:
- Around line 34-48: The tests and parseGithubUrl implementation incorrectly
hard-code ref: 'main' for bare GitHub URLs; update the parseGithubUrl function
so it does not assume 'main'—either return ref as undefined (or null) for URLs
like 'https://github.com/owner/repo' or make parseGithubUrl require an explicit
ref, and move default-branch resolution to a separate step that queries the
GitHub API when building archive URLs; update the tests
(parse_github_url.test.ts) to expect no default ref (or an error) instead of
'main' and add new tests for the separate default-branch resolution logic if you
implement the API lookup.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/parse_github_url.ts`:
- Around line 42-58: parseGithubUrl is incorrectly defaulting ref to 'main' for
bare repo URLs, which breaks repos with other default branches; modify
parseGithubUrl so it does not silently assume 'main'—either require callers to
supply /tree/{ref} for bare repo URLs (throw an error when ref is missing) or
defer defaulting until download time by returning ref as undefined and letting
the caller or a new helper (e.g., resolveDefaultBranch) query the GitHub API to
determine the repository's default branch before building the archive URL;
update the function signature and any callers of parseGithubUrl accordingly to
handle a possibly undefined ref.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/save_uploaded_file.ts`:
- Around line 21-30: The saveUploadedFile function currently awaits
pipeline(stream, writeStream) and if pipeline rejects the partially written
tmp/<uuid>.zip is never removed; wrap the pipeline call in try/catch so that on
any error you call deleteFile(fileName, { volume: VOLUME }) (or the cleanup
logic used in the success path) before rethrowing the error, ensuring you still
reference the same fileName/fullPath from getSafePath/createWriteStream;
optionally also ensure writeStream is properly closed/destroyed in the
catch/finally block to avoid resource leaks.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/skills/persisted/client/client.ts`:
- Around line 138-153: bulkCreate is incorrectly promoting the business key
attributes.id to the Elasticsearch _id (in bulkCreate -->
storage.getClient().bulk), making the id global across spaces; change bulkCreate
to use the same storage-id strategy as create() by not hard-coding _id (let ES
assign an opaque id) or by constructing a space-qualified/storage-generated id
via the same helper used by create(), keeping createAttributes and space-scoping
intact so duplicates fail instead of clobbering other-space documents.
---
Nitpick comments:
In `@oas_docs/output/kibana.yaml`:
- Around line 3236-3245: The code samples under x-codeSamples use the
placeholder {id} but the OpenAPI path parameter is named pluginId; update both
examples (the curl and Console entries) to use {pluginId} so the placeholder
matches the parameter name (look for x-codeSamples, the curl "-X DELETE" sample
and the Console "DELETE kbn://api/agent_builder/plugins/{id}" sample and replace
{id} with {pluginId}).
- Around line 3303-3311: The code samples under x-codeSamples for the GET
operation use the placeholder "{id}" but the path parameter is named "pluginId";
update the sample sources in the GET x-codeSamples (both the curl sample and the
Console sample) to use "{pluginId}" so they match the operation parameter name
(e.g., curl -X GET "${KIBANA_URL}/api/agent_builder/plugins/{pluginId}" and GET
kbn://api/agent_builder/plugins/{pluginId}).
In
`@x-pack/platform/packages/shared/agent-builder/agent-builder-common/plugins/plugin_definition.ts`:
- Around line 21-57: The DTOs are mutable; make UnmanagedPluginAssets,
PluginManifestMetadata, and PluginDefinition use readonly fields and
ReadonlyArray for all arrays so consumers cannot mutate them: mark each property
in UnmanagedPluginAssets (commands, agents, hooks, mcp_servers, output_styles,
lsp_servers) as readonly ReadonlyArray<string>, mark
PluginManifestMetadata.keywords as readonly ReadonlyArray<string> (and other
optional fields readonly), and update PluginDefinition to use readonly for all
top-level properties (id, name, version, description, manifest, source_url,
skill_ids as ReadonlyArray<string>, unmanaged_assets, created_at, updated_at) so
the DTOs are fully immutable in memory.
In
`@x-pack/platform/packages/shared/agent-builder/agent-builder-server/skills/internal.ts`:
- Around line 62-65: The property plugin_id should be renamed to camelCase
pluginId across the code to follow coding guidelines; update the declaration in
internal.ts (replace plugin_id with pluginId), then update all references/usages
(type annotations, object literals, destructuring, assignments) to use pluginId
and adjust any serialization/deserialization or API mapping code that expects
snake_case (add mapping from plugin_id → pluginId or update serializers) and
update affected tests/types to preserve backward compatibility where needed.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/client.ts`:
- Around line 150-153: The code calls updateRequestToEs with document._source
using a non-null assertion; instead, guard against a missing source by checking
document._source before calling updateRequestToEs (e.g., if (!document._source)
{ /* handle: throw a descriptive Error or return a failed Result/log and exit
early */ }), then only call updateRequestToEs({ current: document._source,
update }) when present; update references to updatedAttributes and any
downstream logic to handle the early-failure path accordingly so you don't rely
on the non-null assertion.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/converters.test.ts`:
- Around line 84-87: The test is mutating the fixture with an any cast; instead
update the fixture helper to accept an optional skill_ids so you can call
createPluginDocument({ skill_ids: undefined }) instead of casting, or widen the
helper's input type to include skill_ids?: string[] | undefined; modify the
createPluginDocument signature (and its input type/interface) used by the test
so the edge-case can be modeled directly and remove the (doc._source as any)
mutation in the test.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/plugin_service.ts`:
- Around line 22-23: Remove the eslint-disable comment and address the empty
interface PluginsServiceSetup: either convert the empty interface to an
equivalent type alias (e.g., type PluginsServiceSetup = {}) or keep the
interface but add a self-documenting placeholder (a TODO comment plus a
non-exported placeholder property or JSDoc indicating future members) so linting
is satisfied without suppressing the rule; update the declaration for
PluginsServiceSetup accordingly and ensure no eslint-disable comment remains.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/create_scoped_archive.test.ts`:
- Around line 11-23: Extract the duplicated ZipArchive test double into a single
test helper by moving the createMockArchive function (which returns an object
implementing hasEntry, getEntryPaths, getEntryContent and close) into a shared
test utilities module and replace the local definitions in both tests with
imports of that helper; ensure the helper exports createMockArchive and uses the
same function signature (files: Record<string,string>) so existing tests
referencing createMockArchive, ZipArchive, getEntryContent, hasEntry and close
continue to work without further changes.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/open_zip_archive.test.ts`:
- Around line 14-18: The helper createTestZip currently encodes every entry as
UTF-8 text which prevents true byte-for-byte tests; modify createTestZip so it
accepts file contents as either strings or Buffers and when adding entries to
the yazl.ZipFile call zipFile.addBuffer(...) with the raw Buffer when the value
is a Buffer, otherwise convert strings with Buffer.from(value, 'utf-8');
reference createTestZip and ZipArchive.getEntryContent in the test and then add
a binary fixture like Buffer.from([0x00, 0xff, 0x0a]) to the byte-preservation
test so the test verifies exact bytes are preserved.
- Around line 98-104: The test is asserting a synchronous throw but
getEntryContent() returns a Promise; change the assertion to expect the promise
to reject: call archive!.getEntryContent('does-not-exist.txt') and use Jest's
.rejects.toThrow(/not found in archive/) (keeping the same regex) so the test
verifies the async contract of getEntryContent from openZipArchive; update the
test invocation accordingly to await or return the expect(...).rejects chain.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/download_plugin.test.ts`:
- Around line 11-19: The test mocks currently forward parameters as (...args:
unknown[]) which loses the real function signatures for openZipArchive and
parsePluginZipFile; replace the unknown[] passthroughs with the actual parameter
types by using Parameters<typeof openZipArchive> and Parameters<typeof
parsePluginZipFile> (or import the functions and use their parameter types) when
defining the mock factories so the mockOpenZipArchive and mockParsePluginZipFile
preserve the real contracts; keep the PluginArchiveError class export as-is.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/skills/skill_service.test.ts`:
- Around line 30-33: The mocks for the persisted-client need async defaults:
change the plain jest.fn() for bulkCreate and deleteByPluginId to resolved mocks
so they return a Promise (e.g., jest.fn().mockResolvedValue(undefined) or an
appropriate resolved value) instead of undefined synchronously; update the mock
declarations for bulkCreate and deleteByPluginId in the test file so they use
mockResolvedValue(...) while leaving update and delete as-is.
In
`@x-pack/platform/plugins/shared/agent_builder/server/services/skills/utils.ts`:
- Around line 15-30: internalToPublicDefinition always includes the plugin_id
key even when undefined; change it to only add plugin_id when skill.plugin_id is
set (e.g., check for null/undefined) so the returned plain object omits the key
for non-plugin-managed skills—update the internalToPublicDefinition return
construction to conditionally include plugin_id (keeping the rest of the fields
and tool_ids await logic intact).
In `@x-pack/platform/test/agent_builder_api_integration/apis/plugins/index.ts`:
- Around line 10-14: The exported unnamed function lacks an explicit return type
and should be a typed const-arrow export; change the default export to a named
const arrow like e.g. const pluginsApi = ({ loadTestFile }: FtrProviderContext):
void => { describe('Plugins API', function () {
loadTestFile(require.resolve('./installation')); }); }; and then export default
pluginsApi; ensuring the parameter is typed with FtrProviderContext and the
function return type is explicitly void.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: cdf55c63-36e9-4027-bd0f-a00671637b39
📒 Files selected for processing (71)
oas_docs/output/kibana.serverless.yamloas_docs/output/kibana.yamlx-pack/platform/packages/shared/agent-builder/agent-builder-common/base/errors.tsx-pack/platform/packages/shared/agent-builder/agent-builder-common/index.tsx-pack/platform/packages/shared/agent-builder/agent-builder-common/plugins/index.tsx-pack/platform/packages/shared/agent-builder/agent-builder-common/plugins/parsing.tsx-pack/platform/packages/shared/agent-builder/agent-builder-common/plugins/plugin_definition.tsx-pack/platform/packages/shared/agent-builder/agent-builder-common/skills/definition.tsx-pack/platform/packages/shared/agent-builder/agent-builder-server/skills/internal.tsx-pack/platform/plugins/shared/agent_builder/common/http_api/plugins.tsx-pack/platform/plugins/shared/agent_builder/moon.ymlx-pack/platform/plugins/shared/agent_builder/server/config.tsx-pack/platform/plugins/shared/agent_builder/server/plugin.tsx-pack/platform/plugins/shared/agent_builder/server/routes/examples/plugins_delete.yamlx-pack/platform/plugins/shared/agent_builder/server/routes/examples/plugins_get_by_id.yamlx-pack/platform/plugins/shared/agent_builder/server/routes/examples/plugins_install.yamlx-pack/platform/plugins/shared/agent_builder/server/routes/examples/plugins_list.yamlx-pack/platform/plugins/shared/agent_builder/server/routes/index.tsx-pack/platform/plugins/shared/agent_builder/server/routes/plugins.tsx-pack/platform/plugins/shared/agent_builder/server/services/create_services.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/client.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/client.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/converters.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/converters.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/index.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/storage.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/client/types.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/index.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/plugin_service.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/plugin_service.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/create_scoped_archive.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/create_scoped_archive.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/index.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/open_zip_archive.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/archive/open_zip_archive.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/index.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/parsing/index.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/parsing/parse_plugin_zip_file.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/parsing/parse_plugin_zip_file.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/parsing/parse_skill_file.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/parsing/parse_skill_file.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/download_plugin.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/download_plugin.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/index.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/parse_github_url.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/parse_github_url.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/resolve_plugin_url.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/resolve_plugin_url.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/save_uploaded_file.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/save_uploaded_file.tsx-pack/platform/plugins/shared/agent_builder/server/services/skills/persisted/client/client.tsx-pack/platform/plugins/shared/agent_builder/server/services/skills/persisted/client/converters.tsx-pack/platform/plugins/shared/agent_builder/server/services/skills/persisted/client/storage.tsx-pack/platform/plugins/shared/agent_builder/server/services/skills/persisted/client/types.tsx-pack/platform/plugins/shared/agent_builder/server/services/skills/persisted/converter.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/skills/persisted/converter.tsx-pack/platform/plugins/shared/agent_builder/server/services/skills/persisted/provider.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/skills/skill_service.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/skills/utils.test.tsx-pack/platform/plugins/shared/agent_builder/server/services/skills/utils.tsx-pack/platform/plugins/shared/agent_builder/server/services/types.tsx-pack/platform/plugins/shared/agent_builder/tsconfig.jsonx-pack/platform/test/agent_builder_api_integration/apis/index.tsx-pack/platform/test/agent_builder_api_integration/apis/plugins/assets/test-plugin/plugin.jsonx-pack/platform/test/agent_builder_api_integration/apis/plugins/assets/test-plugin/skills/test-skill/SKILL.mdx-pack/platform/test/agent_builder_api_integration/apis/plugins/assets/test-plugin/skills/test-skill/reference.txtx-pack/platform/test/agent_builder_api_integration/apis/plugins/index.tsx-pack/platform/test/agent_builder_api_integration/apis/plugins/installation.tsx-pack/platform/test/agent_builder_api_integration/configs/config.stateful.tsx-pack/platform/test/agent_builder_api_integration/utils/plugins_server/index.tsx-pack/platform/test/agent_builder_api_integration/utils/plugins_server/plugins_server.ts
| /** | ||
| * If this skill was installed from a plugin, the plugin name. | ||
| */ | ||
| plugin_id?: string; |
There was a problem hiding this comment.
Minor documentation inconsistency: field name vs. comment description.
The field is named plugin_id but the comments state it holds "the plugin name". Clarify whether this should be:
plugin_idholding a unique plugin identifier, orplugin_nameholding the human-readable plugin name
This affects API consumers' understanding of the expected value.
Suggested documentation fix (if plugin name is correct)
/**
- * If this skill was installed from a plugin, the plugin name.
+ * If this skill was installed from a plugin, the plugin identifier.
*/
plugin_id?: string;Or rename to plugin_name if it truly stores the name rather than an ID.
Also applies to: 85-88
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@x-pack/platform/packages/shared/agent-builder/agent-builder-common/skills/definition.ts`
around lines 51 - 54, The comment and field name are inconsistent: the field
plugin_id currently says "the plugin name"—decide whether this property should
be an identifier or a human-readable name; then update the TypeScript definition
and JSDoc accordingly (either rename plugin_id to plugin_name everywhere in this
file or change the comment to state "the plugin id/unique identifier") and apply
the same change to the other occurrence around lines 85-88 so consumers see a
consistent property name and description; reference the property symbol
plugin_id (or plugin_name if renaming) when making the edits.
x-pack/platform/plugins/shared/agent_builder/server/routes/examples/plugins_install.yaml
Show resolved
Hide resolved
x-pack/platform/plugins/shared/agent_builder/server/services/plugins/plugin_service.ts
Show resolved
Hide resolved
...tform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/download_plugin.ts
Outdated
Show resolved
Hide resolved
...plugins/shared/agent_builder/server/services/plugins/utils/sourcing/parse_github_url.test.ts
Show resolved
Hide resolved
...form/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/parse_github_url.ts
Show resolved
Hide resolved
...rm/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/save_uploaded_file.ts
Show resolved
Hide resolved
x-pack/platform/plugins/shared/agent_builder/server/services/skills/persisted/client/client.ts
Show resolved
Hide resolved
ppisljar
left a comment
There was a problem hiding this comment.
pre-approving, some of the reviewer bot comments look relevant
...tform/plugins/shared/agent_builder/server/services/plugins/utils/sourcing/download_plugin.ts
Show resolved
Hide resolved
| security: AGENT_BUILDER_READ_SECURITY, | ||
| access: 'public', | ||
| summary: 'List plugins', | ||
| description: 'List all installed plugins.', |
There was a problem hiding this comment.
| description: 'List all installed plugins.', | |
| description: 'List all installed plugins and their managed assets. Plugins are installable packages that bundle agent capabilities such as skills, following the [Claude agent plugin specification](https://code.claude.com/docs/en/plugins).', |
can we do something like this (caveat not sure if formatting is correct here, maybe need to escape the link syntax or something?)
Right now if I land on these docs I have no idea what plugins are:

Once we get around to publishing narrative docs about this, we can add links, but think a bit of context here is helpful.
| security: AGENT_BUILDER_WRITE_SECURITY, | ||
| access: 'public', | ||
| summary: 'Install a plugin', | ||
| description: 'Install a plugin from a GitHub URL or a direct zip URL.', |
There was a problem hiding this comment.
| description: 'Install a plugin from a GitHub URL or a direct zip URL.', | |
| description: 'Install a plugin from a [GitHub Claude plugin URL](https://code.claude.com/docs/en/plugins) or a direct ZIP URL. Plugins bundle agent capabilities such as skills. Installed assets are read-only and can only be removed by deleting the plugin.', |
Again caveat the formatting, but same motivation as first suggestion :)
x-pack/platform/plugins/shared/agent_builder/server/routes/plugins.ts
Outdated
Show resolved
Hide resolved
…gins.ts Co-authored-by: Liam Thompson <leemthompo@gmail.com>
|
@elasticmachine merge upstream |
⏳ Build in-progress, with failures
Failed CI StepsTest Failures
History
|
|
@elasticmachine merge upstream |
…e_fix * commit '565f7545c422192218b803874fbdf93e8d8f08ee': (27 commits) [Lens API] ESQL schema for XY separately for Agent and some small token optimizations (elastic#256885) Fix "Accessing resource attributes before async attributes settled" telemetry error (elastic#256880) [Security Solution][Attacks/Alerts][Attacks page][Table section] Preserver "Sort by" state on Attacks page (elastic#256717) (elastic#256795) [APM] Improve redirect with default date range guard (elastic#256887) [Security Solution][Attacks/Alerts][Attacks page][Table section] Add assignees avatars to the group component (elastic#250126) (elastic#256901) [Docs] add xpack.alerting.rules.maxScheduledPerMinute setting description (elastic#257041) [SO] Fix non-deterministic ordering in nested find API integration tests (elastic#256447) [Write-restricted dashboards] Update user profile retrieval for getShouldAddAccessControl (elastic#255065) [One Workflow] Add Scout API test scaffold and execution tests (elastic#256300) [Fleet] add use_apm if dynamic_signal_types are enabled (elastic#256429) [Fleet] ignore data streams starting with `.` in Fleet API (elastic#256625) [ES|QL] METRICS_INFO support: columns_after & summary (elastic#256758) [Agent Builder] Agent plugins: initial installation support (elastic#256478) [Streams] Add field descriptions and documentation-only field overrides (elastic#255136) [api-docs] 2026-03-11 Daily api_docs build (elastic#257023) [Security Solution] fix alerts page infinite loading state due to data view error (elastic#256983) [Logging] Add `service.*` global fields (elastic#256878) [Canvas] Apply embeddable transforms to embeddable elements (elastic#252191) [table_list_view_table] stabilize jest test (elastic#254991) [Obs AI] get_index_info: add unit tests (elastic#256802) ...
## Summary Fix https://github.com/elastic/search-team/issues/13213 Add initial support for installing [Claude agent skill plugins](https://code.claude.com/docs/en/plugins) (and Agent Builder plugins - which will be a superset of that spec) We currently support installing from three different sources: - GH Claude plugin url - e.g. `https://github.com/anthropics/financial-services-plugins/tree/main/financial-analysis` or `https://github.com/anthropics/financial-services-plugins/blob/main/financial-analysis/.claude-plugin/plugin.json` - Remote zip url - e.g. `https://my-server/my-plugin.zip` - Uploading the zip file directly ### What do we support from the Claude plugin spec? This initial implementation supports the following asset types: - skills (and associated file assets). Support planned in follow ups: - commands - MCP servers (if we can) Support not planned: - agents - hooks - everything else ### Plugins and managed assets Plugin managed assets (so skills only atm) are considered read-only, and can only be deleted by deleting the plugin. They also won't be able to be "directly" associated with an agent - instead, you add the plugin to the agent, which will expose all its assets (note: this is out of scope of the current PR) ### How to test #### Installing a plugin from url Install the plugin: ``` POST kbn://api/agent_builder/plugins/install { "url": "https://github.com/anthropics/financial-services-plugins/tree/main/financial-analysis", } ``` List the plugins: ``` GET kbn://api/agent_builder/plugins ``` Check that the plugin's skills are available: ``` GET kbn://api/agent_builder/skills ``` **Note:** the skills can't be added to an agent atm, it will be done in a follow-up <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit # Release Notes * **New Features** * Added plugin management system with new API endpoints for listing, retrieving, deleting, and installing plugins * Plugins can be installed from GitHub repositories, direct ZIP URLs, or uploaded files * Plugin-managed skills are marked as read-only to ensure integrity * Support for custom GitHub server configuration for plugin sources <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Liam Thompson <leemthompo@gmail.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Summary
Fix https://github.com/elastic/search-team/issues/13213
Add initial support for installing Claude agent skill plugins (and Agent Builder plugins - which will be a superset of that spec)
We currently support installing from three different sources:
https://github.com/anthropics/financial-services-plugins/tree/main/financial-analysisorhttps://github.com/anthropics/financial-services-plugins/blob/main/financial-analysis/.claude-plugin/plugin.jsonhttps://my-server/my-plugin.zipWhat do we support from the Claude plugin spec?
This initial implementation supports the following asset types:
Support planned in follow ups:
Support not planned:
Plugins and managed assets
Plugin managed assets (so skills only atm) are considered read-only, and can only be deleted by deleting the plugin.
They also won't be able to be "directly" associated with an agent - instead, you add the plugin to the agent, which will expose all its assets (note: this is out of scope of the current PR)
How to test
Installing a plugin from url
Install the plugin:
List the plugins:
Check that the plugin's skills are available:
Note: the skills can't be added to an agent atm, it will be done in a follow-up
Summary by CodeRabbit
Release Notes