Skip to content

Add template marketplace with EN/ZH agent libraries - #7

Merged
tt-a1i merged 8 commits into
mainfrom
feat/template-marketplace
May 22, 2026
Merged

Add template marketplace with EN/ZH agent libraries#7
tt-a1i merged 8 commits into
mainfrom
feat/template-marketplace

Conversation

@tt-a1i

@tt-a1i tt-a1i commented May 22, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a "template marketplace" drawer reachable from the Add Worker dialog. Users browse pre-built role prompts by category, preview the markdown body, and import a chosen agent into the worker form with one click. Snapshots of two MIT-licensed community libraries ride along in vendor/marketplace/ so the runtime works offline.

UI language switches the source repo automatically. Imported agents land in the existing role_templates table as customno DB schema change, no new auth surface. The marketplace is read-only; routes wire through routes.ts like any other endpoint.

What's in the diff

  • scripts/sync-marketplace.mjs — single-tarball fetch via gh, atomic swap. Run pnpm sync:marketplace before each release. Note: vendor/marketplace/ accounts for ~5.8 MB / ~400 markdown files in this PR — feel free to skip that path while reviewing.
  • Backendsrc/server/marketplace-store.ts (vendor path invariant + HIVE_MARKETPLACE_VENDOR_ROOT test override + isPathWithinRoot guard), src/server/routes-marketplace.ts (GET /api/marketplace/{manifest,agent})
  • Frontendweb/src/marketplace/ drawer + category tree + card grid + preview, useMarketplace hook (manifest + agent body cache, ~2MB session ceiling)
  • AddWorkerDialog integrationuseWorkerComposer.applyMarketplaceImport batches form-state writes to avoid the workerRole-change useEffect clobbering the imported description
  • README + README.en — attribution paragraph for both upstream repos

Tests

  • 19 new tests: 12 server (routes-marketplace), 5 drawer, 2 wire-up integration (add-worker-marketplace-import)
  • Full suite: 789/789 green, biome clean
  • TS errors visible in web/src/worker/AddWorkerDialog.tsx:181,189 are pre-existing exactOptionalPropertyTypes issues unrelated to this PR

Test plan

  • pnpm install (gray-matter is a new dep)
  • pnpm vitest run — should be 789/789
  • pnpm dev — open Add Worker dialog, click "Browse marketplace", verify drawer opens with 184 EN agents
  • Switch UI language to 中文, reopen drawer — should swap to 216 ZH agents from agency-agents-zh
  • Click any agent → preview shows markdown body → click "Import as template" → form populated with name + description, role = Custom → Save → worker created
  • pnpm build to verify prepare-build-artifacts.mjs copies vendor/marketplace/ into dist/vendor/

Out of scope (intentional)

  • No DB schema change, no new auth surface
  • No heuristic role-type mapping — imports always land as custom
  • No emoji/color/vibe persisted to DB — no UI surfaces for them
  • No GitHub Action for sync — manual pnpm sync:marketplace only

tt-a1i added 8 commits May 22, 2026 08:32
Adds a "template marketplace" drawer reachable from the Add Worker dialog.
Users browse pre-built role prompts by category, preview the markdown body,
and import a chosen agent into the worker form with one click. The two
upstream prompt libraries are vendored into vendor/marketplace/{en,zh}/ so
the runtime works offline; pnpm sync:marketplace refreshes the snapshot.

Bundles snapshots of two MIT-licensed community libraries:
- en: msitarzewski/agency-agents (184 agents)
- zh: jnMetaCode/agency-agents-zh (216 agents)

Source-language switches with the UI language. Imported agents land in
the user's role_templates table as 'custom'; no DB schema change, no new
auth surface — read-only routes wired through routes.ts.

Key pieces:
- scripts/sync-marketplace.mjs: single-tarball fetch via gh CLI, atomic swap
- src/server/marketplace-store.ts: env-overridable vendor root for tests,
  isPathWithinRoot guard against traversal
- src/server/routes-marketplace.ts: GET /api/marketplace/{manifest,agent}
- web/src/marketplace/: drawer, category tree, card grid, preview pane
- web/src/worker/useWorkerComposer.ts: applyMarketplaceImport batches
  form-state writes to avoid the workerRole-change useEffect clobbering
  the imported description
- README + README.en: attribution to both upstream repos

Tests: 19 new (12 server route, 5 drawer, 2 wire-up integration); 789/789
full suite green, biome clean.
Five polish items on top of the marketplace MVP:

- Localized category names via categoryLabels.ts (engineering → 工程, etc).
  Bilingual dictionary lives outside the i18n catalog so an unknown upstream
  category falls back to a prettified raw label instead of throwing.
- AgentCard now uses the upstream `color` field as a 4px left rail, lifts the
  emoji to text-lg, and bolds the name. Already-imported agents (matching by
  name against the local customTemplates) show a ✓ pill.
- AgentPreview swaps the raw <pre> body for marked + isomorphic-dompurify
  through .marketplace-prose CSS — proper headings, lists, code, links —
  while keeping the same import-flow disabled state on the button.
- Toast feedback after import (`已把 X 填入表单` / `Imported X into the form`)
  so users don't have to scroll the form to verify the fill landed.
- AddWorkerDialog wraps the marketplace onImport handler with the toast call
  and passes a memoized Set of customTemplate names to the drawer for the
  ✓ detection.

Tests: 2 new (one for imported badge, one for toast trigger). Full suite
791/791 green, biome clean. No backend / route / DB changes.
Five rounds of UX feedback from three reviewing subagents collapsed into
the four highest-ROI fixes:

- Card surface dropped from --bg-elevated to --bg-2 so cards visibly sit
  on the drawer "table" instead of blending into it. Selected cards now
  use color-mix(accent 14%) + accent border. Removed the per-card 4px
  color rail — at 200+ cards it looked like a sticker book and the data
  was already in the emoji + name.
- Default category view filters to seven code-relevant categories
  (engineering, design, product, testing, project-management,
  specialized, integrations). Off-topic categories (marketing,
  game-development, academic, etc.) collapse behind a "Show all
  categories (+N)" toggle in the category tree footer.
- Three category labels rewritten for clarity:
    specialized → Industry Verticals / 行业角色
    paid-media  → Paid Ads          / 广告投放
    integrations → System Integrations / 系统集成
- sync-marketplace.mjs now extracts the upstream `vibe` frontmatter field
  (present on 182/184 EN agents, 0/216 ZH). AgentCard surfaces vibe in
  place of description when available — much more flavorful at a glance.
  Type added to MarketplaceAgentEntry in both web/src/api.ts and
  src/server/marketplace-store.ts.

Plus the round-1 fix already in the branch: drawer width 1100→1280,
preview pane collapses when nothing is selected, card grid switched to
auto-fill minmax(220px, 1fr).

Tests: 1 new (toggle reveals + folds non-core). Full suite 792/792
green, biome clean.
Three reviewing subagents agreed the biggest remaining gaps were
EN/ZH card asymmetry (EN had snappy taglines, ZH read like a corporate
directory), card density still wasting vertical space, two latent
selection-stale bugs, and a few ergonomic gaps. Eight fixes:

- sync-marketplace.mjs grew an extractVibe() pass:
    • EN: still uses fm.vibe, but rejects entries > 140 chars (they
      were getting line-clamped to noise anyway).
    • ZH: regex-extracts the **个性**: ... line from the markdown body
      where upstream provides it. ZH vibe coverage jumped 0 → 183/216
      (84%), closing the EN/ZH asymmetry without any per-agent curation.
    • EN vibe net: 170 (180+ minus the >140 outliers).
- AgentCard tagline switched from line-clamp-2 to line-clamp-1; card
  height drops, ~50% more cards visible per scroll.
- AgentCard gained a focus-visible accent ring so keyboard nav has a
  visible target. Tab-through is still long; arrow-key roving tabindex
  deferred.
- Imported ✓ pill flips to solid var(--accent) + white when the card is
  also selected — fixes the case where pill bg (accent 18%) dissolved
  into the selected-card bg (accent 14%).
- Drawer width changed from fixed w-[1280px] to min(1280px, calc(100vw
  - 32px)) so small laptops don't overflow.
- Sidebar widened 180→200px; "System Integrations" was clipping on EN.
- Show-all toggle now sits below a border-t divider with text-sec rest,
  reads as a real action instead of filler.
- Two real bugs fixed in MarketplaceDrawer:
    • Switching UI language mid-session left selectedAgent / selectedCategory
      pointing at the old-language manifest. Preview would silently 404.
      Added a useEffect on [language] that resets both.
    • Collapsing "show all" back to core view left selectedAgent visible in
      the preview pane even when its category had just disappeared. Now
      cleared along with selectedCategory.
- ZH category sort: upstream emits categories alphabetically by EN
  kebab key, which renders as random order to ZH readers. Added a
  static ZH_CATEGORY_ORDER (工程→测试→产品→设计... → 学术→游戏
  开发→空间计算→行业角色) applied to ZH only; EN stays alphabetical.

Tests: 1 new (collapsing all-categories clears stale selection). Full
suite 793/793 green, biome clean. No backend, schema, or i18n key
removal.
Three subagents flagged that bg-3 and bg-elevated are both #222, so every
`hover:bg-3` on the drawer container was a silent no-op — categories,
toggle, browse button, and cards all had invisible hover/active feedback.
Eleven micro-fixes:

- Category sidebar rewritten — rows now use explicit accent-mix wash for
  rest/hover/selected with a 2px accent inset on selected. Counts and
  tagline bumped from text-tertiary to text-secondary so they read as
  data, not chrome. Show-all toggle wrapped in a divider div instead of
  the broken mt-2+borderRadius:0 inline cocktail.
- Card hover/active styles added via .marketplace-card CSS so cards
  actually look clickable. Selected cards skip the hover wash since they
  already carry an accent border.
- AddWorkerDialog "Browse marketplace" button — explicit bg-0 + bright
  border, accent-mix hover, focus-visible ring.
- 3 overflow-y-auto regions (sidebar / grid / preview body) swapped for
  the project's themed `.scroll-y` class so they pick up the thin dark
  scrollbar instead of OS chrome.
- Grid section now re-keyed by selectedCategory; switching category
  resets scroll to top instead of leaving content jumping under cursor.
- Preview body re-keyed by agent.path with the same effect.
- Preview inner border dropped — outer border-l + inner rounded panel
  was double-bordered; now just bg-2 panel.
- Import button disabled state went from opacity-50 (looked like hover)
  to neutral bg-3 + text-tertiary surface.
- "View source" link styled as secondary text with ExternalLink icon
  instead of tertiary underlined (was reading as a dead link).
- Imported pill unselected: bg accent 18%→28%, text accent 60%-white
  blend — bumps contrast from 2.4:1 (below AA) into readable range.
- Padding unified: header was px-5 while body sections were px-3, an
  8px notch. Everything is px-4 now.
- Loading / error / empty states wrapped in flex h-full center so they
  occupy the grid pane instead of sitting top-left as a lonely "…".
  New i18n key marketplace.loading (Loading… / 加载中…).

Tests: 793/793 green, biome clean. No backend or schema changes.
Three subagents on typography, microcopy, and edge-case bugs flagged 15
finds. Land all of them.

Real bugs:
- Search query now resets when switching category or UI language.
  Previously a "redis" filter would silently empty the grid after
  changing context with no clue to the user.
- Preview h3 long agent names get `break-words + min-w-0`. Long
  upstream titles no longer push the preview column wider than 380px.
- `readAgent` adds a `statSync().isFile()` guard before reading. If
  upstream ever pushes a directory whose name ends in `.md`, the route
  returns 404 instead of crashing with EISDIR.
- `marked.parse(body, { async: false })` is now called per-site with
  an explicit narrow type check. If a future marked upgrade flips the
  default to async, we fail loudly instead of rendering `[object
  Promise]` through DOMPurify.

Copy:
- Import button: "Import as template" → "Use this agent" / "导入为模板"
  → "使用此角色". No template is actually persisted on click, so the
  old verb was lying about the action.
- Toast: "Imported {name} into the form" → "Filled the form from
  {name}". ZH was already correct.
- "View source" → "View on GitHub" (matches the existing ZH wording).
- "Source: {repo}" → "Curated from {repo}" / "由 {repo} 提供".
  Reframes a raw GitHub slug as attribution instead of metadata noise.
- Show-all toggle: "Show all categories (+11)" → "Show 11 more
  categories" / "显示其他 11 个分类". The (+N) pattern read as math.
- ZH "只看常用分类" → "只看核心分类". "常用" implies user history;
  the actual filter is a curator-picked subset.
- ✓ pill gets a proper aria-label + tooltip via a new
  `marketplace.importedBadge` key ("You have a saved template with
  this name" / "本地已有同名模板"). Doesn't fix the false-positive
  detection but at least the user knows what the badge means.

Typography:
- Drawer title `text-base` → `text-lg`. Restores the 16/13/12/11
  ladder from modal → preview → card → tagline.
- Source repo slug rendered in DM Mono via a `<span className="mono">`
  inside `Dialog.Description`. `owner/repo` is a path, not prose;
  the slash + descenders read cleanly in mono.
- ✓ pill `tracking-wide` → `tracking-wider` for 10px uppercase
  legibility.
- Card tagline `text-[11px]` → `text-xs` (12px) to match preview body
  font-size and ditch the inline literal.
- Selected category row gets `font-medium` on top of the existing
  color swap, mirroring the workspace-row anchor pattern.

Tests: 793/793 green, biome clean. No backend, schema, or test
breakage.
Three subagents on dialog-family consistency, first-time-user
friction, and vendor content quality. Eleven fixes:

Family consistency (marketplace now reuses hive's design tokens
instead of one-off styles):
- Import button: bespoke inline-style block → `.icon-btn--primary`,
  the same primary CTA shape used in every other dialog footer.
- Search input: custom wrapper div → `.input` class with the Search
  icon overlaid via padding. Border / radius / placeholder weight
  now match the rest of the form-input family.
- Close X: from bordered 8x8 filled button to ghost-style 7x7
  matching `ServerBrowseDialog`'s top-right close.
- Header padding: `px-4 py-3` → `px-5 py-4` to match every other
  dialog header in the family.

Copy + iconography:
- Import button copy: "Use this agent" → "Fill into form" /
  "使用此角色" → "填入表单". "Use" implied install/commit when the
  actual effect is form pre-fill.
- "Imported" pill icon: `Check` → `Bookmark`. A green check on a
  catalog card universally means "you've installed/used this", but
  the badge actually means "you have a local template with the same
  name". Different semantics, different icon.

Sync-script quality (`scripts/sync-marketplace.mjs`):
- `extractVibe` gained a description-lead fallback. When neither
  the vibe frontmatter nor the ZH **个性** body line is present,
  snip the first clause from `description` (split on
  em-dash / Chinese semicolons / "—精通/专注/covering/specializing"
  style joiners). EN vibe coverage 170→179; ZH 183→215 (215/216 =
  near-complete).
- `looksLocalized` filter drops one half-translated entry
  (`integrations/mcp-memory/backend-architect-with-memory.md` in ZH
  ships English name + no emoji). ZH agent count 216→215.
- `nameOverflows` flag on every manifest entry (CJK-aware visual-
  length, > 22 cells). UI uses it to set `title=` on the truncated
  name span so users can hover for the full name. EN 71 / ZH 4
  agents marked.
- Cross-category duplicate names get a `displayName: "X
  (category)"` so e.g. "Backend Architect" in `engineering/`
  doesn't visually collide with "Backend Architect" in
  `integrations/mcp-memory/`. Two collisions per language.

`MarketplaceAgentEntry` extended with `displayName?: string` and
`nameOverflows?: boolean` in both `web/src/api.ts` and
`src/server/marketplace-store.ts`. Card uses `displayName ?? name`
and sets `title` when `nameOverflows`.

Tests: 793/793 green, biome clean (1 false-positive control-char
warning on the visualLength regex rewritten to use codePointAt).
`.input` ships a `padding: 8px 12px` shorthand in unlayered CSS that
out-cascades a Tailwind `pl-9` utility in v4 (unlayered wins over
@layer utilities). Move the left-padding override to inline style so
the placeholder lands at 36px and clears the absolute-positioned
Search icon at 12px.
@tt-a1i
tt-a1i merged commit 091fc8c into main May 22, 2026
3 of 4 checks passed
huangserva added a commit to huangserva/hive that referenced this pull request May 26, 2026
huangserva added a commit to huangserva/hive that referenced this pull request May 26, 2026
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@tt-a1i
tt-a1i deleted the feat/template-marketplace branch May 28, 2026 05:05
huangserva added a commit to huangserva/hive that referenced this pull request May 29, 2026
…-a1i#6 tt-a1i#7)

Bugs surfaced by an adversarial bug-finding workflow on the archived
repo, then triaged against current hive-serva (all confirmed still
present). Each fix ships with anti-fake-verified tests.

- tt-a1i#1 agent-run-sync: reuse the first endedAt on terminal runs so
  post-exit syncPersistedRun stops pushing the DB endedAt forward every
  poll.
- tt-a1i#7 agent-runtime: mark a run stopRequested before SIGTERM and exclude
  such runs from the active-run check, so stop-then-start spawns a fresh
  run instead of returning the dying one.
- tt-a1i#4 terminal-stream-hub: only close/null viewer.flowState when the
  closing socket is the current io socket; a late close from an old
  socket no longer kills the new connection's active flow.
- tt-a1i#5 terminal-flow-control: count bytes sent while paused so unacked
  accounting cannot drift below the real in-flight amount.
- tt-a1i#6 session-capture: thread the identity discriminator into the
  codex/gemini capture path (parity with claude) so concurrent
  same-workspace agents stop grabbing each other's session id.

Bugs tt-a1i#2 tt-a1i#3 tt-a1i#8 live in files (team-operations.ts, sqlite-schema.ts) that
are entangled with uncommitted M24 server work and are deferred until
that lands.

Report: .hive/reports/archived-bug-triage-vs-current-2026-05-29.html

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
huangserva added a commit to huangserva/hive that referenced this pull request Jun 17, 2026
…t-a1i#7 重启全丢)

bug 普查(andy 工作流)+钟馗多轮复核的两高危(同根=approval 状态非持久):tt-a1i#1 resolve 先删内存 ledger、注入 orch ALLOW/DENY fire-and-forget,失败只 log 但 user 已见"已处理"→高风险放行决策丢失;tt-a1i#7 ledger 纯内存 Map 重启全丢,三入口收不了尾、DB approval_request 残留。

修:approval 状态进 SQLite(feishu_approvals,schema v35+v36)。resolve 原子抢占(UPDATE WHERE status=pending→resolving,持久化 decision)——相反 decision 不可翻盘、同 decision 重试幂等;注入成功才 markResolved,失败保留可重试、不假成功。mobile/relay/飞书三入口一致 + 重启穿透测试。钟馗 2 轮独立审(逐出"相反 decision 翻盘"blocking)。

known-limitation:同 decision at-least-once 重复注入(相反翻盘已堵),后续加 approval_id delivery 幂等。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangserva added a commit to huangserva/hive that referenced this pull request Jun 17, 2026
第二个 PM 编排/GLM(andy)执行的工作流(runtime-bug-sweep.mjs)扫出 14 bug → 钟馗独立核 → tt-a1i#1/tt-a1i#2/tt-a1i#3/tt-a1i#7 全修(tt-a1i#4 已被 P1 覆盖)。narrative 归档。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant