release: 0.2.1 (#204)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1cf0e4d9bc
commit
88916880eb
19 files changed
+94
-4
No files matched your search
@@ -0,0 +1,13 @@
|
||||
# Web App: skills management on the Agent settings page, deep-linked list icons
|
||||
|
||||
## Skills tab
|
||||
|
||||
Agent settings gains a Skills tab, placed right after Tools. The installed list re-reads `agent_state/skills/` on every fetch — the directory stays the single source of truth, exactly as the vault and schedule files do, so hand-installed or agent-installed skills show up without any registry. Rows carry the skill icon, name, localized short description and version/updated metadata; uninstalling asks for confirmation first.
|
||||
|
||||
An Import dialog opens from the tab header and leads with the recommended path: install by chatting with the agent. The source field takes more than a webpage URL — a GitHub/GitLab repo or directory, a local folder path, an install command from another ecosystem (`npx skills add …`, Claude Code / Codex plugin installs — those plugins are essentially skill files), or a bare marketplace reference — and a small classifier tailors the generated prompt per form (fetch the page / clone and locate the SKILL.md directories / read the folder / work out what the command would install and fetch that from its source rather than running it blindly). Every variant keeps the review-first clause and points at the skill-porting library skill when installed. "Open a new chat" pre-fills the composer with the generated prompt through the existing draft cache — the agent can adapt what it installs, which a blind unzip cannot. The dialog's second path uploads a skill zip: a new member-level `POST /api/projects/:p/agents/:a/skills/archive` accepts the archive as base64 JSON (the API's established upload shape), takes `SKILL.md` at the zip root or inside exactly one top-level directory, derives the name from that directory (frontmatter otherwise) under the usual name pattern, rejects path-escaping entries, and caps the payload at 200 files / 5 MB per file / 20 MB uncompressed. Uploading a name that is already installed answers 409 and the dialog offers an explicit overwrite, which replaces the directory wholesale. Unzipping uses a new `fflate` dependency in the server package.
|
||||
|
||||
The reverse direction ships too: each row carries an export action that downloads the installed skill as `<name>.zip` — `<name>-v<version>.zip` when the frontmatter declares an explicit version — the whole directory under a single top-level folder, served as a direct attachment like the trace download, under the same size caps, so a skill round-trips through the import endpoint unchanged. Export and uninstall are icon buttons with tooltips, following the icon-plus-tooltip rule the tool rows adopted.
|
||||
|
||||
## Agent list
|
||||
|
||||
The tools, vault-keys, schedules and skills stat icons on each agent row are now buttons that open the settings page landed on the matching tab. Settings learned a `?tab=` deep link for this: the value is validated against the live tab list (unknown values fall back to Overview) and tab switches keep the URL in sync without polluting history. The active-session count badge is gone from list rows — the sessions column already tells the story there — while the settings Overview keeps its active-session figure.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Fix: ANSI color codes no longer leak into tool output
|
||||
|
||||
A nested `penguin run` driven through `exec_command` and polled with `input_command` filled Web tool cards with `[36m`/`[0m` fragments spliced into words (#102). Three layers each contributed, and each is fixed:
|
||||
|
||||
- **CLI**: the renderer wrote escape codes unconditionally. Color is now decided once per output stream — TTY, `NO_COLOR` unset, `TERM` not `dumb`, with a non-empty `FORCE_COLOR` overriding in either direction, matching Node's own semantics — and every renderer escape routes through that palette, so piped output is plain text.
|
||||
- **Command tool environment**: the child environment always set `NO_COLOR=1` and `TERM=dumb`, but an inherited `FORCE_COLOR` silently won (Node ignores `NO_COLOR` when `FORCE_COLOR` is set). `FORCE_COLOR` and `CLICOLOR_FORCE` are now stripped — removed, not blanked, since Node reads an empty `FORCE_COLOR` as "on" — so the hardening finally holds; a vault-provided value still passes through by design.
|
||||
- **Web**: tool output renders through a defensive ANSI stripper (CSI including multi-parameter SGR, OSC, two-byte escapes, and an incomplete trailing sequence cut mid-stream), applied at render time only — historical Traces display clean without their files being rewritten. The Trace event inspector deliberately keeps raw payloads: it is the raw-data view.
|
||||
|
||||
Regression tests cover all three layers, including the reported `FORCE_COLOR=3` + `NO_COLOR=1` + `TERM=dumb` combination and sequences split across streaming chunk boundaries.
|
||||
@@ -0,0 +1,3 @@
|
||||
# CLI: `penguin web` reports readiness-probe failures
|
||||
|
||||
`penguin web` used to discard every readiness-probe exception and print the same generic "not responding yet" line, which read as the server being slow even when the real cause was a firewall silently dropping loopback requests. The probe now retains the last nested Node/undici error, classifies it (connection timeout, refused, reset/socket, permission, DNS, unknown), and prints an actionable, localized diagnosis on stderr — the timeout case asks the user to allow PenguinHarness to communicate on the configured local port rather than telling anyone to disable a firewall. Any HTTP response still counts as ready, and the classification is covered by focused unit tests.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Compaction extraction tolerance, retry parity, and failure observability
|
||||
|
||||
Fixes #170, where deepseek-v4-flash sessions became unusable: the model wrote `[summary][/summary]` as a title with the body after the closing tag, extraction took the empty pair, and every retry re-showed the model its own committed bad output — which it copied verbatim, forever. `extractSummary` now applies a tolerance ladder (first non-empty tag pair → whatever remains after stripping empty summary blocks → tagless output verbatim), preserving byte-identical re-extraction of healthy historical Traces.
|
||||
|
||||
A committed-but-unusable compaction response now counts as one more failed attempt on the standard `compactionMaxReconnects` budget and exponential backoff ladder — only `auth` stops without retrying — with the tool_use pairing repaired and a corrective note prepended before the re-sent prompt (append-only, prompt-cache-safe). The burned cost becomes visible: `compaction_end` gains `attempts`, every attempt's `token_usage` is surfaced between the compaction event pair, and a failed compaction lands as one cost-center error row. Retry state moves to one shared `RetryDetail` shape (`error_message` / `attempt` / `retry_in_ms`) across `request_end` and `compaction_end`, read directly by the CLI and Web retry displays instead of client-side counting. New agents get a shorter compaction prompt that shows the format as a concrete example; existing agents keep their stored prompt and are covered by the extraction rescue and retry guidance.
|
||||
@@ -0,0 +1,3 @@
|
||||
# data-analysis skill v2 and a leaner multi-run evaluation flow
|
||||
|
||||
The `data-analysis` library skill moves to v2, tightening its constraints on data granularity and semantics, native artifact handling, complete delivery, and risk-proportional verification. The benchmark flow stops re-running what it already measured: the design stage pins every Case at one run and reuses the selected Pilot results verbatim as the Formal Baseline, the Optimization stage takes a user-specified per-Candidate `runs` count dispatched Case × Runs in parallel and compares the recorded averages directly, and the Evaluator treats `run` as an upstream-assigned label instead of rejecting runs beyond the config's total. Docs (zh/en), the frontend example prompts and the contract tests are updated in sync.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Dependencies: hono ≥ 4.12.34 (CORS preflight ReDoS)
|
||||
|
||||
Resolves the repository's one open Dependabot alert: GHSA-8j4g-w8fx-2239 / CVE-2026-69207, a moderate ReDoS in Hono's `hono/cors` middleware — quadratic regex backtracking on an attacker-controlled `Access-Control-Request-Headers` preflight header when `allowHeaders` is unconfigured. The server package's direct `hono` range moves from `^4.8.0` to `^4.12.34` (the first patched version, same major); the lockfile change is confined to the hono chain, and `pnpm audit` reports no known vulnerabilities after the bump.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Desktop app: Electron shell over the embedded server
|
||||
|
||||
New `packages/desktop`: a desktop distribution of the Web App that runs the existing server and frontend unchanged — an Electron shell forks `@prismshadow/penguin-server` as a utilityProcess on the shared data root and points its window at `http://localhost:<port>`, keeping the plain same-origin HTTP/SSE contract (no private IPC, no preload, no node integration in the window).
|
||||
|
||||
## Shell
|
||||
|
||||
The single-instance shell starts the server with `PORT=0`, learns the actual port from a `PENGUIN_PORT_FILE` announcement, and loads a one-shot `GET /api/auth/desktop-login?token=…` — the per-launch token (passed via `PENGUIN_DESKTOP_TOKEN`) lands the window signed in as admin with no login page. External links and Workspace previews open in the system browser. A crashed server restarts with 1s/2s/4s backoff; quitting stops it through `POST /api/desktop/shutdown` (Bearer token — the only graceful path on Windows, where killing a child is a hard terminate), falling back to kill.
|
||||
|
||||
## Desktop mode on the server
|
||||
|
||||
Desktop mode is loopback-only and adds: the one-shot login endpoint, the reusable shutdown endpoint, an `auth_sessions.via` column ("password" | "desktop", idempotent ALTER), and `desktopMode` + `sessionVia` on `GET /api/me`. The seeded admin of a desktop-created data root gets a fully random password that is never printed — sign-in goes through the shell's token — and desktop-established sessions may change the password without the old one (browser sessions against the same server still must provide it). With `PORT=0` the `::1` preview listener now reuses the actual bound port instead of grabbing a second random one.
|
||||
|
||||
## Single instance per data root
|
||||
|
||||
`web.db` is single-writer and the schedule scheduler must not run twice, so a new `<root>/server.lock` (pid + port liveness; stale locks overwritten, released on shutdown) admits one server per data root, exported side-effect-free as `@prismshadow/penguin-server/lock`. `penguin server` now refuses a busy root and prints the existing instance URL; `penguin web` opens the existing instance instead of failing; the desktop shell attaches its window to it (normal login page — the one-shot token only works against a server the shell spawned). This also removes the pre-existing hazard of two `penguin web` processes double-running the same schedules.
|
||||
|
||||
## Web App in desktop mode
|
||||
|
||||
The window hides what the shell makes meaningless: the logout entry (the window is the session), the initial-password banner (the seed is random and never shown), and the self-update entry (updates belong to the desktop app; the web self-update re-runs the CLI entry, which does not exist under the shell). The change-password dialog drops the old-password field for desktop sessions.
|
||||
|
||||
## Packaging (three platforms)
|
||||
|
||||
electron-builder produces macOS dmg + zip (arm64/x64), Windows NSIS and Linux AppImage + deb from a `pnpm deploy --prod` staging tree — a portable node_modules with the workspace packages materialized and the web build placed in the server package's npm layout; asar is off on purpose (the shell forks the server, the skill library reads its files from disk, agent commands spawn real shells). Windows carries the same pinned MinGit as the CLI zip and advertises it as `PENGUIN_BUNDLED_SHELL`. A reusable `desktop-build.yml` three-OS matrix (with `workflow_dispatch` dry runs) runs inside `release.yml` BEFORE the Release is created — assets are immutable at publish — attaching the installers plus `SHA256SUMS.desktop`. M3 artifacts are unsigned; signing, notarization, icons and electron-updater are the next milestone. Dev quality-of-life: root `pnpm desktop` builds everything and starts the shell on the dev data root, and a preflight turns the stale-injected-copy `ERR_MODULE_NOT_FOUND` into an actionable message.
|
||||
|
||||
## Fixes
|
||||
|
||||
Both Workspace HTML preview entry points were dead in the desktop app and now work. The preview redirect resolved to port `0` — preview URLs are built from the server's own bind port (deliberately, since dev serves the SPA on a different port) while the shell starts the server with `PORT=0` — producing a `http://127.0.0.1:0/…` address browsers reject as an unsafe port; the actual bound port is now written back once listening, and an unknown port degrades to the sandboxed same-origin preview instead of an unloadable URL. "Open in a new tab" silently did nothing, because the link is app-origin and the shell denied every popup while only forwarding external URLs to the system browser — where the cookie-gated redirect would 401 anyway; app-origin popups now open a Node-free child window that follows the redirect and may navigate this instance's loopback surface, with anything else still going outward. Separately, the desktop shell's process credentials (`PENGUIN_DESKTOP_TOKEN`, `PENGUIN_PORT_FILE`) and the pinned seed password no longer leak into Agent command environments.
|
||||
|
||||
The app also stopped forgetting UI preferences (language, theme) between launches: the renderer persists them in `localStorage`, which the browser scopes to the window origin, and a fresh `PORT=0` every launch changed that origin every time. The shell now remembers the port the server actually bound (`userData/preferred-port`) and requests it again when it is still free on both loopback stacks, falling back to `PORT=0` whenever it is taken — the OS allocator remains the only source of port numbers, and a lost port costs exactly one preference reset.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Desktop installers on the OSS mirror, and a landing download page
|
||||
|
||||
## Distribution
|
||||
|
||||
Desktop installers move to version-less artifact names following the CLI bundle convention — `penguin-desktop-darwin-{arm64,x64}.dmg` / `.zip`, `penguin-desktop-win32-x64.exe`, `penguin-desktop-linux-x64.AppImage` / `.deb` — with the version carried by the Release tag and `SHA256SUMS.desktop`. The OSS mirror job now mirrors all seven installers plus `SHA256SUMS.desktop` into the immutable per-tag prefix, verified as a set through `SHA256SUMS.desktop`; the CLI bundles' canonical manifest is unchanged.
|
||||
|
||||
## Landing site
|
||||
|
||||
New `/download` page in the classic software-download shape: one card per platform with the visitor's OS badged, click-to-download buttons that start on GitHub's static `releases/latest/download/<name>` links and swap to the OSS mirror's per-tag URLs once the bucket's `latest.json` pointer resolves client-side (validated exactly like the installer forwarders validate it — a failed fetch, e.g. missing CORS, silently keeps the GitHub links), a manual GitHub/OSS source toggle, checksum and all-releases links, and the unsigned-build first-launch notes. The nav (landing and its docs parity copy), footer, quick-start hint, sitemap and Pages route shells are wired accordingly, and the README (en/zh) gains a desktop app install section pointing at the page.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Download source selection for standalone installers and `penguin update`
|
||||
|
||||
Standalone `install.sh` / `install.ps1` files gain the same OSS-first source selection the `penguin.ooo` forwarding layer got in 0.2.0 (`PENGUIN_DOWNLOAD_SOURCE=auto|oss|github`). Newly published installers are stamped with their immutable Release tag, so a versioned installer downloads exactly its matching package instead of silently following a future latest Release — `auto` tries that tag on OSS first and falls back to the same tag on GitHub, explicit `PENGUIN_DOWNLOAD_BASE_URL` / fallback overrides keep top priority, and offline installation plus unconditional checksum enforcement are unchanged. An unstamped source-tree installer locks a tag through the OSS `latest.json` pointer before downloading anything, and a one-sided stamping state fails the release rather than shipping mismatched POSIX and Windows installers.
|
||||
|
||||
`penguin update` now follows the same contract instead of requiring GitHub at the start of every upgrade: release discovery prefers the validated OSS `latest.json` and its immutable release in `auto` (same-tag GitHub fallback; forced `oss` and `github` modes stay strict), the selected payload base and same-tag fallback are handed to the child installer with a stale inherited fallback explicitly cleared, explicit HTTPS mirrors keep precedence, and source-selection failures are reported localized.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Server: the last two IO hotspots — scheduler ticks and chat-history loads
|
||||
|
||||
The disk-IO audit that produced the trace index left two hotspots standing; both are closed.
|
||||
|
||||
## Scheduler ticks and project-config reads
|
||||
|
||||
The 30-second scheduler tick used to re-read every schedule file of every agent and re-parse the same `.project_config.toml` once per schedule file, forever, with no change detection — the only unconditional recurring disk load in the process. Schedule scanning is now cached per agent, keyed on the schedule directory's mtime plus a per-file mtime/size check (the per-file stat closes the POSIX blind spot where an in-place edit never touches the directory mtime, so hand-edited files are still picked up on the next tick); the agents-dir enumeration itself is gated the same way, and the "fresh mtime is never cached as clean" sentinel moved to a shared `internal/mtime-gate.ts` that the trace index now imports too — the contract lives in one place. `.project_config.toml` gains a parsed-table cache inside `ProjectConfigService`, stat-checked before reuse and invalidated synchronously by every service write path, so scheduler validation, the schedules routes and usage pricing all share one parse per on-disk version while hand edits still land. Steady state per tick: a handful of stats, zero file reads; scheduling behavior, listing order and validation semantics are byte-identical, and tests pin the zero-read steady state with fs probes.
|
||||
|
||||
## Chat-history pagination
|
||||
|
||||
`GET /api/sessions/:id/messages` rebuilt a session's entire transcript per request — every shard read whole, subagent sessions recursively included, all of it re-done on every SSE resync. The endpoint now takes additive cursor params: `tailLimit` returns the newest window (with the running session's live attachment, same capture semantics), `before=<shard>:<ordinal>` pages older history; without params the response stays byte-identical to before, pinned by a raw-byte test. Windows cut on Task boundaries only — tool-call pairings, compaction spans and grouped sends are never split — and only the shards overlapping the window are read, with shard inventory from the trace index and older shards' turn counts and token totals served from a per-shard `page_stats` prefix cache (a derived, versioned column added idempotently to existing databases; no user action). The web loads the tail first (200 units) and backfills 100 at a time on scroll-up with anchored prepends; a resync splices onto the retained prefix only when epoch and cursor continuity are provable, anything doubtful falling back to the legacy full refetch. The conversation outline keeps globally correct turn numbering over partial history via a server-supplied earlier-turn count, and the header token/elapsed figures stay truthful through seeded prefix stats.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Models: OpenRouter gains qwen/qwen3.8-max
|
||||
|
||||
Prices and specs read from OpenRouter's models API on 2026-08-04.
|
||||
|
||||
The OpenRouter group gains `qwen/qwen3.8-max` — USD 2/6 per mtok input/output, cache reads at the published 0.25 hit price, cache writes at the genuine 2.5 (1.25× input) premium, a 1,000,000-token context window, vision — inserted ahead of `qwen/qwen3.6-35b-a3b` per the newer-versions-first ordering. The block's provenance comment now counts it among the rows carrying a real cache-write premium. The catalog already had the model through the qianwenai resale groups; this adds the OpenRouter spelling with OpenRouter's own USD prices.
|
||||
|
||||
The agenthub-models skill (v10) adds a Qwen 3.8 Max row recording the OpenRouter id — no other gateway in the catalog resells it, so that column stands alone.
|
||||
|
||||
As with every catalog addition there is no automatic migration: existing Projects keep their stored model tables and pick the row up through the models page's explicit "sync presets" action; new Projects see it out of the box.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Web App: per-Project defaults for new chats
|
||||
|
||||
Project settings gains a "New chat defaults" section (owner-editable; members see it read-only; placed below Members, laid out as a compact two-column grid): the default Agent, working directory (empty means the auto temp dir), approval mode and thinking level applied when a chat is created. The workspace and model controls are the composer's own pickers — the draft view's directory browser and the chat input's model picker were extracted into shared components (`workspace-select.tsx`, `model-select.tsx`) and both original surfaces refactored onto them, so the settings dialog and the composer render one implementation. In the dialog they wear a `form` trigger variant styled with the dialog's own field tokens (the composer keeps its pill triggers byte-for-byte), their menus paint above the modal, and the modal's private Escape stack was generalized into a shared esc-layer stack so Escape closes the topmost thing first — menu, then dialog. The values live in an optional `[default_chat]` table in `.project_config.toml` — `agent_id`, `workspace`, `approval_mode`, `thinking_level` — served by a member `GET` and owner `PUT /api/projects/:p/chat-defaults`; the PUT is a declarative whole-block replace (an omitted key clears it), rejects unknown agents and invalid enum values, and read-modify-writes the TOML so models and credentials are untouched.
|
||||
|
||||
The draft view seeds from these beneath anything more specific: route state (e.g. "New Chat" from an agent row) beats an unsent draft, which beats the project default, which beats the previous hardcoded fallbacks. The model row deliberately adds no new key: it renders and writes the same top-level `default_model` the models page owns, through a narrow owner `PUT /api/projects/:p/models/default` that validates membership in the models table, and changing it releases any draft-pinned model exactly as the models page does (the two surfaces now share that helper).
|
||||
|
||||
Thinking level resolves as: the Agent's explicit `model.thinking_level`, else the project default, else `medium`. The draft picker shows the effective value, and a pick still writes the Agent's own config — the project value is only ever a fallback. One deliberate behavior change rides on this: an Agent config with no explicit level now follows the project default where it previously always meant the built-in default.
|
||||
|
||||
The block is additive — configs without it behave exactly as before and no migration runs. One version-skew caveat: an older penguin CLI (0.2.0 and earlier) rewriting a project config drops the block, since its loader rebuilds a known-keys literal; the current code round-trips it. The TOML renderer also learned to emit table-valued keys after scalar keys, so a later scalar append (say, a rename on a config that lacked `name`) can no longer be parsed into a preceding table.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Skills: skill-porting — install skills from any ecosystem
|
||||
|
||||
New library skill `skill-porting` (agent-tuning group) teaching an agent to bring skills in from the outside world and land them correctly in `agent_state/skills/<name>/`. Its schema tables were verified by directly fetching each source on 2026-08-04 — the skill says so and treats the live JSON as authoritative:
|
||||
|
||||
- **Claude Code plugin marketplaces** (`anthropics/claude-plugins-official`, 278 plugins): the marketplace.json shape, all four `source` forms (relative path, sha-pinned `url`, `git-subdir`, `github`) and the five places a plugin can keep skills.
|
||||
- **Codex plugins** (`openai/plugins`, 180 plugins): the `.agents/plugins/marketplace.json` entry shape with its `policy` block and `.codex-plugin/plugin.json` layout.
|
||||
- **`npx skills add` / skills.sh** (`vercel-labs/skills`): spec forms, per-agent install destinations, and the in-repo discovery order — resolved by fetching from the source repo, never by blindly running the installer.
|
||||
- Plain **GitHub repos/subdirectories** (sparse checkout / tarball / raw fetch variants) and **local folders**, plus the agentskills.io SKILL.md convention.
|
||||
|
||||
Each flow ends in the same normalization: adapt frontmatter to penguin's fields (`short_description`, `short_description_zh`, integer `version`, `updated`), port or drop commands/agents/hooks penguin has no runtime for (honestly — the skill forbids pretending a dropped capability survived), and verify the install. Safety first is mandatory: read every file before installing, refuse content that exfiltrates, phones home, or overrides safety rules, prefer pinned revisions.
|
||||
|
||||
The web Import dialog's generated prompt points agents at this skill when it is installed. Docs skill tables (en/zh) gained the one row the library-sync test requires.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Web App: the Traces page scales like the session sidebar
|
||||
|
||||
The trace list used to fetch everything at once: every agent auto-expanded on load, each expansion walked the agent's entire trace tree server-side with a stat per file, every session row and file pill rendered, and any session the paged sidebar store hadn't loaded fell back to its raw id in mono type — long-lived Projects froze the page.
|
||||
|
||||
`GET /api/projects/:p/agents/:a/traces` now takes optional `offset`/`limit`. Without them the response shape is byte-identical to before; with them the server returns newest-first session groups plus a total, and only the returned page is stat-ed (sizes written back to the index).
|
||||
|
||||
Discovery itself no longer touches the tree per request. A SQLite-derived **trace index** (`trace_files` plus per-session facts read once at registration: origin, workspace, a first-prompt title fallback, model ref) serves every consumer. The hot path is two stats — an mtime gate on the traces root and the newest date dir; a moved gate triggers an incremental readdir of only the changed date dirs, each new session classified once with a bounded head-read; server-side writes (import, session/agent/project deletion) update the index synchronously; and every consumer's miss forces one full reconcile before it may 404, so a stale index costs one extra scan, never a lie. The index is a rebuildable cache — the on-disk Trace files stay the single source of truth, and the first request after an upgrade or restart simply rebuilds it. The same index now feeds the agents-list 30-day activity sparkline (previously a full-history walk on every agents-list request), the `/messages` subagent resolver (previously a project-wide scan), trace locate/download/delete, CLI-session discovery — adoption reads no file anymore — and session resume. Listings do zero head-reads; titles come from the stored facts: DB session title, else the registered first-prompt fallback.
|
||||
|
||||
The page now mirrors the sidebar's structure outright, not just its pure helpers. The sidebar's group header, folder section, "more" rows and group-mode toggle were extracted verbatim from its inner closures into shared `components/ui/group-list.tsx` — and the sidebar itself was refactored onto them, so the two surfaces render from one implementation. The traces tree gains both grouping modes (by Workspace or by Agent, on the same persisted preference as the sidebar — flipping the mode on either surface flips both) and the three lazy folders per group (subagent, schedule, archived), each paging independently. Server support is additive: paged session groups carry `category` and `workspace`, the response carries per-category and per-workspace counts, and a `category=` filter pages within one folder — all answered from the trace index, with classification exact on the first request since facts are stored at registration.
|
||||
|
||||
Elsewhere the earlier fixes stand: the page-size split for load-more (traces-specific steps — 20 sessions, 15 groups, larger than the sidebar's 10s since the full-height page holds more), group caps with a "more groups" row, pinned-first ordering for a deep-linked agent, only the focused (or first) agent expanded by default, capped file pills, and a memoized title lookup. CLI-origin sessions follow the user's "show CLI sessions" preference here too, defaulting to hidden; a "View trace" deep link to a CLI session still resolves and opens the detail pane regardless of the preference. Session ids no longer render anywhere on the page — an untitled session shows the standard "New chat" label, and the detail subtitle keeps date · size only. The chat page links back: its session info dropdown gains a "View trace" action deep-linking to the traces page with the agent focused and the session selected, falling back to a one-agent full fetch when the session sits beyond the first page or inside a folder.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Web App: random seeded admin password and login throttling
|
||||
|
||||
The built-in admin's fixed, publicly documented initial password (`penguin-2026`) is gone.
|
||||
|
||||
## Random seed password
|
||||
|
||||
On first start the server now seeds `admin` with a random `penguin-<4 digits>` password and prints it exactly once to the startup console (`Seeded built-in admin "admin" — initial password: penguin-1234 …`); the login page and change-password dialog point at the startup output instead of naming a constant, and the READMEs, docs, installer next-steps and blog posts follow. `PENGUIN_SEED_ADMIN_PASSWORD` pins the seed for automated tests and e2e, and is subject to the same ≥ 8 characters policy as every other initial/reset password — a configuration typo cannot create a trivially weak privileged account.
|
||||
|
||||
## Login throttling
|
||||
|
||||
The login endpoint gains per-username throttling so the 10,000-value password shape stays a convenience, not an attack surface: after 5 consecutive failures, the next attempt is admitted only after an exponentially growing delay from the last failure (1s doubling to a 60s cap; attempts inside the window get `429 too_many_attempts` — localized in the UI — and do not extend it), and a successful login clears the counter. Past ~40 failures an attacker gets one guess per minute, while a mistyping user never waits more than the cap. Unknown usernames are throttled identically, so the throttle is not an account-existence oracle. Counters live in process memory; a restart clears them.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Web App: outline windowing, a cost stat that stays put, attachments as user content, quieter failure and update chrome
|
||||
|
||||
## Conversation outline
|
||||
|
||||
The tick-rail minimap over the stream's left gutter now appears only once a conversation reaches 5 exchanges, and renders a sliding window of at most 20 turns either side of the active one instead of one tick per turn forever — the window recenters as reading position moves, shifts rather than shrinks at the ends, and small edge dots mark hidden ranges; global turn numbering is preserved and the toolbar dropdown fallback still lists every turn. Two overlap bugs went with it: the tick stack is height-adaptive with an overflow backstop, so very long runs can no longer spill ticks over the toolbar and composer; and the gutter-fit check, which compared against a hardcoded 768 px column, now resolves the column's real 48 rem width against the live root font size — under browser font scaling the old check kept the rail visible while the widened prose column ran beneath it.
|
||||
|
||||
## Cost stat
|
||||
|
||||
The toolbar cost chip could vanish mid-run: goal rounds reset the live task buckets while the running state blocked the session-total refetch, a page opened during an active run never fetched the accrued total at all, and the idle blip between queued follow-ups could clobber a known total with an empty response. The displayed figure is now sticky and monotone while a session runs — the last fetched total plus each finished Task's settled increment plus the open Task's live estimate, reconciled verbatim once the session is idle. The usage fetch fires on session open regardless of run state, and an empty response can no longer erase a known value.
|
||||
|
||||
## Attachments as user content
|
||||
|
||||
Uploaded file attachments belong to the user's message and now render like it: below the user text in the same right-aligned container and hover-timestamp footer as uploaded images, instead of a left-aligned system-notice banner above the bubble (`AttachedFilesBanner` becomes presentation-only; its caller owns alignment, animation and the timestamp). A files-only steering message also shows its attached-files banner inside the steering chip — previously the filenames were dropped entirely and the chip rendered empty.
|
||||
|
||||
## Quieter chrome
|
||||
|
||||
Tool rows no longer print stop-reason markers at all — `[failed]`, `[aborted]`, `[timeout]`, `[malformed]` and `[auth]` are gone; the status icon is the single carrier of the outcome, with the raw reason still in its tooltip and aria-label, and the Trace viewer keeping the literal per-event values. The home page's "new version" hint sheds its accent pill for plain superscript text set in the version line's own type; only the link affordance remains.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Version 0.2.1
|
||||
|
||||
Released on 2026-08-04.
|
||||
|
||||
- [2026-08-04] Desktop app distribution: installers move to version-less names (`penguin-desktop-<os>-<arch>.<ext>`), are mirrored to the OSS bucket's immutable per-tag prefix alongside `SHA256SUMS.desktop`, and get a landing `/download` page — per-platform cards with the visitor's OS badged, static GitHub latest links that swap to the OSS mirror once its `latest.json` resolves client-side, a source toggle, and checksum links; nav/footer/README wired. ([details](2026-08-04-desktop-distribution-and-download-page.md))
|
||||
|
||||
- [2026-08-04] Tooling & CLI: standalone `install.sh` / `install.ps1` gain the forwarder's OSS-first download source selection with immutable Release-tag stamping, and `penguin update` adopts the same contract — OSS `latest.json` discovery with same-tag GitHub fallback in `auto`, strict forced modes, mirror overrides, localized failures. ([details](2026-08-04-download-source-selection.md))
|
||||
|
||||
- [2026-08-04] Core: compaction survives malformed summaries (fixes #170) — tolerant extraction, unusable responses retried on the standard backoff budget with a corrective note, burned attempts and tokens surfaced in stats, and one shared `RetryDetail` shape (`error_message`/`attempt`/`retry_in_ms`) across `request_end` and `compaction_end` read by both frontends. ([details](2026-08-04-compaction-retry-observability.md))
|
||||
|
||||
- [2026-08-04] Skills: `data-analysis` v2 — tighter data-semantics/delivery/verification constraints, and a leaner multi-run evaluation flow (Pilot results reused as the Formal Baseline, per-Candidate `runs` dispatched Case × Runs, Evaluator accepts `run` as a label). ([details](2026-08-04-data-analysis-skill-v2.md))
|
||||
|
||||
- [2026-08-04] Server: the last two IO hotspots closed — the 30-second scheduler tick and the schedules routes now serve from mtime-gated caches (zero steady-state file reads, hand edits still picked up; `.project_config.toml` parsed once per on-disk version across scheduler, routes and usage pricing), and `GET /messages` gains cursor pagination with tail-first web loading, Task-boundary windows, provable-continuity resync splicing and globally correct outline numbering — the no-params response stays byte-identical. ([details](2026-08-04-io-hotspots-scheduler-messages.md))
|
||||
|
||||
- [2026-08-04] Dependencies: `hono` bumped to ≥ 4.12.34, resolving the open Dependabot alert (GHSA-8j4g-w8fx-2239, CORS preflight ReDoS); lockfile change confined to the hono chain. ([details](2026-08-04-deps-hono-redos.md))
|
||||
|
||||
- [2026-08-04] Desktop app: new `packages/desktop` — an Electron shell that runs the existing server as a utilityProcess on the shared data root and its window on `http://localhost` with token-based no-login sign-in, graceful shutdown, and crash restart; desktop mode on the server (one-shot login, shutdown endpoint, unprinted random seed, `desktopMode`/`sessionVia` on /api/me), a new per-root `server.lock` single-instance guard the CLI and shell both honor, desktop-aware Web App chrome, and three-platform packaging (electron-builder over a pnpm-deploy staging tree, built by a reusable CI matrix ahead of Release creation; unsigned until the signing milestone). UI preferences (language, theme) now survive restarts: the shell reuses last launch's port when still free so the window origin — and its origin-scoped localStorage — stays stable, with `PORT=0` remaining the sole allocator. ([details](2026-08-04-desktop-app.md))
|
||||
|
||||
- [2026-08-04] Web App: the public fixed admin password `penguin-2026` is replaced by a random `penguin-<4 digits>` seed printed once at first start (`PENGUIN_SEED_ADMIN_PASSWORD` pins it for tests, policy-checked), and the login endpoint gains per-username exponential throttling (5 free failures, 1s doubling to 60s, `429 too_many_attempts`, reset on success, identical for unknown usernames) so the 4-digit space cannot be enumerated. ([details](2026-08-04-web-app.md))
|
||||
|
||||
- [2026-08-04] CLI: `penguin web` readiness-probe failures are no longer swallowed — the last error is classified (timeout / refused / reset / permission / DNS) and reported with actionable, localized guidance instead of a generic "not responding yet". ([details](2026-08-04-cli-web-probe-diagnostics.md))
|
||||
|
||||
- [2026-08-04] Skills: new `skill-porting` library skill — verified, schema-accurate flows for bringing skills in from Claude Code plugin marketplaces, Codex plugins, `npx skills add`/skills.sh, GitHub repos and local folders, normalizing them into penguin's skill format with a read-everything-first safety gate. ([details](2026-08-04-skill-porting-library-skill.md))
|
||||
|
||||
- [2026-08-04] Web App: skills become manageable from Agent settings — a Skills tab after Tools listing the installed set straight from disk, uninstall with confirmation, an Import dialog that leads with chat-driven install (URL + generated review-first prompt) and also accepts a skill zip through a new validated archive endpoint, and per-skill zip export that round-trips through that import; the agent list's tools/vault/schedules/skills icons deep-link to the matching settings tab via a new `?tab=` param, and the list rows drop the active-session badge. ([details](2026-08-04-agent-skills-and-list.md))
|
||||
|
||||
- [2026-08-04] Web App: Project settings gains a "New chat defaults" section — default Agent, working directory, approval mode and thinking level stored as an additive `[default_chat]` block in `.project_config.toml`, seeded into new drafts beneath route overrides and unsent drafts; the model row reads and writes the same `default_model` the models page owns through a narrow set-default endpoint, and thinking level resolves Agent-explicit → project default → `medium`. ([details](2026-08-04-project-chat-defaults.md))
|
||||
|
||||
- [2026-08-04] Web App: the Traces page becomes a second surface of the session sidebar — shared group/folder/mode-toggle components (the sidebar refactored onto them), workspace and agent grouping on the sidebar's own persisted preference, lazy subagent/schedule/archived folders, server-side session paging — and trace discovery plus the agents-list activity sparkline move off per-request filesystem walks onto a SQLite-derived, mtime-reconciled trace index (rebuildable cache; disk stays the source of truth). CLI sessions now follow the show-CLI-sessions preference (default hidden), titles fall back to the first user prompt, and raw session ids are gone from the page. ([details](2026-08-04-traces-page-scaling.md))
|
||||
|
||||
- [2026-08-04] Web App: chat refinements — the conversation outline rail appears from 5 exchanges and windows to ±20 turns around the active one (fixing its overlap onto the composer and, under browser font scaling, onto the prose column), the toolbar cost stat no longer blinks out mid-run, uploaded file attachments render as user content (right-aligned below the text like uploaded images, and inside the steering chip instead of vanishing from files-only steering), tool rows drop the stop-reason text markers entirely in favor of the status icon, and the home update hint sheds its pill for plain superscript text. ([details](2026-08-04-web-chat-refinements.md))
|
||||
|
||||
- [2026-08-04] Fix: ANSI color codes no longer leak into tool output — the CLI gates color on TTY/`NO_COLOR`/`TERM` with `FORCE_COLOR` override semantics matching Node, the command tool strips inherited `FORCE_COLOR`/`CLICOLOR_FORCE` from child environments so its `NO_COLOR=1` hardening wins, and the Web renders tool output through a defensive ANSI stripper covering historical Traces. ([details](2026-08-04-ansi-tool-output.md))
|
||||
|
||||
- [2026-08-04] Models: OpenRouter gains `qwen/qwen3.8-max` (1M context, vision, published cache-hit and cache-write prices), and the agenthub-models skill (v10) records the OpenRouter spelling — existing Projects pick it up through the models page's explicit "sync presets" action. ([details](2026-08-04-model-catalog-openrouter-qwen38-max.md))
|
||||
@@ -0,0 +1,51 @@
|
||||
PenguinHarness 0.2.1 — the desktop app arrives: the full Web experience as a double-click application for macOS, Windows and Linux, opening already signed in, with installers on the new download page and the OSS mirror.
|
||||
|
||||
## Install
|
||||
|
||||
**Desktop app** (new): grab your platform's installer from [penguin.ooo/download](https://penguin.ooo/download) — served from the OSS mirror when it is reachable, GitHub otherwise. Current builds are unsigned: on first launch use right-click → Open on macOS, and "More info → Run anyway" past Windows SmartScreen.
|
||||
|
||||
CLI / server (Linux, macOS; bundled Node runtime):
|
||||
|
||||
```sh
|
||||
curl -fsSL https://penguin.ooo/install.sh | sh
|
||||
penguin web
|
||||
```
|
||||
|
||||
Windows (PowerShell):
|
||||
|
||||
```powershell
|
||||
irm https://penguin.ooo/install.ps1 | iex
|
||||
penguin web
|
||||
```
|
||||
|
||||
Or via npm (needs Node >= 24):
|
||||
|
||||
```sh
|
||||
npm install -g @prismshadow/penguin-cli
|
||||
```
|
||||
|
||||
## Highlights
|
||||
|
||||
**The desktop app.** A thin Electron shell over the unchanged server and Web App: it forks the server on the shared `~/.penguin/data` root, opens its window already signed in — no terminal, no login page, no initial password — and everything still flows through the same HTTP API (no preload, no node integration). A per-root single-instance lock keeps the schedule scheduler and `web.db` single-writer safe across the shell and CLI alike; if a CLI-started server is already up, the app attaches to it. UI preferences survive restarts via a stable window origin (the shell reuses last launch's port when still free). Installers: macOS dmg (Apple silicon / Intel), Windows NSIS, Linux AppImage / deb.
|
||||
|
||||
**A download page with mirror-aware links.** [penguin.ooo/download](https://penguin.ooo/download) shows one card per platform with your OS badged. Buttons start on GitHub's static `releases/latest/download` links — installers now carry version-less names, which is what makes those links possible — and swap to the OSS mirror's immutable per-tag URLs once the bucket's `latest.json` resolves in the browser, with a manual source toggle and checksum links. Desktop installers are mirrored to OSS alongside the CLI bundles.
|
||||
|
||||
**Download source selection everywhere.** Standalone `install.sh` / `install.ps1` gain the forwarder's OSS-first selection (`PENGUIN_DOWNLOAD_SOURCE=auto|oss|github`) with immutable Release-tag stamping, so a saved versioned installer downloads exactly its matching package forever. `penguin update` adopts the same contract: OSS `latest.json` discovery with a same-tag GitHub fallback in `auto`, strict forced modes, explicit HTTPS mirror precedence, and localized failure messages.
|
||||
|
||||
**Compaction failures fixed — and visible.** Models that mangle the `[summary]` format no longer trap a session in a failing-compaction loop (#170): extraction applies a tolerance ladder, an unusable response retries on the standard backoff budget with a corrective note, and the burned attempts and tokens finally show up in stats and the cost center, with one shared retry-detail shape across the CLI and Web.
|
||||
|
||||
## Notable in this release
|
||||
|
||||
- **Login hardened.** The fixed `penguin-2026` seed admin password is replaced by a random one printed once at first start, and the login endpoint gains per-username exponential throttling.
|
||||
- **Skills manageable in the app.** Agent settings gains a Skills tab (list from disk, uninstall, zip import/export, chat-driven install), agent-list icons deep-link to settings tabs, and the new `skill-porting` library skill brings skills in from other ecosystems; `data-analysis` moves to v2 with a leaner multi-run evaluation flow.
|
||||
- **New chat defaults per Project.** Default agent, working directory, approval mode, thinking level and model, seeded into new drafts.
|
||||
- **A Traces page that scales.** Shared sidebar grouping components, workspace/agent grouping, server-side session paging, and a SQLite-derived mtime-reconciled trace index replacing per-request filesystem walks.
|
||||
- **Chat refinements.** The conversation outline rail windows to ±20 turns and stops overlapping the composer, the cost stat stays put across task boundaries, uploaded file attachments render as user content (and inside steering chips), tool rows drop `[failed]`-style text markers, and ANSI color no longer leaks into tool output.
|
||||
- **Server IO hotspots closed.** The scheduler tick and schedules routes serve from mtime-gated caches, and `GET /messages` gains cursor pagination with tail-first web loading.
|
||||
- **Dependencies.** `hono` bumped past the CORS-preflight ReDoS advisory (GHSA-8j4g-w8fx-2239); OpenRouter catalog gains `qwen/qwen3.8-max`.
|
||||
|
||||
## Requirements
|
||||
|
||||
Linux or macOS (x64 / arm64), or Windows 10+ (x64). The desktop app and the CLI installers bundle their own runtime; installing from npm needs Node >= 24. All data stays under `~/.penguin/data`.
|
||||
|
||||
Full detail: [changelog/0.2.1/](https://github.com/Prism-Shadow/penguin-harness/tree/main/changelog/0.2.1).
|
||||
Reference in new issue
Block a user