@app-vitals/agent-admin 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/api.ts CHANGED
@@ -44,6 +44,12 @@ export interface AgentConfigResponse {
44
44
  env: Record<string, string>;
45
45
  allowedTools: string[];
46
46
  plugins: AgentPlugin[];
47
+ /**
48
+ * PMK-1.2: remote plugin marketplaces to register before plugin install.
49
+ * Optional so existing fixtures/doubles built against this interface keep
50
+ * compiling; the live handler always populates it (empty when none).
51
+ */
52
+ marketplaces?: Array<{ name: string; source: string; ref: string | null }>;
47
53
  repos: string[];
48
54
  reviewAuthorAllowlist: string[];
49
55
  patchAuthorAllowlist: string[];
@@ -155,6 +161,12 @@ interface AgentPluginServiceLike {
155
161
  listEnabled(agentId: string): Promise<Array<{ name: string }>>;
156
162
  }
157
163
 
164
+ interface AgentMarketplaceServiceLike {
165
+ list(
166
+ agentId: string,
167
+ ): Promise<Array<{ name: string; source: string; ref: string | null }>>;
168
+ }
169
+
158
170
  export interface AgentPhaseMethodologyServiceLike {
159
171
  list(
160
172
  agentId: string,
@@ -166,6 +178,11 @@ export interface AgentRuntimeDeps {
166
178
  agentCronJobService: AgentCronJobServiceLike;
167
179
  agentService: AgentServiceLike;
168
180
  agentPluginService: AgentPluginServiceLike;
181
+ /**
182
+ * PMK-1.2: optional. Absent → GET /agents/:id/config reports
183
+ * `marketplaces: []`.
184
+ */
185
+ agentMarketplaceService?: AgentMarketplaceServiceLike;
169
186
  /**
170
187
  * Optional: supplied by an AdminExtension (the coding extension). Absent →
171
188
  * GET /agents/:id/config omits `phaseMethodology`.
@@ -290,11 +307,13 @@ export function createAgentRuntimeApp(deps: AgentRuntimeDeps): OpenAPIHono {
290
307
  }
291
308
 
292
309
  // Fetch env bundle, plugins, and phase-methodology overrides in parallel
293
- const [bundle, plugins, phaseMethodologyRows] = await Promise.all([
294
- agentEnvService.getConfigBundle(id),
295
- agentPluginService.listEnabled(id),
296
- agentPhaseMethodologyService?.list(id),
297
- ]);
310
+ const [bundle, plugins, marketplaceRows, phaseMethodologyRows] =
311
+ await Promise.all([
312
+ agentEnvService.getConfigBundle(id),
313
+ agentPluginService.listEnabled(id),
314
+ deps.agentMarketplaceService?.list(id),
315
+ agentPhaseMethodologyService?.list(id),
316
+ ]);
298
317
 
299
318
  // WIF-2.1: with federation enabled, a non-self-hosted agent must never be
300
319
  // handed Anthropic credentials, even ones already stored in its bundle
@@ -341,6 +360,11 @@ export function createAgentRuntimeApp(deps: AgentRuntimeDeps): OpenAPIHono {
341
360
  ? { marketplace: "shipwright", plugin: p.name }
342
361
  : { plugin: p.name.slice(0, at), marketplace: p.name.slice(at + 1) };
343
362
  }),
363
+ marketplaces: (marketplaceRows ?? []).map((m) => ({
364
+ name: m.name,
365
+ source: m.source,
366
+ ref: m.ref,
367
+ })),
344
368
  repos: agent.repos,
345
369
  reviewAuthorAllowlist: agent.reviewAuthorAllowlist,
346
370
  patchAuthorAllowlist: agent.patchAuthorAllowlist,
@@ -895,6 +895,43 @@ export const PatchAgentPluginBodySchema = z
895
895
  })
896
896
  .openapi("PatchAgentPluginBody");
897
897
 
898
+ // ─── AgentMarketplace ─────────────────────────────────────────────────────────
899
+
900
+ export const AgentMarketplaceSchema = z
901
+ .object({
902
+ id: z.string().openapi({ example: "clx1234567890" }),
903
+ agentId: z.string().openapi({ example: "clx1234567890" }),
904
+ name: z.string().openapi({ example: "shipwright" }),
905
+ source: z.string().openapi({ example: "app-vitals/shipwright" }),
906
+ ref: z.string().nullable().openapi({ example: "main" }),
907
+ createdAt: z
908
+ .string()
909
+ .datetime()
910
+ .openapi({ example: "2026-01-01T00:00:00.000Z" }),
911
+ updatedAt: z
912
+ .string()
913
+ .datetime()
914
+ .openapi({ example: "2026-01-01T00:00:00.000Z" }),
915
+ })
916
+ .openapi("AgentMarketplace");
917
+
918
+ export type AgentMarketplace = z.infer<typeof AgentMarketplaceSchema>;
919
+
920
+ /**
921
+ * `source` / `name` / `ref` are validated by AgentMarketplaceService (422), not
922
+ * here — the Zod layer answers 400.
923
+ */
924
+ export const UpsertAgentMarketplaceBodySchema = z
925
+ .object({
926
+ name: z.string().openapi({ example: "shipwright" }),
927
+ source: z.string().openapi({
928
+ description: "GitHub `owner/repo` or an https git URL",
929
+ example: "app-vitals/shipwright",
930
+ }),
931
+ ref: z.string().nullable().optional().openapi({ example: "main" }),
932
+ })
933
+ .openapi("UpsertAgentMarketplaceBody");
934
+
898
935
  // ─── Pipeline phases ────────────────────────────────────────────────────────────
899
936
  //
900
937
  // The six pipeline phases a methodology override can target — the keys of the
@@ -1084,6 +1121,11 @@ export const TokenIdParamSchema = z.object({
1084
1121
  tokenId: z.string().openapi({ example: "clx0987654321" }),
1085
1122
  });
1086
1123
 
1124
+ export const MarketplaceIdParamSchema = z.object({
1125
+ id: z.string().openapi({ example: "clx1234567890" }),
1126
+ marketplaceId: z.string().openapi({ example: "clx0987654321" }),
1127
+ });
1128
+
1087
1129
  export const EnvKeyParamSchema = z.object({
1088
1130
  id: z.string().openapi({ example: "clx1234567890" }),
1089
1131
  key: z.string().openapi({ example: "MY_VAR" }),
@@ -1298,6 +1340,14 @@ const AgentConfigPluginSchema = z
1298
1340
  })
1299
1341
  .openapi("AgentConfigPlugin");
1300
1342
 
1343
+ const AgentConfigMarketplaceSchema = z
1344
+ .object({
1345
+ name: z.string().openapi({ example: "shipwright" }),
1346
+ source: z.string().openapi({ example: "app-vitals/shipwright" }),
1347
+ ref: z.string().nullable().openapi({ example: "main" }),
1348
+ })
1349
+ .openapi("AgentConfigMarketplace");
1350
+
1301
1351
  export const AgentConfigResponseSchema = z
1302
1352
  .object({
1303
1353
  env: z
@@ -1305,6 +1355,12 @@ export const AgentConfigResponseSchema = z
1305
1355
  .openapi({ example: { SLACK_BOT_TOKEN: "xoxb-..." } }),
1306
1356
  allowedTools: z.array(z.string()).openapi({ example: ["Read", "Write"] }),
1307
1357
  plugins: z.array(AgentConfigPluginSchema),
1358
+ /**
1359
+ * PMK-1.2: remote plugin marketplaces to register before plugin install.
1360
+ * Optional so existing fixtures/doubles keep compiling; the handler
1361
+ * always populates it (empty array when the agent has none).
1362
+ */
1363
+ marketplaces: z.array(AgentConfigMarketplaceSchema).optional(),
1308
1364
  repos: z.array(z.string()).openapi({ example: ["org/repo1", "org/repo2"] }),
1309
1365
  reviewAuthorAllowlist: z
1310
1366
  .array(z.string())
@@ -10,6 +10,7 @@ import { type AgentRuntimeDeps, createAgentRuntimeApp } from "./api.ts";
10
10
  import {
11
11
  type AgentCronJob,
12
12
  AgentCronJobSchema,
13
+ AgentMarketplaceSchema,
13
14
  type AgentPlugin,
14
15
  AgentPluginSchema,
15
16
  type AgentToken,
@@ -18,6 +19,7 @@ import {
18
19
  AgentToolSchema,
19
20
  ErrorSchema,
20
21
  PatchAgentBodySchema,
22
+ UpsertAgentMarketplaceBodySchema,
21
23
  } from "./openapi-schemas.ts";
22
24
 
23
25
  // ─── Fixtures ─────────────────────────────────────────────────────────────────
@@ -381,3 +383,30 @@ describe("PatchAgentBodySchema.podServiceAccountName (COS-49.1)", () => {
381
383
  }
382
384
  });
383
385
  });
386
+
387
+ // ─── AgentMarketplace (PMK-1.2) ───────────────────────────────────────────────
388
+
389
+ describe("AgentMarketplace schemas", () => {
390
+ test("AgentMarketplaceSchema accepts a row with a null ref", () => {
391
+ const result = AgentMarketplaceSchema.safeParse({
392
+ id: "m1",
393
+ agentId: "a1",
394
+ name: "shipwright",
395
+ source: "app-vitals/shipwright",
396
+ ref: null,
397
+ createdAt: "2026-01-01T00:00:00.000Z",
398
+ updatedAt: "2026-01-01T00:00:00.000Z",
399
+ });
400
+ expect(result.success).toBe(true);
401
+ });
402
+
403
+ test("UpsertAgentMarketplaceBodySchema leaves source validation to the service (422)", () => {
404
+ expect(
405
+ UpsertAgentMarketplaceBodySchema.safeParse({ name: "m", source: "bad" })
406
+ .success,
407
+ ).toBe(true);
408
+ expect(
409
+ UpsertAgentMarketplaceBodySchema.safeParse({ name: "m" }).success,
410
+ ).toBe(false);
411
+ });
412
+ });