Skip to content

Commit d196266

Browse files
fullsend-ai-coder[bot]gabemonteroclaude
authored
feat(#3311): add dynamic plugin packaging and skills marketplace routes (#3584)
* feat(#3311): add dynamic plugin packaging and skills marketplace routes Configure boost-backend, boost-backend-module-llamastack, and boost-backend-module-kagenti for RHDH dynamic plugin export (OCI) by adding janus-cli export-dynamic scripts and dist-dynamic to published files (tasks 6.1, 6.2). Create dynamic-plugins.yaml with example configuration for deploying boost as a dynamic plugin in RHDH (task 6.3). Add skills marketplace proxy routes (GET /skills, /skills/runtimes, /skills/domains) that forward requests to an external skills catalog backend configured via boost.skillsMarketplace.endpoint (task 5.1). Add POST /skills/deploy for K8s manifest generation with OCI init containers (task 5.3) and GET /skills/deployments/:id for deployment progress polling (task 5.4). Chat routing via chatEndpoint is returned in the deploy response (task 5.6). Enrich Zod schema definitions with detailed descriptions and inline .describe() annotations for all 17 configurable keys (tasks 3.1, 3.2). Add boost.encryptionSecret field documenting the AES-256-GCM encryption secret (task 8.2 documentation). All skills routes are gated by boost.features.skillsMarketplace feature flag and permissions. Tests cover feature gating, permission checks, deployment manifest generation, and input validation (25 tests pass). Closes #3311 * chore(boost): deduplicate yarn.lock Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address review findings in skills routes and config schema - Fix URL resolution in proxyToSkillsCatalog: use URL pathname join instead of new URL(path, base) which discards base path per RFC 3986 - Add try/catch around response.json() for non-JSON upstream responses - Remove unused boost.skillsMarketplace.enabled config key (the actual feature toggle is boost.features.skillsMarketplace) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: update api-report and test for removed config key Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: switch to rhdh-cli, split dynamic-plugins examples, add skills openspec - Replace @janus-idp/cli with @red-hat-developer-hub/cli in all three boost package.json files (boost-backend, module-llamastack, module-kagenti) and update export-dynamic scripts to use rhdh-cli - Split dynamic-plugins.yaml into two reference files: dynamic-plugins-filesystem-reference.yaml (local dev paths) and dynamic-plugins-image-reference.yaml (OCI registry paths) - Add skills marketplace config examples (commented out) to both files - Add section 8 (Skills Marketplace Integration) to boost openspec tasks.md with design decisions from grill-me review session - Deduplicate yarn.lock after dependency changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address round-3 review findings and add staged issue 16 - Remove stale boost.skillsMarketplace.enabled from config.d.ts and runtime-config spec.md (consolidated into boost.features.skillsMarketplace) - Use InputError instead of res.status(400) in skills deploy route - Add InputError mapping to test error handler - Fix task ID references in plugin.ts comment (section 8, not 5.x) - Add issue 16 (skills route improvements) to staged-issues.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: fullsend-code <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Co-authored-by: gabemontero <gmontero@redhat.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4866641 commit d196266

18 files changed

Lines changed: 5968 additions & 530 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Example dynamic-plugins.yaml for local/development Boost deployment in RHDH
2+
#
3+
# This file shows how to configure Boost as a dynamic plugin in Red Hat
4+
# Developer Hub using local filesystem paths. Use this for development
5+
# and testing. For production OCI deployments, see
6+
# dynamic-plugins-image-reference.yaml.
7+
#
8+
# Each plugin is loaded from a local path under dynamic-plugins/dist/.
9+
# The package names follow the RHDH dynamic plugin naming convention:
10+
# red-hat-developer-hub-backstage-plugin-<name>-dynamic
11+
#
12+
# For more information on RHDH dynamic plugins, see:
13+
# https://docs.redhat.com/en/documentation/red_hat_developer_hub
14+
15+
plugins:
16+
# -----------------------------------------------------------------------
17+
# Core backend plugin — required
18+
# -----------------------------------------------------------------------
19+
- package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-boost-backend-dynamic
20+
disabled: false
21+
pluginConfig:
22+
boost:
23+
# Security mode: 'full' for production, 'development-only-no-auth' for local dev
24+
security:
25+
mode: full
26+
model:
27+
baseUrl: ${BOOST_MODEL_BASE_URL}
28+
name: ${BOOST_MODEL_NAME}
29+
# Skills marketplace (optional):
30+
# features:
31+
# skillsMarketplace: true
32+
# skillsMarketplace:
33+
# endpoint: http://skills-catalog.example.com
34+
35+
# -----------------------------------------------------------------------
36+
# Provider modules — install at least one
37+
# -----------------------------------------------------------------------
38+
39+
# Llama Stack provider: connects to a Llama Stack Responses API endpoint
40+
- package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-boost-backend-module-llamastack-dynamic
41+
disabled: false
42+
43+
# Kagenti provider: connects to a Kagenti multi-agent orchestrator
44+
- package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-boost-backend-module-kagenti-dynamic
45+
disabled: true
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Example dynamic-plugins.yaml for production Boost deployment in RHDH
2+
#
3+
# This file shows how to configure Boost as a dynamic plugin in Red Hat
4+
# Developer Hub using OCI container images. Use this for production
5+
# deployments. For local development, see
6+
# dynamic-plugins-filesystem-reference.yaml.
7+
#
8+
# Each plugin is loaded from an OCI artifact in a container registry.
9+
# Replace <registry> and <tag> with your actual registry and version.
10+
#
11+
# For more information on RHDH dynamic plugins, see:
12+
# https://docs.redhat.com/en/documentation/red_hat_developer_hub
13+
14+
plugins:
15+
# -----------------------------------------------------------------------
16+
# Core backend plugin — required
17+
# -----------------------------------------------------------------------
18+
- package: oci://<registry>/backstage-plugin-boost-backend:<tag>!red-hat-developer-hub-backstage-plugin-boost-backend-dynamic
19+
disabled: false
20+
pluginConfig:
21+
boost:
22+
# Security mode: 'full' for production, 'development-only-no-auth' for local dev
23+
security:
24+
mode: full
25+
model:
26+
baseUrl: ${BOOST_MODEL_BASE_URL}
27+
name: ${BOOST_MODEL_NAME}
28+
# Skills marketplace (optional):
29+
# features:
30+
# skillsMarketplace: true
31+
# skillsMarketplace:
32+
# endpoint: http://skills-catalog.example.com
33+
34+
# -----------------------------------------------------------------------
35+
# Provider modules — install at least one
36+
# -----------------------------------------------------------------------
37+
38+
# Llama Stack provider: connects to a Llama Stack Responses API endpoint
39+
- package: oci://<registry>/backstage-plugin-boost-backend-module-llamastack:<tag>!red-hat-developer-hub-backstage-plugin-boost-backend-module-llamastack-dynamic
40+
disabled: false
41+
42+
# Kagenti provider: connects to a Kagenti multi-agent orchestrator
43+
- package: oci://<registry>/backstage-plugin-boost-backend-module-kagenti:<tag>!red-hat-developer-hub-backstage-plugin-boost-backend-module-kagenti-dynamic
44+
disabled: true

workspaces/boost/openspec/changes/platform-operations-deployment/specs/runtime-config/spec.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ The following new features MUST have runtime configuration fields as specified b
100100
| Field | Scope | Description |
101101
|---|---|---|
102102
| `boost.skillsMarketplace.endpoint` | yaml-only | Skills catalog backend URL |
103-
| `boost.skillsMarketplace.enabled` | db-overridable | Enable/disable skills marketplace |
104103

105104
#### Scenario: Token exchange configuration
106105

workspaces/boost/openspec/changes/pluggable-ai-platform-architecture/tasks.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,48 @@
6161
- [ ] 7.2 Verify hot-swap works with modular provider packages
6262
- [ ] 7.3 Verify cache behavior in both in-memory and Redis-backed modes
6363
- [ ] 7.4 Verify no provider ID string checks in frontend — all capability-based
64+
65+
## 8. Skills Marketplace Integration (P1)
66+
67+
### Design decisions (from grill-me review):
68+
69+
- Runtimes are **local app-config**, not proxied from the external catalog.
70+
`boost.skillsMarketplace.runtimes[]` defines which agent execution frameworks
71+
this RHDH instance supports (e.g. DocsClaw, ZeroClaw). Each entry has `id`,
72+
`name`, `description`, `image`, `language`, `footprint`, `features[]`, `status`.
73+
- Deploy endpoint accepts `runtimeId` in the request body; the backend resolves
74+
the container image from config server-side. Frontend never sees registry URLs.
75+
- Manifest generation is extracted into `src/skills/manifestBuilder.ts` for
76+
independent testability and future extension (Service, ConfigMap, Secret).
77+
- Reference implementation: augment workspace `agent-creation-discovery` section 5
78+
and UC-23 flow diagram. Boost is a clean room re-implementation — endpoint paths
79+
and internal structure may diverge from augment.
80+
81+
### 8a. Skills catalog proxy routes
82+
83+
- [x] 8a.1 Implement `GET /skills` proxy to external skills catalog backend
84+
- [x] 8a.2 Implement `GET /skills/domains` proxy to external skills catalog backend
85+
- [ ] 8a.3 Add proxy tests for `GET /skills` and `GET /skills/domains` (mock fetch, verify URL construction, query param forwarding, feature gate, permission checks, non-JSON handling)
86+
87+
### 8b. Skills runtimes from config
88+
89+
- [ ] 8b.1 Add `boost.skillsMarketplace.runtimes[]` Zod schema to `schemas.ts` (`yaml-only` scope) with fields: `id`, `name`, `description`, `image`, `language`, `footprint`, `features[]`, `status`
90+
- [ ] 8b.2 Refactor `GET /skills/runtimes` to read from local app-config instead of proxying to external catalog
91+
- [ ] 8b.3 Add tests for `GET /skills/runtimes` (reads config, returns runtime list, feature gate)
92+
93+
### 8c. Deployment with runtime resolution
94+
95+
- [ ] 8c.1 Change `POST /skills/deploy` request body to accept `runtimeId` instead of `ociImage`; resolve container image from `boost.skillsMarketplace.runtimes[]` config
96+
- [ ] 8c.2 Extract manifest generation into `src/skills/manifestBuilder.ts`
97+
- [ ] 8c.3 Update deploy tests for `runtimeId` resolution and `manifestBuilder` unit tests
98+
99+
### 8d. Skills browse UI
100+
101+
- [ ] 8d.1 Implement skills browse UI with runtime and domain filters (consuming proxied data)
102+
- [ ] 8d.2 Add skill badge to gallery for framework agents
103+
- [ ] 8d.3 Route chat to skill agents via `chatEndpoint` field
104+
105+
### 8e. Deployment progress
106+
107+
- [ ] 8e.1 Implement deployment status persistence (replace current stub)
108+
- [ ] 8e.2 Add deployment progress polling and status display in UI

workspaces/boost/plugins/boost-backend-module-kagenti/package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"@red-hat-developer-hub/backstage-plugin-boost-node": "workspace:^"
3737
},
3838
"devDependencies": {
39-
"@backstage/cli": "^0.34.5"
39+
"@backstage/cli": "^0.34.5",
40+
"@red-hat-developer-hub/cli": "^1.11.1"
4041
},
4142
"sideEffects": false,
4243
"scripts": {
@@ -49,10 +50,13 @@
4950
"tsc": "tsc",
5051
"start": "backstage-cli package start",
5152
"prepack": "backstage-cli package prepack",
52-
"postpack": "backstage-cli package postpack"
53+
"postpack": "backstage-cli package postpack",
54+
"export-dynamic": "rhdh-cli plugin export"
5355
},
5456
"files": [
55-
"dist"
57+
"dist",
58+
"dist-dynamic/*.*",
59+
"dist-dynamic/dist/**"
5660
],
5761
"repository": {
5862
"type": "git",

workspaces/boost/plugins/boost-backend-module-llamastack/package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"@red-hat-developer-hub/backstage-plugin-boost-responses-api-toolkit": "workspace:^"
3838
},
3939
"devDependencies": {
40-
"@backstage/cli": "^0.34.5"
40+
"@backstage/cli": "^0.34.5",
41+
"@red-hat-developer-hub/cli": "^1.11.1"
4142
},
4243
"sideEffects": false,
4344
"scripts": {
@@ -50,10 +51,13 @@
5051
"tsc": "tsc",
5152
"start": "backstage-cli package start",
5253
"prepack": "backstage-cli package prepack",
53-
"postpack": "backstage-cli package postpack"
54+
"postpack": "backstage-cli package postpack",
55+
"export-dynamic": "rhdh-cli plugin export"
5456
},
5557
"files": [
56-
"dist"
58+
"dist",
59+
"dist-dynamic/*.*",
60+
"dist-dynamic/dist/**"
5761
],
5862
"repository": {
5963
"type": "git",

workspaces/boost/plugins/boost-backend/config.d.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,6 @@ export interface Config {
9393
* @configScope yaml-only
9494
*/
9595
endpoint?: string;
96-
/**
97-
* Enable or disable skills marketplace.
98-
* @visibility frontend
99-
* @configScope db-overridable
100-
*/
101-
enabled?: boolean;
10296
};
10397

10498
/** Kagenti provider configuration. */

workspaces/boost/plugins/boost-backend/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
},
4545
"devDependencies": {
4646
"@backstage/cli": "^0.34.5",
47+
"@red-hat-developer-hub/cli": "^1.11.1",
4748
"@types/express": "4.17.25"
4849
},
4950
"sideEffects": false,
@@ -57,10 +58,13 @@
5758
"tsc": "tsc",
5859
"start": "backstage-cli package start",
5960
"prepack": "backstage-cli package prepack",
60-
"postpack": "backstage-cli package postpack"
61+
"postpack": "backstage-cli package postpack",
62+
"export-dynamic": "rhdh-cli plugin export"
6163
},
6264
"files": [
63-
"dist"
65+
"dist",
66+
"dist-dynamic/*.*",
67+
"dist-dynamic/dist/**"
6468
],
6569
"repository": {
6670
"type": "git",

workspaces/boost/plugins/boost-backend/report.api.md

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -126,74 +126,75 @@ export const boostConfigFields: {
126126
readonly 'boost.model.baseUrl': {
127127
readonly schema: z.ZodString;
128128
readonly configScope: ConfigScope;
129-
readonly description: 'Base URL for the AI model endpoint';
129+
readonly description: string;
130130
};
131131
readonly 'boost.model.name': {
132132
readonly schema: z.ZodString;
133133
readonly configScope: ConfigScope;
134-
readonly description: 'Name of the AI model to use';
134+
readonly description: string;
135135
};
136136
readonly 'boost.systemPrompt': {
137137
readonly schema: z.ZodOptional<z.ZodString>;
138138
readonly configScope: ConfigScope;
139-
readonly description: 'System prompt for AI conversations';
139+
readonly description: string;
140140
};
141141
readonly 'boost.security.mode': {
142142
readonly schema: z.ZodEnum<
143143
['development-only-no-auth', 'plugin-only', 'full']
144144
>;
145145
readonly configScope: ConfigScope;
146-
readonly description: 'Security mode for the boost plugin';
146+
readonly description: string;
147147
};
148148
readonly 'boost.features.agentCreation': {
149149
readonly schema: z.ZodOptional<z.ZodBoolean>;
150150
readonly configScope: ConfigScope;
151-
readonly description: 'Enable agent creation feature';
151+
readonly description: string;
152152
};
153153
readonly 'boost.features.skillsMarketplace': {
154154
readonly schema: z.ZodOptional<z.ZodBoolean>;
155155
readonly configScope: ConfigScope;
156-
readonly description: 'Enable skills marketplace feature';
156+
readonly description: string;
157157
};
158158
readonly 'boost.agentApproval.mode': {
159159
readonly schema: z.ZodOptional<z.ZodEnum<['built-in', 'sonataflow']>>;
160160
readonly configScope: ConfigScope;
161-
readonly description: 'Agent approval mode: built-in or SonataFlow-managed';
161+
readonly description: string;
162162
};
163163
readonly 'boost.agentApproval.sonataflow.endpoint': {
164164
readonly schema: z.ZodOptional<z.ZodString>;
165165
readonly configScope: ConfigScope;
166-
readonly description: 'SonataFlow workflow endpoint for agent approval';
166+
readonly description: string;
167167
};
168168
readonly 'boost.skillsMarketplace.endpoint': {
169169
readonly schema: z.ZodOptional<z.ZodString>;
170170
readonly configScope: ConfigScope;
171-
readonly description: 'Skills catalog backend URL';
172-
};
173-
readonly 'boost.skillsMarketplace.enabled': {
174-
readonly schema: z.ZodOptional<z.ZodBoolean>;
175-
readonly configScope: ConfigScope;
176-
readonly description: 'Enable or disable skills marketplace';
171+
readonly description: string;
177172
};
178173
readonly 'boost.kagenti.auth.tokenExchange.enabled': {
179174
readonly schema: z.ZodOptional<z.ZodBoolean>;
180175
readonly configScope: ConfigScope;
181-
readonly description: 'Enable RFC 8693 token exchange for Kagenti';
176+
readonly description: string;
182177
};
183178
readonly 'boost.kagenti.auth.tokenExchange.audience': {
184179
readonly schema: z.ZodOptional<z.ZodString>;
185180
readonly configScope: ConfigScope;
186-
readonly description: 'Target audience for exchanged token';
181+
readonly description: string;
187182
};
188183
readonly 'boost.kagenti.auth.tokenExchange.userTokenHeader': {
189184
readonly schema: z.ZodOptional<z.ZodString>;
190185
readonly configScope: ConfigScope;
191-
readonly description: 'Header containing user OIDC token';
186+
readonly description: string;
187+
};
188+
readonly 'boost.encryptionSecret': {
189+
readonly schema: z.ZodOptional<z.ZodString>;
190+
readonly configScope: ConfigScope;
191+
readonly description: string;
192+
readonly sensitive: true;
192193
};
193194
readonly 'boost.devSpaces.credentials': {
194195
readonly schema: z.ZodOptional<z.ZodString>;
195196
readonly configScope: ConfigScope;
196-
readonly description: 'DevSpaces integration credentials';
197+
readonly description: string;
197198
readonly sensitive: true;
198199
};
199200
};
@@ -328,6 +329,9 @@ export function createKagentiAdminRoutes(
328329
// @public
329330
export function createMcpServerRoutes(options: McpServerRoutesOptions): Router;
330331

332+
// @public
333+
export function createSkillsRoutes(options: SkillsRoutesOptions): Router;
334+
331335
// @public
332336
export function createToolResourceLoader(): ResourceLoader;
333337

@@ -473,6 +477,14 @@ export interface RuntimeConfigResolverOptions {
473477
// @public
474478
export type SecurityMode = 'development-only-no-auth' | 'plugin-only' | 'full';
475479

480+
// @public
481+
export interface SkillsRoutesOptions {
482+
config: RootConfigService;
483+
httpAuth: HttpAuthService;
484+
logger: LoggerService;
485+
permissions: PermissionsService;
486+
}
487+
476488
// @public
477489
export class ToolLifecycleStore {
478490
constructor(options: ToolLifecycleStoreOptions);

workspaces/boost/plugins/boost-backend/src/config/schemas.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ describe('boostConfigFields', () => {
3636
expect(keys).toContain('boost.security.mode');
3737
expect(keys).toContain('boost.features.agentCreation');
3838
expect(keys).toContain('boost.agentApproval.mode');
39-
expect(keys).toContain('boost.skillsMarketplace.enabled');
4039
expect(keys).toContain('boost.kagenti.auth.tokenExchange.enabled');
40+
expect(keys).toContain('boost.encryptionSecret');
4141
expect(keys).toContain('boost.devSpaces.credentials');
4242
});
4343

@@ -121,11 +121,13 @@ describe('isDbWritable', () => {
121121
expect(isDbWritable('boost.kagenti.auth.tokenExchange.enabled')).toBe(
122122
false,
123123
);
124+
expect(isDbWritable('boost.encryptionSecret')).toBe(false);
124125
});
125126
});
126127

127128
describe('isSensitiveField', () => {
128129
it('returns true for sensitive fields', () => {
130+
expect(isSensitiveField('boost.encryptionSecret')).toBe(true);
129131
expect(isSensitiveField('boost.devSpaces.credentials')).toBe(true);
130132
});
131133

0 commit comments

Comments
 (0)