* feat(landing): add curated Codex design collection under /plugins
Add a standalone /plugins/codex-design/ collection of the best real,
external skills for doing design with OpenAI Codex (openai/skills,
MengTo/Skills), positioning Open Design as the home for designing with
Codex and driving to the client download + the awesome-codex-design list.
- New collection page + 6 flagship skill detail pages (+ locale wrappers)
- Featured Codex design banner on the /plugins hub
- Editorial content in _lib/codex-design.ts (every entry links to source)
- On-brand illustrations (Figma-selection language) for hub cover, hero,
and each skill
English baseline; per-locale copy layered in a follow-up.
* fix(landing): center Codex design collection + kill oversized section gaps
The collection page rendered full-bleed against the left edge and the
global `section { padding: 130px 0 }` rule blew the vertical gaps wide
open on both the collection and detail pages. Constrain the collection
to the shared 1180px safe area, neutralize the section padding on both,
and de-duplicate the hero eyebrow.
* style(landing): align Codex design collection to the site's editorial card DNA
The collection reinvented a soft, rounded, coral-accented card style that
clashed with the rest of /plugins/*. Rebuild it on the established
system: reuse the tpl-card DNA (1.5px ink border, hard offset hover
shadow, media bleed, mono uppercase labels, dark full-width CTA), the
shared .plugins-* hero primitives, and the ink/bone palette. Move the
skill grid to a global .codex-skill-grid (a scoped rule failed to apply),
fix the cramped detail-page related cards to full tpl-cards, drop the
off-palette coral from UI chrome, and de-duplicate the hero eyebrow.
* style(landing): make Codex design skill grid 4-up
Denser 4-column grid (was 3), matching the plugin hub's explore grid;
detail-page related row stays 3-up. De-dup the hero eyebrow.
* style(landing): regenerate Codex design illustrations brighter and punchier
The first pass read washed-out and samey at card size. Regenerate all
eight (hub cover, hero, six skill covers) with vivid acid-green + coral
accents, higher contrast, and crisp ink outlines so the covers pop in
the 4-up grid and match the site's acid-green accent.
* feat(landing): curated Codex design plugin catalogue (50 entries)
Rebuild the /plugins/codex-design collection from 6 placeholder entries into
50 curated, real, installable design skills and plugins for Codex and other
coding agents.
Each entry carries copy grounded in the upstream SKILL.md, its source repo,
star count, licence, a Codex-compatibility note, and a traction signal
(GitHub stars plus X/YouTube), cross-checked across GitHub, X and YouTube.
- 50 flat /plugins/<slug>/ routes with per-locale wrappers (11 locales)
- 50 covers: 14 upstream first-party showcase images, 36 generated
- collection page gains a category filter; detail card shows stars, traction,
and a bundle note for multi-module entries
- 13 generic sub-command names disambiguated as 'Parent - Subname'
- full 11-locale localisation of UI and all 50 bodies
- README refresh plus new hero for the codex-design repo lives separately
Licence discipline: openai/skills (no root LICENSE), anthropics/skills (no
LICENSE), MengTo demo imagery (third-party sourced) and design-dna imagery
(real third-party sites) were all excluded.
* chore(landing): trigger landing CI (no-op comment)
* fix(landing): address review on the Codex design catalogue
Copilot review follow-ups:
- drop the committed GitHub repo-stats snapshot (data, lib, and fetch
script). It carried an openai/skills row that contradicted the stated
licence discipline, and a checked-in stats file goes stale silently.
The detail card now shows only the star count that lives with the
catalogue entry itself.
- remove the unused sourcePath frontmatter local (dead code).
- drop the .codex-related .codex-skill-grid override; the related row
renders under .dbrelated, so the selector never matched.
- the filter chip bar is a plain filter, not a tab set: role=group
instead of role=tablist, which promised tab semantics the buttons
never implemented.
- fix the stale route in the hallmark page header comment.
- delete the detailForks / detailContributors / detailUpdated i18n keys
that the removed stats card was the only consumer of.
* style(landing): let the source card stat row flow by column
The row now carries a single stat, so a fixed three-track grid was
describing a layout that no longer exists.
* fix(landing): stop tracking the landing-page node_modules symlink
A local symlink named node_modules was committed with the catalogue: the
root ignore rule is `node_modules/`, and a trailing slash matches only
directories, so a symlink of that name slipped past it. It pointed at an
absolute path on the author's machine, so every CI job died in setup —
pnpm cannot mkdir through a dangling link:
ENOENT: no such file or directory, mkdir '.../apps/landing-page/node_modules'
Untrack it and drop the trailing slash so the rule covers a file or
symlink of that name too.
* fix(landing): drop the Figma category and announce the active filter
Review follow-ups on the Codex design catalogue:
- The catalogue has no plugin that reads Figma. The official Figma
skills are excluded on licence grounds, so the sole entry filed under
"Figma to Code" was image-to-code, an image-first workflow that
generates its own reference images and never connects to Figma. The
chip therefore promised "read a real Figma design" and led to a
plugin that cannot do it. Move that entry to Frontend & UI, drop the
category from the union type, the filter, and the copy, and reword the
lede and intro from three jobs to two.
- Announce the filter state: the selected chip lived only in the visual
is-on class, so a screen reader heard plain buttons and could not tell
which filter was active. Initialise aria-pressed and keep it in step
with is-on in the click handler.
- The locale files still advertised the old "6 plugins / 3 workflows"
figures from the six-entry version; correct them to 50 and 13 across
all ten.
* fix(landing): put the catalogue's source-card prose into English
Follow-ups from the second review pass:
- codexNote, social, and bundle are rendered verbatim by the detail
template, but 57 of those values were written in Chinese, so English
and every other locale showed strings like 'README 提 Codex ×8' on the
source card. Translate them in place; the data module is the English
source these pages fall back to.
- The plugin hub banner still sold 'Figma pipelines' in all eleven
locales after the category was dropped. Reword it to match.
- The jobs band allocated three fixed desktop columns and now holds two
entries, leaving an empty third. Flow one equal column per category so
the band tracks whatever CODEX_COLLECTION.categories holds.
* fix(landing): name the catalogue cover links for screen readers
The cover image is decorative (alt=""), so the only text left inside
each card's media link was the category chip: all 37 frontend covers
announced as "Frontend & UI" and all 13 system covers as "Design
Systems", with no way to tell which plugin a link opened. Label each
link with the localized skill name, matching what the shared
template-card already does, on both the collection grid and the related
row of the detail page.
---------
Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
* fix(web): dedupe project workspace tabs on repeated open
The openWorkspaceTab event handler unconditionally appended a new tab on
every project-open event, so reopening the same project stacked duplicate
tabs instead of focusing the existing one. Mirror the dedup logic already
in syncStateToRoute: when a project tab with the same projectId exists,
update its conversationId/fileName/lastActiveAt and activate it rather
than appending.
Fixes#2641
* fix(web): coalesce duplicate project tabs on workspace restore
normalizeTabsState deduplicated entry tabs and tab ids but not project
tabs sharing a projectId, so workspaces persisted before the dedupe fix
restored with their pre-existing duplicate project tabs still visible.
Coalesce project tabs by projectId in normalizeTabsState (keep the active
match, else the newest), mirroring the entry-tab dedupe. This repairs
affected persisted state on restore, not just new corruption.
Adds a restore regression with two distinct tab ids for the same project.
Follow-up to #2641 / review feedback on #5993
buildBlocks() in AssistantMessage filters a fixed list of internal status
labels before they reach StatusPill, but tool_call / tool_call_update are
not in it, so they fall through and render as raw, expandable status pills
with no tool name, input, output, or detail — looking like "a tool ran but
produced no output".
These are transient ACP markers: on the live SSE path the daemon already
normalizes them to `running` (TRANSIENT_ACP_STATUS_LABELS in
providers/daemon.ts), which buildBlocks() skips. The persisted-events path
does not normalize, so the bare labels survive into the UI. Add both labels
to the existing skip list, with a comment pointing at the daemon source of
truth.
Add a red-spec test asserting the two empty status rows render no pill, plus
a guard that status rows carrying a real detail still render.
* fix(message-center): honor active AMR profile for anonymous sync
* fix(vela): keep control context on one config snapshot
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* feat: support Codex service tiers
* fix: forward service tiers through MCP runs
* fix: clear default service tier selections
* fix: preserve service tier on model option updates
* fix: preserve service tier without explicit model
* fix: preserve service tier in connection tests
* test: stabilize API empty stream UI check
* fix: preserve codex service tier labels
* fix: preserve service tier without saved model
* fix: address service tier review follow-ups
* fix: pin legacy chat assistant rows before start
* fix: accept bare model catalog responses
`stable_prompt_cache_miss_reason='stable-prompt-changed'` says THAT the
cached instruction prefix moved between turns, never WHICH input moved.
Diagnosing a drift has meant pairing Langfuse traces by hand and eyeballing
two multi-KB prompts for a diff, so the dominant bucket in production --
4,673 of 6,612 AMR drift events over 14 days, on later-resume turns -- has
no explanation telemetry can give.
Hash each named input of the prefix per turn, store the map alongside the
existing hash on agent_sessions, and diff it when the hash no longer
matches. `run_finished` gains `stable_prompt_changed_sections`; the Langfuse
turn gains the same field.
Attribute over the composer's INPUTS rather than by slicing the composed
prompt on its `---` separators: the question a drift has to answer is what
CAUSED the prefix to move, and the cause is always an input -- splitting the
output would report which bytes differ but still leave the mapping back to a
cause to a human. It also keeps prompt assembly untouched.
stablePromptHash stays the only source of truth for hit/miss; section hashes
never gate a re-send, so a section-table bug can mislabel a drift but never
suppress or fabricate one. A drift no tracked section explains reports
`unattributed` -- a coverage-gap alarm rather than a silently clean turn,
which is what keeps the table honest as new inputs enter the prefix.
The composeSystemPrompt argument literal is hoisted to a const and passed
unchanged, so one object both composes the prompt and feeds attribution
instead of a second hand-maintained copy that would drift from the real
inputs and mislabel the telemetry it exists to explain. No prompt bytes
change.
Installer-managed processes can respawn while NSIS replaces the active executable. Quarantine the launch path and preserve the previous installation until system registration succeeds.
Co-authored-by: Kirill Turanskiy <thebtf@users.noreply.github.com>
* give the unattributable client failures a real error code
Three buckets in the daily stability report name nothing, and in every case the
evidence was already in scope at the emission site:
- BYOK connection test: 801 failures across 201 devices in 24h — 10% of ALL
connection tests — reported `result.kind` verbatim, so every response the
daemon's status→kind map had no case for collapsed into `unknown`. The user
is told "connection failed" and we cannot say whether it was an out-of-credits
402, a self-signed cert, or a base URL pointing at an HTML login portal.
`ConnectionTestResponse` already carried `status` and a redacted `detail`;
only the emission site threw them away.
- Frontend exceptions: the Top-5 table literally renders a row as
`Error — 235 次 / ~74 台`. Two independent sources — `buildExceptionList`
copied `error.name`/`error.message` straight through and both can be '', and
`parseStack` returned a zero-frame stacktrace for a stackless error, which
PostHog drops at ingestion so neither `$exception_type` nor
`$exception_value` survives.
- Export failures (8.1%, 210/24h): everything that was not a sidecar-skew
message fell through to `err.name`, i.e. the literal "Error".
The daemon side of the same path flattened every sidecar contract violation to
`UPSTREAM_UNAVAILABLE`, which forced the web layer to regex-match English prose
to tell a version-skewed mesh from a dead renderer; it now forwards the
sidecar's real code, and the web classifier treats the remaining generic
envelope codes as non-classifications so a specific message still wins.
Two existing export-code expectations changed on purpose and say why inline: a
generic envelope `.code` no longer outranks message classification, and a
message carrying an HTTP status now reports that status instead of "Error".
* keep the forwarded sidecar code inside ApiErrorCode
Follow-up on the same PR. Forwarding the sidecar contract violation's `code`
compiled, but it was leaving the declared union: `ctx.http.sendApiError` is
typed `(...args: any[]) => any` in server-context.ts, so nothing caught that
`SIDECAR_UNKNOWN_MESSAGE` is not an `ApiErrorCode`. Every web-side handler
typed against that union would have failed to match the code the daemon
actually put on the wire.
Declare the two SIDECAR_* codes in API_ERROR_CODES and narrow the helper's
return type to ApiErrorCode, allowlisting exactly those two so a future sidecar
code cannot silently leave the union again.
No consumer of the export 502 switches on the old code — the UPSTREAM_UNAVAILABLE
call sites are finalize (useFinalizeProject), AMR run guidance, and ChatPane run
errors, none of which are on this route — so this is additive, not a behavior
change for anything that exists today.
* make the attribution actually fire on the production paths
Three review findings, all correct, all verified against the real code paths
rather than the synthetic ones the first cut tested.
1. The sidecar code passthrough was dead. `requestJsonIpc` rejects with
`new Error(response.error?.message)` — its response type does not even
declare a `code` — so SIDECAR_UNKNOWN_MESSAGE never reaches the route, and
the helper always fell back to UPSTREAM_UNAVAILABLE. Removed the passthrough
(and the two API_ERROR_CODES entries added for it: nothing emits them). The
message-based classifier already achieves the attribution and is tested;
dead code that reads as working is worse than no code. Threading the code
through IPC → API → web export result is a three-layer change and belongs in
its own PR.
2. The zero-frame guard checked the input instead of the output. A header-only
stack ("Error: Script error.") is truthy, so it skipped the fallback, and the
parser then dropped that single line as a message — yielding `frames: []`,
the exact ingestion failure this change exists to prevent. The guard now runs
on the PARSED result, covering missing / empty / header-only / unparseable
stacks, with a fixture matrix over all four.
3. The certificate bucket could not fire in production. undici reports a TLS
failure as a TypeError whose message is the generic `fetch failed`, with the
reason only on `cause.code`, and `networkErrorToKind` maps just an allowlist
— so a self-signed cert became `kind: unknown` + `detail: "fetch failed"`.
The unit test passed only because it was fed a detail string production never
emits. The daemon now appends the cause code to `detail`, and the client
tests assert against that real shape.
* stop a 2xx status from claiming a failure it cannot explain
Review catch, and it made this PR's headline case unreachable. The status
fallback ran before every detail classifier, so the daemon's
`{ kind: 'unknown', status: 200, detail: 'Unexpected token < …' }` — which is
exactly the 'base URL points at an HTML login portal' shape, returned when a 2xx
body fails JSON.parse (connectionTest.ts) — was reported as HTTP_200, and
INVALID_JSON_RESPONSE could never fire.
Reproduced before fixing:
byokErrorCode({kind:'unknown',status:200,detail:'Unexpected token < …'})
→ 'HTTP_200'
The cut is now at 300 rather than 0. A 2xx that still failed did so for a reason
the status cannot express, so it falls through to the detail classifiers; 3xx
stays on the status side because the daemon fetches with `redirect: 'error'`,
making a redirect a real failure the status does name.
* read the daemon-appended cause code instead of enumerating codes
Review catch on a gap my own previous commit opened: forwarding cause.code from
the daemon put OpenSSL's TLS codes on the wire, and this file could not read
them. UNABLE_TO_GET_ISSUER_CERT_LOCALLY, CERT_NOT_YET_VALID and CERT_HAS_EXPIRED
have no Node-errno shape, and the `\bcertificate\b` fallback never matches a
name that spells it CERT — so the signal was preserved on the wire and dropped
here, landing back in UNKNOWN_NO_SIGNAL.
Read the daemon's known trailing `(CODE)` position directly rather than
extending an allowlist: an enumeration would silently drop every code added
upstream after this file was written, which is the same failure mode one layer
down. Inline codes keep a widened pattern, and the TLS fallback now also matches
CERT-shaped names.
Fixtures cover all seven codes the daemon can now forward.
* name every packaged startup failure instead of bucketing it as unknown
The weekly `packaged_runtime_failed` alert is dominated by failures the event
had enough evidence to name and reported as opaque anyway. Triaging a 7d window
turned up three places where the emitter threw away a cause it was already
holding:
- 62 devices died with the daemon log tail read successfully, but
`parseDaemonLogTail` only ever matched `ERR_*`. A daemon that threw anything
else (SQLite corruption, EPERM, a plain throw) reported error_code=null AND
missing_module=null with its reason printed in the tail we had just read.
- 21 devices hit win32 CreateProcess failures. Node puts the real cause on the
error's `code`/`errno`/`syscall`; the event sent `.message` only, so
`spawn UNKNOWN` was indistinguishable from any other opaque failure.
- 149 devices produced an Error with an empty `.message` and no `.stack`. Every
free-form field went null, leaving residue that could not even be counted
apart from "no error at all".
So: parse the sidecar's own fatal line as a backstop when `ERR_*` misses, carry
the errno triplet off the thrown object, classify a rejected spawn as its own
`spawn-failed` kind rather than `unknown`, and guarantee a thrown error never
serializes to error_message=null — an unnamed leftover is now a named, countable
bucket.
Classification-only; the failures themselves are unchanged. Every test input is
a verbatim field payload from the alert window.
* reduce syscall to its operation token before sending it
Review catch, and a real leak. Node does not guarantee `syscall` holds only the
operation name: spawning a missing binary sets it to the whole invocation.
Verified against real Node —
spawn('/Users/elian/nonexistent/bin/thing')
→ syscall === 'spawn /Users/elian/nonexistent/bin/thing'
Forwarding that verbatim would have put the local username and install path
into an event that is retained even for opted-out users, while every other path
this module sends goes through scrubUserPaths.
Keep the first token — the operation is the whole analytic value, and the path
is already available (scrubbed) via error_message — then scrub what survives
and cap it, as a second line of defence. `code` is capped too.
This also fixes a classification miss the same review implies: the spawn-failed
branch compared the RAW syscall to 'spawn', so a spawn error carrying a path
never matched and fell through to `unknown` — the exact bucket this PR exists
to drain.
* fix: preserve dark-first brand canvases in derived themes
A brand extracted with an explicitly dark background role and a light
foreground role previously rendered as a white kit: seedFromBrand
clamped any background below 0.72 luminance to #ffffff, and
deriveTokens re-clamped the seed and hardcoded #141414 for dark mode.
Every dark-first source (near-black app UIs, dark portfolio sites)
produced a default theme that looked nothing like the brand, and
re-running finalize reverted agent-made dark rethemes.
seedFromBrand now preserves the true canvas when the brand carries a
confidently dark background (or surface) plus a light foreground, and
deriveTokens honors a dark seed across all three algorithms: the
default theme keeps the brand canvas with dark palette math, and the
dark algorithm uses the brand's own near-black instead of the generic
fallback. Ambiguous brands (mid-gray canvas, missing roles) keep the
light baseline unchanged, and the programmatic URL fast pass
(seedFromMaterial) intentionally stays light.
* fix: align exported theme.json algorithm with dark-first tokens
Review follow-up: the derived tokens, variables.css, and kit.html use dark
palette + neutral math for a dark-first seed, but both theme.json producers
still serialized tokensToThemeJson(seed, 'default'). An Ant ConfigProvider
consumer would then apply the light algorithm to the dark canvas, diverging
from the rest of the generated system.
Add defaultThemeAlgorithm(seed) as the single shared decision (dark-first
-> 'dark', light/ambiguous -> 'default'), exported from the engine and used
by both the build.ts and system.ts (finalize) theme.json paths. Assert the
exported algorithm for both the dark-first and ambiguous cases so the
ConfigProvider artifact can't drift from the kit/CSS again.
* fix: gate dark-first predicate on dark bg AND light fg
Second review follow-up (@nettee): seedPrefersDark() keyed only off
colorBgBase, but neutralBases() (the extraction-path gate) requires a dark
background AND a light foreground. The rebuildSystem seed-override path
(sanitizeSeedOverrides) can merge a background-only override like
{ colorBgBase: '#050505' } onto an otherwise light brand, which flipped
deriveTokens to the dark ladder (forcing white text) while
tokensToThemeJson still serialized the seed's dark colorTextBase — the same
ConfigProvider mismatch under overrides instead of extraction.
Make seedPrefersDark mirror neutralBases exactly (dark <= 0.3 AND light
>= 0.6) so a background-only override stays on the light/default path
consistently across deriveTokens, defaultThemeAlgorithm, and theme.json.
Add a regression locking the override seed both ways (bg-only -> default,
bg+light-fg -> dark).
---------
Co-authored-by: wiggdevin <wiggdevin@gmail.com>
* fix(i18n): localize BYOK provider titles to English
Five BYOK provider titles in apps/web/src/state/config.ts were hardcoded in
Chinese while the rest of the provider list used English-facing names:
qwen 千问 -> Qwen
volcengine 火山引擎 -> Volcengine Ark
qianfan 百度千帆 -> Baidu Qianfan
mimo 小米 MiMo -> Xiaomi MiMo
zhipu 智谱 -> Zhipu AI
These were rendered into the Settings > Execution mode > BYOK provider chip
list verbatim, producing a mixed-language list when the UI was set to
English (issue #5693). The providerLabel for each was already in English;
only the user-facing title was inconsistent.
The new titles mirror the providerLabel phrasing where the existing
providerLabel read naturally as a standalone name (Qwen, Volcengine Ark,
Baidu Qianfan, Zhipu AI) and use a friendly "Xiaomi MiMo" for mimo where
the providerLabel carried extra "OpenAI" suffix not appropriate for a chip
title.
No i18n catalog entries needed — these were never translated, the title
field is the display string.
Fixes#5693
* fix tests: update BYOK provider tab labels to English
Two test assertions in apps/web/tests/components/SettingsDialog.execution.test.tsx
hardcoded the old Chinese tab names that were just localized to English in
the BYOK_PROVIDER_PRESET_SPECS array (commit 13037c5):
- "小米 MiMo" -> "Xiaomi MiMo" (line 1190)
- "智谱" -> "Zhipu AI" (line 1628)
Without these test updates the Web workspace tests job fails because the
tab element with the old Chinese accessible name no longer exists — the
config change renamed the chip title and the tab role name derives from it.
* fix(web): lift home context picker secondary text to a legible token (#4468)
The home context picker (the @-mention surface above the prompt) drew its
secondary text — per-tab counts, section labels, option meta, and the
hover-card kicker — in `--text-faint`. In dark mode that token (#4e4b46)
sits at ~1.8:1 against the picker panel, far below WCAG AA, so the counts
and category labels read as illegible bleed-through.
Move those four selectors to `--text-muted`, matching the option
description that already uses it. The picker's secondary tier now clears
AA in dark mode (~5.3:1) and stays consistent across the tabs and list;
light mode improves from ~2:1 toward AA on the same selectors.
Adds a stylesheet-level contrast guard that resolves each selector's
color token and asserts WCAG AA against the surface it renders on.
* fix(web): extend picker contrast lift to the hover-card meta row (#4468)
Mirrors vela's TRIAL_CREDIT_PROMO_ENABLED (powerformer/vela#912): the
new-user signup trial-credit promotion is temporarily offline, so the
/pricing/ Free card must stop advertising it while keeping every string
in place for relaunch.
While the switch is false:
- the Free card drops its trial-credit benefit row and both hosted-model
lists (free hosted-model access, incl. the Grok trial pool, only exists
through the signup grant)
- the Free tagline swaps to a no-promo variant (10 locales)
- the FAQ drops the "what is the limited-time trial credit" entry and
swaps the plans-differ answer to a trial-free variant (10 locales)
Flipping the switch back to true restores all promo copy untouched.
Claude-Session: https://claude.ai/code/session_01SaoLc5kR92BffzDP8acfrA
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
* feat(daemon): route completed-run telemetry through Vela
* fix(daemon): migrate test telemetry relay domain
* feat(daemon): send Vela feedback scores with completed-run telemetry
Map score-create into the Vela envelope and use the same Vela sink for
thumbs feedback, binding scores via the client run id instead of skipping
them when account-aware telemetry is selected.
* fix(daemon): skip anonymous fallback for Vela feedback auth errors
Score-only feedback batches must stay on the account-scoped Vela path.
When Vela returns 401/403, fail the delivery instead of posting to the
anonymous relay, which has no matching trace for those scores.
Generated-By: looper 0.11.0-beta.1 (runner=fixer, agent=grok-build)
* fix(daemon): normalize Anthropic-compatible BYOK base URLs
* test(e2e): stabilize home plugin details flow
Wait for Home project loading to settle before revealing templates, then target the semantic Details control after verifying actionability so layout shifts cannot collapse the gallery or trigger the wrong card action.
Generated-By: looper 0.10.4 (runner=fixer, agent=codex)
* fix(daemon): version Anthropic gateway URL paths
Append /v1 through the parsed URL pathname so gateway query parameters and fragments remain in their proper URL components. Preserve the prior string-concatenation fallback for invalid URLs.
Generated-By: looper 0.10.4 (runner=fixer, agent=codex)
* reconcile run terminals after restart
* fix(daemon): preserve replayed failure taxonomy
Reclassify analytics-incomplete terminal failures from their persisted state and events while keeping the interrupted template exclusive to daemon-restart recovery.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* fix(daemon): preserve repaired errors and skip completed logs
The repair path previously collapsed every failed message to restart copy and hydrated event logs before checking completed telemetry checkpoints. Use durable recovery metadata for status detail and gate log reads on outstanding PostHog or Langfuse work.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* fix(daemon): bound macOS watcher file descriptors
* fix(daemon): preserve symlink roots and refresh Nix hashes
Keep the watch-root exemption ahead of the symlink traversal guard and lock the behavior with a real directory-symlink assertion. Refresh the daemon and web pnpm dependency hashes reported by the failing Nix build.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* fix(landing-page): keep locale variants of proper-noun blog posts out of the index
The marp / slidev / reveal.js posts each rank for a single navigational
product-name query. Those queries are language-neutral, so Google ranks all
ten translated copies against the same English SERP. Over 2026-07-01..07-18
the thirty localized URLs took ~96k impressions and returned 19 clicks — a
0.02% CTR — while accounting for the entire site-wide impression surge that
pushed average CTR from 14% down to 3.7%. The Spanish copies were outranking
the English originals (/es/blog/slidev/ 27,465 impressions vs 3,431).
Add an opt-in `noindexLocaleVariants` frontmatter flag. When set, the
localized variants emit `noindex, follow` (outbound equity still flows) and
the hreflang cluster is dropped from every variant including English, since
hreflang must not advertise noindexed URLs. English stays indexable.
SeoHead grows a page-level `noindex` prop, kept distinct from the existing
site-wide staging flag: staging continues to emit `noindex, nofollow` for the
whole mirror, verified unchanged by an OD_LANDING_NOINDEX=1 build.
* refactor(landing-page): give the hreflang suppression its own prop
`localeCanonicalOnly` is documented as "this page has no per-locale
variants" and also drives the language switcher and locale auto-redirect in
sub-page-layout. These posts do have locale variants, all thirty are built
and remain reachable through the switcher; only their hreflang advertisement
has to go. Borrowing the flag happened to produce the right HTML today
because SeoHead ignores the switcher half of its meaning, which is exactly
the kind of coincidence that breaks on the next refactor.
Split out `noindexedLocaleVariants`. Output is byte-identical: 30 variants
still emit `noindex, follow`, the three English posts still carry no robots
meta and zero non-x-default hreflang, the control post keeps its 11.
---------
Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
* fix(daemon): keep canceled runs canceled after late agent errors
* fix(daemon): preserve failures that precede cancellation
Record whether stream and ACP failures were observed before a cancel request, and let close-time classification win over the cancel fallback in that ordering. Add a startup smoke case that posts cancel only after the provider error is visible.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* feat(plugins): add Humanize PPT community plugin
Ports LearnPrompt/humanize-ppt (MIT) into plugins/community/ following
the community/hallmark precedent, and registers it in the community
marketplace index (0.2.0 -> 0.2.1).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugins): harden humanize-ppt per review — out-dir guard, PPTX fail-closed, packaging (v1.1.1)
- brief mode no longer wipes a non-empty --out unless it carries Humanize
run markers or --force is passed; wipe semantics documented in SKILL.md
and docs/local-demo.md
- read_source fails closed on .ppt/.pptx with guidance (extract text
first; rendered decks go to --qa-from); SKILL.md/SPEC.md wording aligned
- vendor examples/01-ai-tool-update/source.md, inline-fixture fallback in
smoke_check.py, drop repo-only record_demo_gif.py, point dangling doc
links at the upstream repo
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugins): humanize-ppt review follow-ups — outline-gate markers, shim exit codes (v1.1.2)
- HUMANIZE_OUT_MARKERS now includes outline-preview.md and
preview-confirmed.json, so the documented --preview-outline ->
--confirm-outline -> brief flow on one --out is no longer refused by
the out-dir guard; refusal text, --out help, SKILL.md/SPEC.md/
docs/local-demo.md list all four markers
- humanize_ppt_v3/v4/v5 shims call sys.exit(main()) like the stable
entrypoint, so validation failures propagate exit 2 instead of 0
- smoke_check.py gains an outline-gate check (preview -> confirm ->
brief on one --out, plus the unrelated-out refusal negative path) and
an exit-code matrix across all five public entrypoints
- plugin 1.1.2, community registry 0.2.3
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix release: notarize prerelease mac builds
* fix(release): inject Apple notary env on prerelease mac retry steps
The cache-miss retry steps for both mac_arm64 and mac_x64 prerelease
builds pass --notarize but lacked the APPLE_ID /
APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID env block present on the
primary build steps. notarize.cjs fails closed when those vars are
missing, so a recovery build could not produce a notarized candidate.
Mirror the primary step env on both retry steps and assert the notary
env appears twice per mac job in release-workflows.test.ts.
Generated-By: looper 0.10.3 (runner=fixer, agent=claude-code)
---------
Co-authored-by: Looper <looper@noreply.github.com>
* feat(daemon): E-lite root-cause telemetry for inactivity_timeout attribution
Add four run_finished fields that separate WHY a stalled run died, which
the coarse `last_observed_phase` (WHICH phase) cannot:
- approval_requested: an ACP approval/permission gate fired. Only the ACP
path is daemon-observable (stream/CLI runtimes pass a skip-permissions
flag so no gate fires); surfaced via an acp_approval_request diagnostic
and folded in by summarizeRunDiagnosticsForAnalytics.
- stdin_backpressure: the prompt write to the child's stdin was queued
because the OS pipe buffer was full (the child was not draining stdin) —
the corroborating signal for a stdin_write-phase stall.
- tool_result_sent: a tool_result came back for the run's LAST tool_use.
A stall with tool_call_seen && !tool_result_sent is the
tool-result-not-delivered root cause.
- last_progress_age_ms: age of the last agent activity at finish (the
inactivity-watchdog clock). Near the ceiling on a stall, ~0 on a clean
finish.
Contract fields on RunFinishedProps; daemon populates two off the run
object (stdin_backpressure, last_progress_age_ms) and derives two from
run.events in the diagnostics summarizer (tool_result_sent,
approval_requested).
* fix(daemon): pair tool_result_sent by id so parallel tools attribute correctly
Review catch (nettee/Looper): the first cut flipped `tool_result_sent` to
true on any tool_result after a tool_use, so a multi-tool turn like
tool_use(A), tool_use(B), tool_result(A) reported delivered even though B
was still outstanding — bucketing multi-tool stalls as "provider stalled
after delivery" instead of "tool result not delivered".
Now paired by id (`tool_use.id` <-> `tool_result.toolUseId`, the same
pairing summarizeRunTimingAnalytics uses): `tool_result_sent` is true only
when EVERY committed tool_use received a matching tool_result (outstanding
set empty). Tests cover the reviewer's regression case plus parallel
all-resolved and sequential-last-outstanding.
* fix(daemon): count-pair id-less tool events so degraded stalls aren't "delivered"
Second review catch (nettee/Looper): id-based pairing alone still fell
through to `tool_result_sent: true` when a runtime emits a tool_use with a
null id, because those were counted in `sawAnyToolUse` but never tracked as
outstanding. That is not hypothetical — `agent-protocol/pi-rpc/events.ts`
and `copilot-stream.ts` both emit `toolCallId ?? null` for tool_use.id AND
tool_result.toolUseId on degraded provider events, so an unpaired id-less
tool call would mask exactly the stall this metric attributes.
Id-less tool events are now paired by count alongside the id-keyed set:
`tool_result_sent` requires both an empty outstanding-id set and at least as
many id-less results as id-less uses. Tests cover an unpaired id-less stall
and an id-less pair that did resolve.
* fix(daemon): capture stdin backpressure on the default text-input path
Third review catch (nettee/Looper): `stdin_backpressure` never recorded
anything for non-`stream-json` runtimes. `child.stdin.end(composed, ...)`
returns the stream rather than a boolean, and `writableNeedDrain` is already
back to false by the time it returns — even for a chunk that `write(chunk)`
would have rejected. Claude is the only runtime on the stream-json path, so
the field was permanently false on exactly the runs whose `stdin_write`
stalls it exists to attribute.
The write and the close are now issued separately through
`writePromptAndEndStdin`, which returns the boolean the OS pipe actually
gave us. Extracted into chat-run-lifecycle.ts so it is unit-testable:
tests cover the backpressured write, the accepted write, and the flush
callback still firing (the `stdin_write_end` lifecycle mark). Verified red
against the previous behavior.
* Classify AVX2 illegal-instruction agent crashes as cpu_unsupported and stop retrying them
The Windows opencode.exe bundled with the AMR runtime is a Bun build that
requires AVX2. On CPUs without it (Intel Atom/Celeron/Pentium N-series
through 2021) the binary dies with STATUS_ILLEGAL_INSTRUCTION before
readiness; vela surfaces an ACP fatal, the daemon classified it as a
retryable fatal_rpc_error, and the automatic retry deterministically
failed again. 88 of 105 pre-readiness Windows crash traces in Langfuse
are this single failure.
The crash text (Bun 'Illegal instruction' banner with a no_avx2 CPU
feature line, or the raw 0xC000001D / decimal 3221225501 exit status) now
classifies as process_exit/cpu_unsupported ahead of the fatal_rpc_error
close-reason promotion, is never auto-retried, and renders a dedicated
error card telling the user to update Open Design instead of a dead
Retry button.
* Bump @powerformer/vela-cli to 0.0.23 so packaged Windows builds bundle the baseline (non-AVX2) OpenCode
The 0.0.23 win32-x64 platform package compiles the OpenCode fork with
--target=windows-x64-baseline, so the bundled opencode.exe no longer
requires AVX2 and starts on Intel Atom/Celeron/Pentium N-series CPUs.
Verified in vela CI: the build log shows the baseline target and the new
post-publish smoke job boots the published bundle's opencode.exe on a
real Windows runner (powerformer/vela#838).
* Refresh Nix pnpm deps hashes for the vela-cli 0.0.23 lockfile change
* Add the abort-after-panic (exit status 3) production shape to cpu_unsupported specs
A user on an AVX-but-no-AVX2 CPU (Sandy/Ivy Bridge era) hit the same
illegal-instruction crash, but Bun panicked during its own panic handler
and abort()ed, so vela reported exit status 3 instead of
STATUS_ILLEGAL_INSTRUCTION. The stderr banner (no_avx2 / Illegal
instruction) is the only signal; encode the real Langfuse trace shape so
the classifier keeps catching it.
* Bump @powerformer/vela-cli to 0.0.25 so packaged Windows builds bundle the baseline (non-AVX2) OpenCode
0.0.25 is the first stable cut after powerformer/vela#838 merged: its
win32-x64 package compiles the OpenCode fork with
--target=windows-x64-baseline (verified in the release CI log), the
post-publish smoke boots the published bundle on a real Windows runner,
and the lockfile regenerator now pins the baseline asset so a routine
version bump cannot silently flip back. 0.0.24 predated that merge and
still shipped the AVX2 build.
* Tighten cpu_unsupported to precise signals only (no bare 'Illegal instruction')
Review is right that a bare illegal-instruction line is too broad: an
unrelated SIGILL on an AVX2-capable machine (runtime bug, corrupted
jump) would be mislabeled 'Processor not supported' and lose its retry.
Bun's crash banner always prints the CPU-feature line, so machines
actually missing AVX2 are identified by no_avx2; the bare Windows
STATUS_ILLEGAL_INSTRUCTION renderings (0xC000001D / 3221225501) stay
because those runs carry no banner at all. Generic illegal-instruction
banners without no_avx2 return to process_crashed / fatal_rpc_error,
locked by a new negative spec.
* Refresh Nix pnpm deps hashes for the vela-cli 0.0.25 lockfile change
* Gate raw STATUS_ILLEGAL_INSTRUCTION on the bundled-opencode startup context
Every AppImage launch mounts a fresh FUSE squashfs whose VFS page cache
is cold, so the daemon sidecar demand-pages its 124 MB bundled node
binary (plus dist and the ~70 MB bundled plugins registry) through FUSE
on EVERY launch while Electron faults through the same mount. The
daemon could not bind its IPC socket within the 35s DAEMON_STATUS_TIMEOUT_MS
and every launch failed with a sidecar status timeout (white screen /
recovery error).
- Add apps/packaged/src/prewarm.ts: best-effort sequential read of the
sidecar cold-start set into the page cache before spawning, gated to
linux + FUSE-backed payloads (plain-file installs skip). Sequential
reads measure ~45 MB/s through squashfuse vs ~5 MB/s demand-paged
under launch contention; daemon ready drops from ~25-60s+ to ~3-19s.
- Widen the linux status-wait baseline to 90s as a safety net for slow
devices, mirroring the win32 rationale (#5515): same "slow, not
dead" failure class.
Fixes#5835