Changelog, dev startup, README, AgentHub 0.4.0, model catalog, and landing site (#7)

Branch-length batch covering tooling, the model layer, the Web App and the public
surfaces. Highlights:

- Changelog: a per-release `changelog/<version>/` tree, grouped by the surface each
  change touches, with a root CHANGELOG.md holding one line per release.
- Dev startup: `scripts/dev-prebuild.mjs` serializes the skills+core prebuild behind a
  lock and keeps `pnpm install` current; `pnpm dev` runs server+web together.
- AgentHub 0.3.3 -> 0.4.0: OmniMessage complete payloads carry one opaque `fidelity`
  object in place of item-level `signature`/`phase`, threaded verbatim through Trace,
  replay and resume; malformed classification adapted to the new error types.
- Model layer: a model is always referenced by an explicit `(provider, model_id)` pair.
  The provider is never inferred, guessed or defaulted -- both the catalog inference and
  the unique-match config resolution are gone, and CLI, SDK, server routes and
  run_subagent all require the complete pair. Catalog gains the Qwen Token Plan, Qwen
  Pay-As-You-Go and Fireworks AI gateways, plus an expanded OpenRouter group.
- Web App: catalog preset sync and per-group speed test on the Models page, positional
  slash commands, a markdown renderer, skill-library update reminders, and a vertically
  centred draft page whose upward menus size themselves to the room available.
- Public surfaces: restructured READMEs, the penguin.ooo landing site and blog, refreshed
  benchmark results for both suites, and the demo videos playing on the landing page.

Includes the fixes from a full review of the branch: 23 confirmed findings, among them a
provider-inference bug that could send one vendor's API key to another vendor's endpoint,
and an Escape handler that destroyed the composer's contents unrecoverably.

Verified on the branch head: pnpm test (1127 passing, 7 packages), pnpm typecheck and
pnpm format:check clean, Playwright e2e 14/14.
This commit is contained in:
Yaowei Zheng
2026-07-21 17:43:31 +08:00
committed by GitHub
parent abf0a2f248
commit d4faee3a1e
214 changed files with 9406 additions and 1688 deletions
+82 -10
View File
@@ -1,16 +1,69 @@
/**
* Sticky top bar: logo + site name + "Docs" badge, then (right) a link back to the
* main site, GitHub, language/theme toggles and — on small screens — the sidebar
* toggle. The sidebar itself lives in the layout (router.tsx); this bar only flips
* its open state.
* Sticky top bar, landing-parity: logo + site name + "Docs" badge, then the SAME
* link row as the landing page's nav (section anchors on the landing home, blog,
* docs) with the same sliding hover pill — so the two sites link into each other
* seamlessly. Section/blog links are plain anchors into the landing SPA one level
* up; "Docs" routes to this site's own root. The right side keeps the language
* and theme toggles, GitHub, and — on small screens — the sidebar toggle.
*/
import { useRef } from "react";
import type { MouseEvent } from "react";
import { Link } from "react-router";
import { S } from "../lib/strings";
import { REPO_URL, SITE_URL } from "../lib/links";
import { GitHubIcon, MenuIcon, XIcon } from "./icons";
import { ThemeToggle } from "./theme-toggle";
import { LangToggle } from "./lang-toggle";
const SECTION_IDS = ["highlights", "quickstart", "benchmark", "contract", "features"] as const;
export function Nav({ menuOpen, onToggleMenu }: { menuOpen: boolean; onToggleMenu: () => void }) {
const pillRef = useRef<HTMLSpanElement | null>(null);
const pillVisible = useRef(false);
const sectionLabel: Record<(typeof SECTION_IDS)[number], string> = {
highlights: S.nav.highlights,
quickstart: S.nav.quickstart,
benchmark: S.nav.benchmark,
contract: S.nav.contract,
features: S.nav.features,
};
const linkCls =
"relative z-10 rounded-md px-2.5 py-1.5 text-sm text-gray-600 transition-colors hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100";
// Landing-parity active style: on the docs site, the "Docs" link is the current page.
const activeLinkCls =
"relative z-10 rounded-md px-2.5 py-1.5 text-sm transition-colors bg-black text-white hover:bg-black hover:text-white dark:bg-black dark:text-white dark:ring-1 dark:ring-gray-600 dark:hover:bg-black dark:hover:text-white";
/**
* The hover pill appears IN PLACE under the first link it lands on (position
* jumps with only the fade animating), slides while moving between links, and
* fades out where it is on leave — never sweeping in from the nav's edge.
*/
const slideTo = (e: MouseEvent<HTMLElement>) => {
const el = e.currentTarget;
const pill = pillRef.current;
if (!pill) return;
if (!pillVisible.current) {
pill.style.transitionProperty = "opacity";
pill.style.left = `${el.offsetLeft}px`;
pill.style.width = `${el.offsetWidth}px`;
void pill.offsetWidth; // flush the jump before restoring the full transition
pill.style.transitionProperty = "";
pillVisible.current = true;
} else {
pill.style.left = `${el.offsetLeft}px`;
pill.style.width = `${el.offsetWidth}px`;
}
pill.style.opacity = "1";
};
const hidePill = () => {
const pill = pillRef.current;
if (pill) pill.style.opacity = "0";
pillVisible.current = false;
};
return (
<header className="sticky top-0 z-40 border-b border-gray-200 bg-white/85 backdrop-blur dark:border-gray-800 dark:bg-gray-950/85">
<div className="mx-auto flex h-14 max-w-7xl items-center gap-2 px-4 sm:px-6">
@@ -32,13 +85,32 @@ export function Nav({ menuOpen, onToggleMenu }: { menuOpen: boolean; onToggleMen
</span>
</a>
<div className="ml-auto flex items-center gap-1">
<a
href={SITE_URL}
className="hidden rounded-md px-2.5 py-1.5 text-sm text-gray-600 transition-colors hover:text-gray-900 sm:inline-block dark:text-gray-400 dark:hover:text-gray-100"
>
{S.nav.home}
<nav
className="relative ml-4 hidden items-center gap-0.5 md:flex"
aria-label="Primary"
onMouseLeave={hidePill}
>
{/* Sliding hover pill: appears in place, slides between links, fades out in place. */}
<span
ref={pillRef}
aria-hidden="true"
className="absolute top-1/2 h-8 -translate-y-1/2 rounded-md bg-gray-100 transition-[left,width,opacity] duration-200 ease-out dark:bg-gray-800"
style={{ left: 0, width: 0, opacity: 0 }}
/>
{SECTION_IDS.map((id) => (
<a key={id} href={`${SITE_URL}#${id}`} className={linkCls} onMouseEnter={slideTo}>
{sectionLabel[id]}
</a>
))}
<a href={`${SITE_URL}blog`} className={linkCls} onMouseEnter={slideTo}>
{S.nav.blog}
</a>
<Link to="/" className={activeLinkCls} onMouseEnter={slideTo} aria-current="page">
{S.nav.docs}
</Link>
</nav>
<div className="ml-auto flex items-center gap-1">
<LangToggle />
<ThemeToggle />
<a
+7 -1
View File
@@ -6,7 +6,13 @@ export const en: Strings = {
docsBadge: "Docs",
nav: {
home: "Website",
highlights: "Highlights",
quickstart: "Quick start",
benchmark: "Benchmark",
contract: "CONTRACT.md",
features: "Features",
blog: "Blog",
docs: "Docs",
github: "GitHub",
openMenu: "Open navigation",
closeMenu: "Close navigation",
+9 -1
View File
@@ -10,7 +10,15 @@ export const zh = {
docsBadge: "Docs",
nav: {
home: "产品主页",
// Landing-parity labels: the top bar mirrors the landing page's nav exactly
// (same items, same order) so the two sites link into each other seamlessly.
highlights: "特色",
quickstart: "快速开始",
benchmark: "评测",
contract: "CONTRACT.md",
features: "功能",
blog: "博客",
docs: "文档",
github: "GitHub",
openMenu: "打开目录",
closeMenu: "关闭目录",