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
+19 -20
View File
@@ -13,6 +13,7 @@ import type {
CompactionMode,
CompactionReason,
EventMessage,
Fidelity,
ImageUrlPayload,
InlineDataPayload,
InlineThinkingPayload,
@@ -61,30 +62,28 @@ export function sessionMeta(payload: SessionMetaPayload): SessionMetaMessage {
// Complete model_msg -----------------------------------------------------------
/**
* Provider fidelity fields: kept as-is and restored verbatim on replay.
* Builder convention: positional-argument-style builders carry these in a trailing `fidelity`
* object (narrowed via Pick per payload type — e.g. thinking only has signature); object-argument-
* style builders (toolCall) flatten `fidelity` fields into the parameter object alongside
* `stopReason`, mirroring the payload structure directly.
* Provider-fidelity payload (opaque, see `Fidelity` in types.ts): kept as-is and restored
* verbatim on replay. Builder convention: positional-argument-style builders take it as a
* trailing `fidelity` object; object-argument-style builders (toolCall) carry it in the
* parameter object alongside `stopReason`. An empty object is treated as absent — the
* payload field is only set when the fidelity carries at least one key.
*/
export interface FidelityFields {
phase?: string | null;
signature?: string;
function fidelityProp(fidelity?: Fidelity): { fidelity?: Fidelity } {
return fidelity !== undefined && Object.keys(fidelity).length > 0 ? { fidelity } : {};
}
export function textMessage(
role: Role,
text: string,
stopReason: StopReason = "completed",
fidelity?: FidelityFields,
fidelity?: Fidelity,
): OmniMessage<TextPayload> {
return model({
type: "text",
role,
text,
stop_reason: stopReason,
...(fidelity?.phase != null ? { phase: fidelity.phase } : {}),
...(fidelity?.signature !== undefined ? { signature: fidelity.signature } : {}),
...fidelityProp(fidelity),
});
}
@@ -93,7 +92,7 @@ export const userText = (text: string): OmniMessage<TextPayload> => textMessage(
export const assistantText = (
text: string,
stopReason: StopReason = "completed",
fidelity?: FidelityFields,
fidelity?: Fidelity,
): OmniMessage<TextPayload> => textMessage("assistant", text, stopReason, fidelity);
export function imageUrlMessage(imageUrl: string): OmniMessage<ImageUrlPayload> {
@@ -109,7 +108,7 @@ export function inlineData(
role: Role,
data: string,
mimeType: string,
fidelity?: Pick<FidelityFields, "signature">,
fidelity?: Fidelity,
): OmniMessage<InlineDataPayload> {
return model({
type: "inline_data",
@@ -117,28 +116,28 @@ export function inlineData(
data,
mime_type: mimeType,
stop_reason: "completed",
...(fidelity?.signature !== undefined ? { signature: fidelity.signature } : {}),
...fidelityProp(fidelity),
});
}
export function thinkingMessage(
thinking: string,
stopReason: StopReason = "completed",
fidelity?: Pick<FidelityFields, "signature">,
fidelity?: Fidelity,
): OmniMessage<ThinkingPayload> {
return model({
type: "thinking",
role: "assistant",
thinking,
stop_reason: stopReason,
...(fidelity?.signature !== undefined ? { signature: fidelity.signature } : {}),
...fidelityProp(fidelity),
});
}
export function inlineThinking(
data: string,
mimeType: string,
fidelity?: Pick<FidelityFields, "signature">,
fidelity?: Fidelity,
): OmniMessage<InlineThinkingPayload> {
return model({
type: "inline_thinking",
@@ -146,7 +145,7 @@ export function inlineThinking(
data,
mime_type: mimeType,
stop_reason: "completed",
...(fidelity?.signature !== undefined ? { signature: fidelity.signature } : {}),
...fidelityProp(fidelity),
});
}
@@ -155,7 +154,7 @@ export function toolCall(args: {
arguments: string;
toolCallId: string;
stopReason?: StopReason;
signature?: string;
fidelity?: Fidelity;
}): OmniMessage<ToolCallPayload> {
return model({
type: "tool_call",
@@ -164,7 +163,7 @@ export function toolCall(args: {
arguments: args.arguments,
tool_call_id: args.toolCallId,
stop_reason: args.stopReason ?? "completed",
...(args.signature !== undefined ? { signature: args.signature } : {}),
...fidelityProp(args.fidelity),
});
}
+23 -14
View File
@@ -99,15 +99,23 @@ export interface SessionMetaPayload {
// ---------------------------------------------------------------------------
// Docs: /docs/omni-message § "model_msg: complete payloads"
/**
* Provider-fidelity payload (mirrors AgentHub's `Fidelity`): an arbitrary JSON-style object of
* wire-level data the LLM client records to reproduce the original message on replay — thinking
* signatures, phase labels, encrypted reasoning, the upstream reasoning field name, etc. Opaque
* to PenguinHarness: written to the Trace as-is and passed back verbatim; some models **require**
* it when history is replayed (e.g. Claude thinking signatures, GPT-5 encrypted reasoning) —
* losing it breaks Session resumption.
*/
export type Fidelity = Record<string, unknown>;
export interface TextPayload {
type: "text";
role: Role;
text: string;
stop_reason?: StopReason;
/** Provider fidelity field: text phase marker (e.g. GPT-5 segments by phase), kept as-is and restored verbatim. */
phase?: string | null;
/** Provider fidelity field: signature, kept as-is and restored verbatim. */
signature?: string;
/** Provider-fidelity payload (e.g. `phase` for GPT-5 segment markers, `signature`), kept as-is and restored verbatim. */
fidelity?: Fidelity;
}
export interface ImageUrlPayload {
@@ -125,8 +133,8 @@ export interface InlineDataPayload {
data: string;
mime_type: string;
stop_reason?: StopReason;
/** Provider fidelity field: signature, kept as-is and restored verbatim. */
signature?: string;
/** Provider-fidelity payload, kept as-is and restored verbatim. */
fidelity?: Fidelity;
}
export interface ThinkingPayload {
@@ -135,11 +143,12 @@ export interface ThinkingPayload {
thinking: string;
stop_reason?: StopReason;
/**
* Provider fidelity field: thinking-block signature (Claude thinking blocks / redacted
* thinking, GPT-5 encrypted reasoning, etc. — **required** when some models replay history),
* kept as-is and restored verbatim — losing it breaks Session resumption.
* Provider-fidelity payload closing the thinking block (Claude thinking signatures / redacted
* thinking, GPT-5 encrypted reasoning, the OpenAI-compatible reasoning field name, etc. —
* **required** when some models replay history), kept as-is and restored verbatim — losing it
* breaks Session resumption.
*/
signature?: string;
fidelity?: Fidelity;
}
export interface InlineThinkingPayload {
@@ -149,8 +158,8 @@ export interface InlineThinkingPayload {
data: string;
mime_type: string;
stop_reason?: StopReason;
/** Provider fidelity field: signature, kept as-is and restored verbatim. */
signature?: string;
/** Provider-fidelity payload, kept as-is and restored verbatim. */
fidelity?: Fidelity;
}
export interface ToolCallPayload {
@@ -161,8 +170,8 @@ export interface ToolCallPayload {
arguments: string;
tool_call_id: string;
stop_reason?: StopReason;
/** Provider fidelity field: signature, kept as-is and restored verbatim. */
signature?: string;
/** Provider-fidelity payload, kept as-is and restored verbatim. */
fidelity?: Fidelity;
}
export interface ToolCallOutputPayload {