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:
1 parent
abf0a2f248
commit
d4faee3a1e
214 files changed
+9406
-1688
No files matched your search
@@ -108,10 +108,13 @@ export function agentSessionsRoutes(deps: AppDeps): Hono<AppEnv> {
|
||||
const body = await readJson(c);
|
||||
const modelId = optionalString(body, "modelId", { minLen: 1, label: "modelId" });
|
||||
const provider = optionalString(body, "provider", { minLen: 1, label: "provider" });
|
||||
// Model reference is submitted as a pair: provider can't appear without modelId (core does the same validation; this catches it early).
|
||||
if (provider !== undefined && modelId === undefined) {
|
||||
// Model reference is submitted as a pair — both or neither. Neither half is ever
|
||||
// inferred from the other, so half a reference is rejected here instead of being
|
||||
// resolved (core does the same validation; this catches it early). Omitting both
|
||||
// falls back to the Project's default model.
|
||||
if ((modelId === undefined) !== (provider === undefined)) {
|
||||
throw badRequest(
|
||||
"provider is specified but modelId is not: a model reference must be given as a pair.",
|
||||
"modelId and provider must be given together as a model reference pair: specify both, or neither to use the Project's default model.",
|
||||
);
|
||||
}
|
||||
const approvalMode = optionalEnum(body, "approvalMode", APPROVAL_MODES);
|
||||
@@ -361,6 +364,11 @@ export function sessionsRoutes(deps: AppDeps): Hono<AppEnv> {
|
||||
const row = resolveSession(c);
|
||||
const rel = c.req.query("path") ?? "";
|
||||
const download = c.req.query("download") === "1";
|
||||
// Sandboxed top-level preview ("open in a new tab" for html): the document keeps its REAL
|
||||
// content type but carries a CSP sandbox WITHOUT allow-same-origin — it renders and runs
|
||||
// fully in an opaque origin, so agent-generated markup cannot reach this origin's cookies
|
||||
// or API. The request itself still authenticates (top-level GET sends the Lax cookie).
|
||||
const preview = !download && c.req.query("preview") === "1";
|
||||
const { data, fileName, contentType, scriptable } = await deps.workspaceFiles.read(
|
||||
row.workspace,
|
||||
rel,
|
||||
@@ -368,15 +376,22 @@ export function sessionsRoutes(deps: AppDeps): Hono<AppEnv> {
|
||||
const disposition = download ? "attachment" : "inline";
|
||||
// Same-origin XSS defense: html/svg inline previews are always returned as plain
|
||||
// text (Workspace files may be Agent-generated and untrusted); downloads
|
||||
// (attachment) keep the real content type. Paired with nosniff to prevent MIME
|
||||
// sniffing from undoing this.
|
||||
const effectiveType = !download && scriptable ? "text/plain; charset=utf-8" : contentType;
|
||||
// (attachment) keep the real content type, and sandboxed previews keep it under the
|
||||
// CSP above. Paired with nosniff to prevent MIME sniffing from undoing this.
|
||||
const effectiveType =
|
||||
!download && scriptable && !preview ? "text/plain; charset=utf-8" : contentType;
|
||||
return new Response(new Uint8Array(data), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": effectiveType,
|
||||
"Content-Disposition": `${disposition}; filename*=UTF-8''${encodeURIComponent(fileName)}`,
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
...(preview && scriptable
|
||||
? {
|
||||
"Content-Security-Policy":
|
||||
"sandbox allow-scripts allow-popups allow-modals allow-forms",
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user