From 5dae7357a75f74e84ace796f10726acd146e1f15 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Sun, 24 May 2026 03:03:55 -0700 Subject: [PATCH] chore(seed): project first-party apps into Installable collection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends `seed-native-agents.ts` so each FIRST_PARTY_APPS entry also upserts an Installable row alongside its AgentRegistry row. Marketplace detail page (PR #439) reads from this collection — without the projection, no first-party app could be resolved by direct URL. Source set to `builtin`, kind `app`, scope `instance`. Browse continues to filter `source: 'marketplace'` (the marketplace page is for community-published items only by design); detail page reads any source. Idempotent: subsequent boots refresh display metadata via $set without touching stats. Closes #445. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/scripts/seed-native-agents.ts | 54 +++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/backend/scripts/seed-native-agents.ts b/backend/scripts/seed-native-agents.ts index 64843129..3ad342b4 100644 --- a/backend/scripts/seed-native-agents.ts +++ b/backend/scripts/seed-native-agents.ts @@ -23,6 +23,8 @@ import type { NativeAgentDefinition } from '../config/native-agents/apps'; // Lazy requires keep this file import-safe even if dependent services move // and avoid pulling the full Mongoose model graph into typecheck. // eslint-disable-next-line @typescript-eslint/no-require-imports +const Installable = require('../models/Installable'); +// eslint-disable-next-line @typescript-eslint/no-require-imports const AgentIdentityService = require('../services/agentIdentityService'); // eslint-disable-next-line @typescript-eslint/no-require-imports const Pod = require('../models/Pod'); @@ -206,6 +208,58 @@ async function seedOneApp(app: NativeAgentDefinition): Promise { return; } + // 1b. Project an Installable row (ADR-001) alongside the AgentRegistry + // row so first-party apps appear in the unified taxonomy. The v2 + // marketplace detail page (`/v2/marketplace/:installableId`) hits + // `/api/marketplace/manifests/:id` which reads from this collection. + // Browse intentionally filters source=='marketplace' only — a builtin + // app appears via Agent Hub / direct URL, not the marketplace browse. + // Idempotent: a fresh app inserts; subsequent boots refresh display + // metadata via $set without touching stats. + try { + await Installable.findOneAndUpdate( + { installableId: app.agentName.toLowerCase() }, + { + $set: { + name: app.displayName, + description: app.description, + version: VERSION, + kind: 'app', + source: 'builtin', + scope: 'instance', + status: 'active', + components: [ + { + name: app.agentName, + type: 'agent', + version: VERSION, + description: app.description, + runtime: 'native', + persona: { + displayName: app.displayName, + }, + }, + ], + }, + $setOnInsert: { + installableId: app.agentName.toLowerCase(), + stats: { + totalInstalls: 0, + activeInstalls: 0, + forkCount: 0, + }, + }, + }, + { upsert: true, new: true, setDefaultsOnInsert: true }, + ); + } catch (err: unknown) { + console.warn( + `[native-seed] failed to project Installable for ${appLabel}:`, + (err as { message?: string })?.message || err, + ); + // Non-fatal — registry row is in place; marketplace projection is best-effort. + } + // 2. Demo pod must exist; if not, skip the install step gracefully. // The registry row is still in place so the Agent Hub UI can show it. const podObjectId = new mongoose.Types.ObjectId(DEMO_POD_ID);