Commit Graph
315 Commits
Author SHA1 Message Date
Ray XiandGitHub 068c9ae83f fix(daemon): normalize Anthropic-compatible BYOK base URLs (#5807)
* 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)
2026-07-21 10:17:14 +00:00
421ac5ad56 fix(web): keep model picker anchored within visible bounds (#5907)
Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com>
2026-07-21 08:02:51 +00:00
d8b6b797fe Simplify chat execution disclosures (#5667)
* Simplify chat execution disclosures

* test: cover collapsed file disclosures

* refactor(web): consolidate chat task activity

* fix(web): show small file outputs inline

* fix(web): separate file outputs from reads

* fix(web): collapse completed todo cards

* refactor(web): compact completed todo lists

* fix(web): make completed todos expandable

* fix(web): keep four output files visible

* refactor(web): clarify chat activity timeline

* fix(web): make file output disclosure interactive

* feat(web): emphasize chat run states

* style(web): simplify chat run status colors

* style(web): remove chat run status icons

* feat(web): stream one chat activity at a time

* style(web): align chat completion row

* fix(web): simplify priority chat states

* feat(web): simplify secondary chat details

* style(web): simplify secondary chat hierarchy

* fix(web): keep execution state out of artifacts

* fix(web): use icons for artifact operations

* style(web): frame todo disclosure header

* fix(web): clarify artifact and tool semantics

* fix(web): clarify chat disclosure hierarchy

* fix(web): keep execution history neutral

* fix(web): simplify user action cards

* fix(web): align failure card actions

* fix(web): refine chat action hierarchy

* fix(web): address chat disclosure review feedback

* fix(web): restore chat recovery actions

* fix(web): make collapsed action details inert

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
2026-07-21 07:44:52 +00:00
PerishFireandGitHub 20c6c0a6ed ci(e2e): shard the full UI functional pool into a merge-ready matrix (#5883)
* ci: benchmark deduplicated UI merge sentinel

* ci: benchmark restoration-hosted sentinel

* ci: benchmark distributed P0 sentinel

* ci: benchmark split P0 sentinel

* ci: serialize split sentinel benchmark

* ci: finalize deduplicated UI merge topology

* ci: prototype all-shades UI domain topology

* ci: bound full UI runtime by shade

* test(e2e): stabilize full UI suite

* fix(web): unclip DS picker popover and propagate patched project metadata

Two regressions surfaced by re-enabling the full UI pool:

- The New Project design-system popover lost its portal and was clipped by
  short viewports and the modal body. Render it through createPortal with a
  viewport-aware anchor that flips upward when space below runs out.

- After a working-dir pick, onProjectMetadataChange forwarded only the
  metadata onto a stale project copy, so ProjectView.currentProject's
  recency comparison let an older useProjectDetail snapshot shadow the
  accepted PATCH and the picker label never updated. The callback now
  carries the daemon's authoritative post-patch project (fresh updatedAt
  included), matching the design-system switch path, and ProjectView hands
  it straight to onProjectChange.

* test(e2e): repair remaining stale full-suite UI contracts

Second repair batch for the failures surfaced by full diagnostic run
29480362163, plus one CI-invisible case that was serially skipped behind
the BYOK failure in the same file:

- Point the working-dir metadata spec at a directory that exists on the
  runner instead of a hardcoded absolute path, and assert the label from
  the picked path (this spec is red without the ProjectView patched-project
  fix).
- Navigate back to All project files before asserting the plugin-folder
  cards, since a finished run auto-opens the produced file tab.
- Resolve the create-run response race in real-daemon-run's sendPrompt via
  a single waitForResponse alongside the click.
- Refresh selectors and copy for entry topbar, home hero rail, chat todo
  autoscroll, project management, workspace keyboard, design files, manual
  edit, restoration, and DS picker clipping flows to match the current UI
  contracts while keeping each spec's oracles.

* ci: distribute the full UI suite over a generic 8-shard matrix

Replace the diagnostic four-domain P0/P1/P2 serial topology with a single
all-shades Playwright invocation per shard (--shard=N/8, two fully
parallel workers, 20-minute cap) and drop the scheduled trigger; full runs
stay manual until the merge-queue wiring lands. Update the workflow
topology guard accordingly.

* ci: serialize full UI shards and widen the matrix to twelve

The eight-shard two-worker experiment left six light shards healthy
(260-477s test steps) but both shards holding runtime-heavy files
degraded: repeated two-minute timeouts with retry recovery in one, and a
consistent create-run response starvation failure in the other. This
matches the earlier finding that runtime-heavy scenarios cannot share a
runner under two fully parallel workers. Run each shard with a single
worker and rebalance to twelve shards; fully-parallel stays on only for
test-granularity sharding.

* fix(web): preserve prompt-extracted plugin inputs across apply reconcile

The active-plugin apply reconcile rebuilt the inputs from the bind-time
optimistic snapshot, so edits the user typed during the apply roundtrip
(already extracted into active.inputs by handlePromptChange) were silently
reverted to defaults — the submitted pluginInputs then disagreed with the
visible prompt. Merge the apply result's defaults into the current inputs
inside the functional updater instead, mirroring the guard the prompt-text
reconcile already had.

* test(e2e): stabilize home gallery flows for all-shades execution

Generic all-shades sharding executes tests in orders the shade-filtered
domain topology never produced, exposing a home-surface remount window:
the async projects fetch can mount or remount HomeTemplatesReveal after a
test starts interacting, collapsing the gallery (inert) under the test's
cursor. Stabilize the shared helpers instead of individual specs:

- gotoEntryHome arms a projects-list response waiter before navigating and
  holds until it settles.
- revealHomeTemplates requires the revealed state (or a true pass-through)
  to survive a settle window, retrying when the wrapper mounts late; a
  card's isVisible() is true inside the collapsed container, so
  openHomePluginDetails now always routes through the reveal path.
- The browse-registry spec reveals before clicking.
- The DS-picker options moved out of the new-project-modal subtree with the
  portal fix; query the page-level popover per the clipping spec's
  convention.
- real-daemon-run's sendPrompt reports on timeout whether POST /api/runs
  was ever issued, so the CI-only BYOK failure can be split into
  swallowed-click vs unanswered-request on the next reproduction.

* test(e2e): make BYOK availability hermetic and reveal remaining gallery clicks

The BYOK missing-provider spec could only pass on machines with a real
`opencode` binary: daemon detection maps byok-opencode's availability
through the `opencode` agent's configured cli env, and the web composer
refuses to POST /api/runs for an unavailable agent (the sendPrompt
diagnostic confirmed "was never issued" on CI). Point the helper's
agentCliEnv at the fake opencode runtime — the run still fails pre-spawn
on the missing provider config, so the fake binary never executes.

Also route the two remaining collapsed-container gallery clicks (the
browse-registry starter journey and the detail-use force-click) through
revealHomeTemplates; an inert container swallows force-clicks silently.

* ci: probe a 24-shard full UI matrix against the four-minute wall target

The first green 12-shard run put eleven shards at 141-354s test steps but
concentrated the contiguous runtime-heavy chain in one 894s shard, setting
a ~16-minute wall tail. With ~3568 total test-seconds and ~90s per-job
overhead, 24 shards should land every job near four minutes.

* ci: settle the full UI matrix on twelve shards

The 24-shard probe stayed green but proved the runtime-heavy chain is a
serial group (test.describe.configure serial in real-daemon-run), which
Playwright keeps atomic within one shard: the tail stayed ~16 minutes while
the extra twelve cold starts raised aggregate runner time from ~76 to ~102
minutes. Twelve shards dominate; lowering the wall below the serial-chain
floor is a de-serialization follow-up, not a shard-count knob.

* test(e2e): drop serial mode from shard-splittable files

Playwright keeps a serial group atomic within one shard, so
real-daemon-run's file-level serial made its ~16-minute chain the floor of
the full UI matrix wall regardless of shard count. Every test in the three
serial files already performs its full setup per test (config reset,
route stubs, own project), so serial only added skip-after-failure and
shard atomicity. Verified order independence by running each file's two
standalone shard halves locally (fully parallel sharding, one worker):
21/21, 57/57, and 12/12 across the halves.

* test(e2e): settle the projects fetch in real-daemon-run home navigation

With serial mode gone, the plugin-authoring spec runs after arbitrary
shard predecessors, so the daemon holds more projects when it navigates
home; the late-resolving projects list re-renders the hero and left the
shortcuts trigger unstable for the whole click timeout on CI. Port the
projects-settle waiter already used by entry-chrome-flows into this
file's gotoEntryHome.

* test(e2e): name the plugins-load gate in the plugin authoring spec

The home shortcuts trigger is disabled while the plugins list loads, and
this spec's mid-flow reload re-fires that fetch after gotoEntryHome's
settle has already passed — on CI the click then times out opaquely for
120 seconds. Arm a response waiter across the reload and assert the
trigger's enabled state explicitly, so a slow or hung /api/plugins fails
with the actual cause named.

* test(e2e): enter plugin authoring through the plugins page create button

The home shortcuts trigger sits disabled on CI runners while a home
plugin apply hangs (plugins list resolves, the pending-apply gate never
clears); three reproduction attempts confirmed the anomaly is CI-only
and independent of this journey. The create button on the Plugins page
drives the same queuePluginAuthoring flow, and this spec's oracle is the
generated scaffold plus its action cards, so route the entry there and
track the disabled-trigger anomaly separately.

* ci: let configure-ci-parallelism drive full UI shard workers

The single-worker override predates the repairs that explained the
two-worker instability (BYOK agent availability and the reveal remount
race, both test-side and since fixed), so it was pinning throughput on a
stale diagnosis. Drop it and let the repo's nproc/2 convention apply —
two workers on the four-core ui_hot profile — halving the per-shard test
step if the runtime holds.

* ci: probe three full UI workers against Blacksmith's actual vCPU capacity

The ui_hot profile resolves to blacksmith-4vcpu runners that reportedly
provide six actual vCPUs while nproc's cgroup view says four, so the
nproc/2 convention derives two workers and idles capacity. Pin three
workers for this probe and print the CPU topology alongside for
verification.

* Revert "ci: probe three full UI workers against Blacksmith's actual vCPU capacity"

This reverts commit 63c8296ae0ead2c3dadfbfda41f6e068d58331ef.

* ci: skip the critical fallback for pure packaged-leaf changes

Playwright starts tools-dev web (daemon + web runtimes) and never
launches the desktop, packaged, or tools-pack entrypoints, so PRs
confined to apps/desktop, apps/packaged, or tools/pack were paying the
two-job critical fallback (5.4-13.4 minutes of wall each) for coverage
their package tests and preflight already provide — seven of the eight
fallback PRs in the latest 100-merge sample were such false positives.

Add a fail-closed ui_critical_validation_required scope: it clears only
when every non-exempt changed file sits under one of those three leaf
roots. tools-dev, any transitive package (including the undeclared
metatool edge), scripts, runtime resources, unknown roots, and mixed
leaf + runtime changes keep the fallback armed; root manifests keep
enabling P0 with the existing critical/P0 mutual exclusion; merge-group
full mode is unchanged.

* test(e2e): align question-form specs with inline Studio discovery

Rebasing onto main picked up #5496, which moved clarification forms
inline into the chat flow and removed the legacy Questions workspace
tab. Re-point the submit-persistence spec at the inline form, and fix
the oracles that no other lane had verified since: the answered summary
echoes the picked visual-style card title (not the option label), and
the submitted answer message carries the card title plus its value id.

* test(e2e): absorb post-rebase drift in BYOK gating and zh-CN hero copy

Two more contracts only the full pool guards moved under the rebase:

- The composer's BYOK gate silently swallows submits until the
  incremental /api/agents stream has delivered byok-opencode's
  availability, so a fast submit races the stream (the sendPrompt
  diagnostic reports the POST was never issued). Retry through that
  window; runErrorCard reads the last card, so swallowed attempts cannot
  shadow the provider-validation oracle. The gate erroring instead of
  waiting while agents are still loading is a product-side follow-up.
- The home hero title copy changed in every locale; re-point the zh-CN
  P2 smoke at the current translation.

* test(e2e): align gallery specs with the all-types default slice

The Community gallery now defaults to an All category pill (#5759)
instead of pre-selecting Deck: assert the new default state in the hero
and category-tab specs, and have the facet-filter spec pick Deck
explicitly before asserting the deck-only visibility set.

* test(e2e): align the first-run reveal spec with the all-types default slice

Same #5759 drift family as the entry-chrome-flows gallery specs, missed
by the first sweep because it lives in home-hero-rail: the revealed
gallery now shows the All pill selected instead of hiding it with
live-artifact pre-selected.

* docs(e2e): add UI test stability rules distilled from the full-pool work

Capture the invariants a UI test must hold to stay green under the generic
sharded full pool, learned from stabilizing that suite and two rebases:

- order independence (no serial groups; setup is per-file, not universal),
  and that neither shade-filtered local runs nor merge-path P0 prove it;
- retry-only passes are an order-leak/readiness signal, not flake;
- settle async surfaces (best-effort waiter + a state-survives-a-window
  check) before interacting;
- an enabled control is not a ready control when a streamed precondition
  (e.g. BYOK-OpenCode agent availability over the /api/agents SSE stream)
  gates it;
- never force-click into inert/collapsed reveal containers; use
  actionability, not visibility, as the readiness signal;
- hermetic agent availability via route mocks (with the terminal SSE `done`
  event) or fake CLI runtimes + agentCliEnv, never host binaries;
- realign stale oracles against the product and confirm them in the lane
  that actually executes the case, since a merged PR does not verify P1/P2
  or unregistered-file P0 oracles;
- name failure causes so a CI-only reproduction carries its own evidence.

Cross-linked from the top-of-file coverage-status pointer.

* test(e2e): realign BYOK missing-provider spec to the #5745 client-side preflight

main #5745 added a client-side BYOK preflight that blocks a missing-provider
run before any POST and opens the execution Settings section, instead of
letting the run reach the daemon and fail there. Rewrite the spec to the new
"blocked before spawn" behavior: submitting opens the execution-mode dialog,
no create-run request is issued, and no artifact is produced (persisted
across reload). This drops the now-obsolete daemon-failed-run assertions and
the SSE-availability retry loop, which the preflight makes unnecessary.

* docs(e2e): explain why the functional config ignores visual specs

Clarify that visual-*.test.ts are owned by playwright.visual.config.ts and
its playwright_visual lane, so excluding them from the functional config is
intentional scope (it keeps a bare test:ui or the full-pool shard run from
executing visual specs without their visual config), not a disabled lane.

* fix(web): portal the New Project brand-preview flyout with the popover

The earlier DS-picker unclip portaled the popover list but left its companion
brand-preview flyout on the old inline `position: absolute` against the
trigger wrapper. Once the popover moved to a body portal, the flyout for a
finalized `user:<id>` brand was still clipped by the modal body and no longer
anchored to the popover. Portal the flyout too and place it beside the
popover with the same viewport-aware logic (right of it when the 320px card
fits, else flipped left, then clamped), mirroring the popover's up/down
vertical anchor.

Red spec: e2e/ui/new-project-ds-picker-clipping.test.ts
"finalized-brand preview flyout is not clipped by the modal body" — red with
the popover portaled but the flyout inline, green with this change.
2026-07-20 09:25:09 +00:00
Ray XiandGitHub 2192a7f6b9 fix incomplete BYOK configuration preflight (#5745)
* fix BYOK configuration preflight

* fix(web): short-circuit non-BYOK run preflight

Local-agent test fixtures omit BYOK-only fields, but provider resolution read model before rejecting non-BYOK protocols. Return before touching those fields and keep the pending-prompt API fixture explicitly valid.

Generated-By: looper 0.10.4 (runner=fixer, agent=codex)

* fix(web): persist committed BYOK key clears

Treat a blank active-provider key as draft-only while it is being edited, then persist the clear once the field is committed. This preserves incomplete replacement drafts while invalidating saved account-scoped model state.

Generated-By: looper 0.10.4 (runner=fixer, agent=codex)

* test(web): align BYOK draft expectations

Keep incomplete model discovery edits in the provider-scoped draft while the active configuration remains unchanged, and expect the current DeepSeek preset default.

Generated-By: looper 0.10.4 (runner=fixer, agent=codex)
2026-07-20 04:26:54 +00:00
3447f60a34 fix packaged payload desktop handoff (#5678)
* fix packaged payload desktop handoff

* fix beta mac full smoke fixture wiring

* fix tools-pack mac payload cold start

* test payload desktop update restart

* fix Nix daemon launcher protocol build

* fix(pack): harden Windows long-jump updates

* fix(pack): harden mac long-jump acceptance

* fix(handoff): defer armed journal writes until spawn and shutdown succeed

Previously executeLegacyPayloadDesktopHandoff wrote the armed handoff
journal, attempt, and runtime state before spawning the payload desktop
and before the old desktop accepted shutdown. When spawn() threw or the
shutdown request failed, the armed journal was stranded on disk, so the
next cold start bailed out of prepareLegacyPayloadDesktopHandoff() with
reason "already-armed" and the install stayed pinned to the old desktop
generation indefinitely.

Move the three writes to after both the payload child has spawned and the
shutdown has been accepted. The old desktop is still alive while it acks
the shutdown, and the payload waits for the old pid to exit before
resuming, so the state still lands before the payload reads it. Add
regression coverage for both the spawn-failed and shutdown-failed paths,
asserting the journal stays "prepared" and a later cold start can retry.

Generated-By: looper 0.10.3 (runner=fixer, agent=claude-code)

* fix(updater): await payload spawn acceptance

* fix(packaged): advance confirmed handoff recovery

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-07-20 02:21:49 +00:00
netteeandGitHub 6b90486c97 docs: reconcile stale documentation with current code (#5829)
* fix(docs): align question guidance with form flow

* docs: reconcile stale documentation with current code

* fix(docs): remove retired template metadata from guides

Generated-By: looper 0.10.6 (runner=fixer, agent=codex)

* fix(e2e): stabilize visual workspace helper

Generated-By: looper 0.10.6 (runner=fixer, agent=codex)

* fix(docs): align localized template guide metadata

Generated-By: looper 0.10.6 (runner=fixer, agent=codex)
2026-07-18 03:41:08 +00:00
lefarcenandGitHub ded62dd8ab fix(web): route history deck keys to preview (#5755)
* fix(web): route history deck keys to preview

Generated-By: looper 0.0.0-dev (runner=worker, agent=codex)

* fix(web): prove history deck keyboard navigation

Generated-By: looper 0.0.0-dev (runner=worker, agent=codex)
2026-07-17 10:03:06 +00:00
Ray XiandGitHub 1dd813f00a fix(byok): preserve provider model order (#5774)
* fix(byok): preserve provider model order

* test(e2e): expect current DeepSeek preset model

Generated-By: looper 0.10.4 (runner=fixer, agent=codex)

* fix(byok): preserve DeepSeek and Ollama defaults

Align DeepSeek e2e fixtures with the V4 preset defaults and keep the existing Ollama Cloud gpt-oss:120b default first in the canonical preference list.\n\nGenerated-By: looper 0.10.4 (runner=fixer, agent=codex)
2026-07-17 06:40:58 +00:00
lefarcenandGitHub 19de7953c4 Fix Open Design example logo loading (#5765)
* Fix Open Design example logo loading

Generated-By: looper 0.0.0-dev (runner=worker, agent=codex)

* Stabilize visual home cold start checks

Generated-By: looper 0.0.0-dev (runner=worker, agent=codex)
2026-07-17 06:35:01 +00:00
lefarcenandGitHub cb5fd3baab fix(web): auto-fit wide html previews (#5751)
* fix(web): auto-fit wide html previews

Generated-By: looper 0.0.0-dev (runner=worker, agent=codex)

* test(e2e): allow auto-fit preview zoom

Generated-By: looper 0.0.0-dev (runner=worker, agent=codex)

* fix(web): gate preview auto-fit on content width

Generated-By: looper 0.0.0-dev (runner=worker, agent=codex)

* fix: measure preview width through iframe bridge

* fix: bridge powered preview sizing
2026-07-17 06:16:04 +00:00
265aa51630 feat(web): make Studio discovery visual and inline (#5496)
* feat(web): make Studio discovery visual and inline

Move clarification forms into the chat flow, add artifact-aware visual style choices, and keep follow-up actions hidden until discovery is complete.

* feat(web): keep clarification entirely in chat

Remove the legacy Questions workspace tab, preserve discovery analytics in the inline form, and keep Skip, Submit, and answered summaries in one conversation flow.

* docs: split Studio Chat input and response work

Frame visual discovery as the input-side initiative and record response aggregation as a separately reviewed follow-up so the current PR stays bounded.

* docs: reorganize Studio Chat experience plans

Separate the overall journey, visual discovery, and response architecture so each scope can be reviewed and delivered independently.

* docs: define first-artifact satisfaction prompt

Limit the feedback prompt to the first successful previewable artifact so it gathers a useful signal without interrupting every iteration.

* docs: keep Studio Chat plans independent

Preserve the existing discovery proposal as one document and keep response architecture as a separate review surface without an extra master layer.

* fix(web): preserve inline discovery behavior

Keep visual direction single-select while restoring custom input, optional auto-continue, analytics, and current end-to-end coverage for the inline flow.

* fix(web): preserve files when skipping discovery step

* fix(web): exclude skipped file answers

* fix(web): align inline discovery with slim prompt

Preserve the latest discovery contract while keeping inline forms, uploads, drafts, and schema-driven visual selections consistent after the rebase.

* feat(web): add style catalog preview images

* fix(web): clarify discovery form completion

* fix(web): preserve question form context

* fix(web): time out unanswered question forms

* test(web): cover inline question form next steps

* fix(web): roll back partial inline uploads

* ci: trigger validation after rebase

* fix(web): unlock blocked inline question submits

* fix(web): retain previewless visual answers

* fix(web): load visual style previews from R2

* fix(web): clean blocked inline uploads

* test(web): expect R2 style preview assets

* feat(web): show full visual style catalog

* fix(web): keep inline question answers consistent

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
2026-07-16 06:57:43 +00:00
6e38058dcf feat(web): add agent-assisted home headline (#5699)
* feat(web): add time-aware home greeting

* feat(web): add contextual greeting emoji

* feat(web): use Agent-focused home headline

* fix(web): refine agent-focused home copy

* fix(web): keep German home copy informal

---------

Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
2026-07-16 03:40:39 +00:00
c67048516c fix(web): polish preview status action (#5696)
* fix(web): polish preview status action

* fix(web): keep preview failure hint passive

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
2026-07-15 12:09:48 +00:00
lefarcenandGitHub 24c7876b37 fix(web): preserve delivery for in-place HTML edits (#5689) 2026-07-15 10:48:26 +00:00
167db9de20 feat(web): add preview delivery status feedback (#5557)
* feat(web): add preview delivery status feedback

* fix(web): keep delivery retry in chat

* feat(web): stage preview delivery progress

* refine(web): clarify preview delivery stages

* fix(web): preserve preview delivery feedback

* fix(web): address preview delivery review

* refine(web): keep preview delivery status passive

* Revert "refine(web): keep preview delivery status passive"

This reverts commit df5579da48.

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
2026-07-15 09:47:02 +00:00
PerishFireandGitHub 925db8d2af ci: move nix and docker off the merge validation path (#5682)
* ci: move nix and docker off the merge validation path

Core ci.yml / Validate workspace / merge_group no longer schedule or require
Nix flake check or Docker image builds. Packaging lives on two standalone
workflows: nix.yml (flake check) and docker-image.yml (PR smoke build +
tag/release publish). Update scopes, topology tests, and agent docs.

* fix(ci): detect Docker publish from release inputs, not workflow_call event

Reusable workflow callers keep their own github.event_name (usually
workflow_dispatch), so matching event_name == workflow_call never fires and
stable GHCR publish would silently smoke-build only. Gate publish on tag push
or non-empty release_version / publish_latest inputs instead.

* fix(ci): keep smoke-only Docker sha tags off the publish path

Gate pr-/manual-sha metadata tags on publish != true so release-stable
workflow_dispatch callers do not push extra manual-sha-* tags to GHCR.
2026-07-15 08:47:03 +00:00
open-design-release-bot[bot]GitHubopen-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
a1cbe0ec57 chore(release): bump main to 0.15.1 ahead of stable 0.15.0 (#5661)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
2026-07-15 03:30:28 +00:00
lefarcenandGitHub d7463072a8 fix(web): Plan 模式生成/重写 HTML 后自动打开生成的文件 (#5602)
* fix(web): re-focus HTML rewritten in place when a turn ends

Plan mode's core loop is plan -> generate -> edit the plan -> regenerate.
On the second generation the deliverable (index.html) already exists, so
the turn-end auto-open — a pure pre/post file-NAME diff — saw no new file
and left the viewer stuck on the markdown plan. Protocols that emit no
Write tool events (codex, gemini, opencode, ACP agents) have no per-write
fallback, so nothing opened at all.

Turn-end selection now also considers pre-existing project files whose
mtime lands inside the turn window (same attribution as AssistantMessage's
inferProducedFilesFromTurn), ranked by the existing HTML > markdown
preview priority. Turns without a start stamp keep the produced-only
behavior.

Red spec: e2e/ui/real-daemon-run.test.ts "Plan mode regeneration re-opens
the existing generated HTML file" (red on main, green here), plus a
first-generation companion spec and unit coverage for the new selector.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)

* worker: Plan模式下,不会自动打开生成的HTML文件

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)

* fix(web): scope the turn-window auto-open to agent-touched files and bound it by turn end

Review follow-ups on the Plan-mode auto-open selector: the mtime
attribution window [turnStartedAt - 1s, +inf) could attribute a file the
USER was editing to the turn - in Plan mode plan.md autosaves from the
split editor during a run, so a text-only turn could yank focus back to
it - and the missing upper bound let post-turn edits fall in-window on
the reload/reattach recovery call sites.

selectAutoOpenTurnArtifact now accepts:
- agentTouchedFileNames: when the protocol emits Write/Edit tool events
  (claude stream-json), mtime-window candidates are restricted to files
  the agent actually wrote; protocols with no write events (codex,
  gemini, opencode, ACP) pass an empty set and keep the pure time
  window - the case the window exists for.
- turnEndedAt: bounds the window at endedAt + 60s, matching
  AssistantMessage's inferProducedFilesFromTurn on both sides; callers
  without an end stamp keep the open-ended window.

All three call sites resolve the agent's raw tool paths to project file
names through the existing findTouchedProjectFile matching (new
resolveAgentTouchedFileNames helper) and pass their terminal timestamp.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)

* chore: untrack the looper task-attachment directory

The runner's fallback lifecycle commit (a838f8a) accidentally captured
.looper-attachments/bug15_1.png (1.2 MB task-reference screenshot).
Those files are task-local reference material and must stay out of git;
remove them from the tree without rewriting pushed history.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 11:58:53 +00:00
Ray XiandGitHub e20ae0bdbe test(e2e): cover uploaded root-relative image preview (#5621) 2026-07-14 09:45:45 +00:00
lefarcenandGitHub 61ee3914d3 fix(web): paint the update-ready prompt above the raised home composer (#5608)
The updater prompt lives inside .entry-main__topbar, an isolated sticky
stacking context at z-index 10, so its own z-index 80 could not escape.
When the composer's agent picker (or another composer popover) is open,
.home-hero__input-card:has(...) raises the card to z-index 1700, painting
the composer and its popover over the update prompt on scrolled home
windows. Mirror the settings-menu precedent and lift the topbar to
z-index 1900 while .updater-popup is open.

Red spec: e2e/ui/updater-popup-stacking.test.ts reproduces the overlap
with elementFromPoint sampling (red on main, green here).

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 09:39:12 +00:00
d7151b2b60 feat(analytics): link downloads and first-party browser opens (#5442)
* feat(analytics): attribute desktop downloads

* fix(analytics): initialize attribution routes after HTTP deps

* feat(analytics): bridge first-party browser opens

* feat(analytics): bridge first-party anchor opens

* fix(analytics): make attribution claims idempotent

* fix(analytics): record pending attribution results

* chore(analytics): bind landing attribution KV

* fix(ci): remove landing preview wrangler config override

* fix(landing): resolve download attribution function

* fix(analytics): harden attribution token consumption

* fix(analytics): preserve same-installation claim retries

* fix(analytics): merge recovered attribution retries

* fix(analytics): preserve attributed browser links

* fix(analytics): clear attribution on privacy reset

* fix(analytics): bridge stopped AMR console links

* fix(ci): deploy attribution schema before Pages

* fix(ci): run attribution migrations with Wrangler 4

---------

Co-authored-by: Siri-Ray <2667192167@qq.com>
Co-authored-by: PerishCode <perishcode@gmail.com>
2026-07-14 08:46:04 +00:00
lefarcenandGitHub da7c4ae051 fix(web): rank Community "Newest" by shipped publishedAt, not local install timestamps (#5597)
* fix(web): rank Community "Newest" by shipped publishedAt, not local install timestamps

The Community gallery Newest toggle sorted by the installed-record
updatedAt/installedAt. Those are stamped when a machine seeds its own
database, so a fresh install (or an upgrade that re-stamps the catalog)
writes the whole bundled catalog milliseconds apart in folder-walk
order — Newest showed reverse walk order (roughly reverse-alphabetical),
unrelated to publication recency.

Root-cause fix: bundled templates now ship a real publication date.

- packages/contracts: add optional manifest field publishedAt (ISO 8601).
- apps/web sortByNewest: freshness = publishedAt when parseable, else
  updatedAt (right signal for user-installed plugins); tie-breaks and
  stability unchanged.
- plugins/_official: backfill publishedAt into all 460 bundled manifests
  from each folder's first-appearance commit date in upstream git
  history.
- e2e: repository-resource guard requiring every bundled manifest to
  declare a parseable publishedAt, so the signal cannot silently erode.
- docs: field reference entries in plugins-spec (en + zh-CN).

Issue: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/ca0778af-97f5-4fc0-bc63-739280904979

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)

* worker: 模板用最新筛选,筛选出来的不是最新的

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)

* fix(web): keep shipped appeal order inside same-publishedAt batches

Review follow-up (#5597, nettee): after the freshness tie on an equal
publishedAt, the comparator still consulted local updatedAt/installedAt,
which a fresh install stamps in folder-walk order — so every
same-publication batch (largest: 322 manifests on one catalog date)
re-sorted per machine instead of keeping the shipped visual-appeal
ranking. Same-publishedAt records now preserve their incoming position;
local timestamps break ties only when at least one side has no usable
publishedAt. Adds the requested regression spec pinning
same-publishedAt / different-updatedAt records.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)

* fix(web): gate publishedAt freshness to bundled records; untrack oversized looper attachment

Review follow-up (#5597, nettee round 2): publishedAt was overriding
updatedAt for every visible plugin, so a user-installed github/local/
marketplace plugin carrying an old manifest publishedAt would be buried
behind bundled templates despite a newer local install. publishedAtMs
now returns null for non-bundled records — "Newest" means install
recency for user installs, catalog publication for bundled ones. Adds
the requested regression (non-bundled, old publishedAt, newer
updatedAt) and clarifies the field docs (en + zh-CN).

CI repair: commit 578f7a9 tracked .looper-attachments/bug13_1.png
(3.8MB bug screenshot), tripping the Static gate 1MiB blob guard and
the Validate workspace aggregate. Untrack it and gitignore
.looper-attachments/ so harness attachments stay out of the repo.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 08:43:57 +00:00
lefarcenandGitHub 906d6ff1f9 fix(web): keep the sticky home topbar above the popover-elevated composer card (#5596)
Opening any popover inside the home composer card (agent/model switcher,
session-mode toggle, plugin picker, template menu) lifted the whole card
to z-index 1700, which out-stacked the sticky entry topbar (z-index 10)
and the fixed modal family (automation 80, plus-menu 90, plugins-import
100, use-everywhere 920). With the home view scrolled, the card body
painted over the topbar chips (GitHub star, Teams, Discord, settings).

The lift only needs to beat the static home content below the hero,
whose stacking contexts top out at 8, so 9 restores the intended
layering: content < elevated card < sticky topbar < fixed modals.
Popover z-indexes inside the card are local to its stacking context and
keep working unchanged.

Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/928fae75-d660-426f-be8e-f98ed4eb88e1

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-14 06:44:53 +00:00
Marc ChanandGitHub d954bee805 Extract telemetry worker from monorepo (#5565) 2026-07-13 14:46:47 +00:00
lefarcenandGitHub e6bef56e19 fix(web): keep the split resize handle hitbox off the chat scrollbar gutter (#5526)
The chat/workspace split handle extends its pointer hitbox with a
::before overhang of left: -2px / right: -10px. The 2px chat-side
overhang sits exactly on the outer edge of the chat-log's 8px scrollbar
gutter, so clicks aimed at the (~4px visible) scrollbar thumb start a
panel resize instead of scrolling, and hovering the gutter flips the
cursor to col-resize with the divider highlight. In RTL the physical
right: -10px lands on the chat side and covers the entire gutter.

Switch the overhang to logical properties with a hard invariant: the
hitbox never crosses the handle's inline-start edge (where the scrollbar
lives in both directions) and extends 8px on the workspace side only
(inset-inline-start: 0; inset-inline-end: -8px). Effective grab width
stays 16px; resize JS is untouched.

Red spec: e2e/ui/split-resize-scrollbar-hitbox.test.ts probes
elementFromPoint on the gutter edge (LTR + RTL), asserts hover does not
enter the resize posture, and keeps a drag-the-handle regression guard.

Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/5c505d35-42e9-40e7-ab07-ba6e3bbfb2ee

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-13 09:13:00 +00:00
3e09ebf282 feat(release): publish localized changelog metadata (#5520)
* feat(release): publish changelog metadata

* fix(release): verify release notes from stored bytes

Generated-By: looper 0.10.3 (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-07-13 08:21:31 +00:00
AmyandGitHub 8325aa3d21 test: expand project context daemon coverage (#5516)
* test: cover project context daemon flows

* test: remove private daemon import from e2e

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)
2026-07-13 06:23:49 +00:00
lefarcenandGitHub fc5b276f7d feat(ci): surface merge-queue needs-validation ejection as a PR comment (#5519)
A PR ejected from the merge queue by the needs-validation gate leaves no
trace on the PR itself: the gate fails on the queue's transient ref, the
PR's own checks stay green, and mergeQueueEntry silently goes null. The
only way to learn why the PR fell out of the queue is to dig through
merge_group workflow runs, which nobody watches (observed on #5329).

Producer: the merge_group gate in ci.yml now emits a handoff/comment
artifact targeting the labeled PR right before it blocks, and uploads it
on the failure path. Consumer: comment.atom.yml accepts merge_group
workflow_run events; since a merge_group run's head_sha is the queue's
synthetic merge commit, artifacts from those runs are bound to their
producing run by run_id, and the base-freshness skip is waived (PRs
ahead in the queue move the base while the run completes). The live-PR
head check still guards staleness.

Topology coverage pins the new boundary: comment.atom consumes
pull_request + merge_group; autofix/report stay pull_request-only.
2026-07-13 04:56:12 +00:00
lefarcenandGitHub bc5b6f0588 fix(daemon): flag runs that end with unfinished work so status never reads "Completed" early (#5470)
A design-generation run that ended its turn with TodoWrite tasks still
pending / in_progress / stopped — or was truncated by max_tokens — was
stamped `succeeded` and read "Completed" on the Pet task center and the
project pill, while the chat footer simultaneously read "Stopped with
unfinished work". Users trusted "Completed", assumed the work was done, and
re-requested, creating duplicate generations and eroding trust in the
run-state UI (#1247 / #1060).

Root cause: the daemon equates "the turn/process ended" with "succeeded" and
never consults the agent's own declared work; multiple web surfaces then map
`succeeded` -> "Completed" without any completeness signal, even though the
chat footer already distinguishes unfinished work.

Fix — one canonical signal, consumed everywhere so the surfaces cannot drift:

- packages/contracts/api/run-completeness.ts defines the canonical
  "unfinished declared work" predicate (status !== 'completed', so pending /
  in_progress / stopped all count), matching the web footer's
  unfinishedTodosFromEvents exactly.
- Daemon folds the last TodoWrite snapshot + a max_tokens truncation off the
  agent event stream (every runtime, via the single emitAgentEvent choke
  point) and derives run.endedWithUnfinishedWork once at finish().
- The project-status projection derives `incomplete` from the already
  persisted events_json — no schema migration — so it survives reload/reattach
  instead of flashing "Completed" once the in-memory run ages out.
- Contracts carry endedWithUnfinishedWork on ChatRunStatusResponse and the SSE
  end payload (backward compatible: absent = finished), plus a new
  `incomplete` ProjectDisplayStatus.
- Web surfaces (Pet task center, project pill, DesignsTab) render `incomplete`
  as amber needs-attention, never the success color; the footer now sources
  the same shared predicate.

Truncation uses the display-only option (spec Option A): the run stays
`succeeded` but is flagged incomplete, avoiding the code===0 / stdin-close
hazards of reclassifying a truncated turn to failed.

Regression coverage, red on main / green here: an HTTP-boundary e2e
(e2e/tests/dialog/task-completeness.test.ts) drives the interactive chat path
for unfinished / stopped / truncated / finished runs; daemon + contracts +
web unit tests pin the projection and the canonical predicate.

Ref: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/011f6f51-7a25-4043-ae94-182e0b8b0931

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
2026-07-13 04:17:24 +00:00
open-design-release-bot[bot]GitHubopen-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
fb9ae974ab chore(release): bump main to 0.14.2 ahead of stable 0.14.1 (#5441)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
2026-07-10 14:50:47 +00:00
bc6a246c65 feat(slides): enhance deck project experience (#5083)
* feat(deck): enhance slide deck functionality with speaker notes and navigation improvements

- Added a switch for including speaker notes in slide decks, defaulting to true.
- Updated keyboard navigation to include 'R' for resetting to the first slide.
- Implemented click navigation for half-slide transitions.
- Enhanced the deck framework to support hidden programmatic prev/next controls.
- Improved handling of file inputs in question forms to support file submissions.

This update aims to improve user experience in creating and presenting slide decks, ensuring better accessibility and functionality.

* feat(artifacts): enforce semantic naming for user-facing deliverables

- Updated guidelines to require semantic filenames derived from user briefs for new artifacts, moving away from defaulting to `index.html`.
- Enhanced documentation to clarify naming conventions for HTML files, emphasizing the use of descriptive names like `investor-pitch-deck.html`.
- Adjusted prompts and hints across the system to reflect these changes, ensuring consistency in user-facing outputs.
- Improved test coverage to validate the new naming conventions and ensure proper implementation in various contexts.

This update aims to enhance clarity and usability in project file management, promoting better organization and user experience.

* feat(file-viewer): enhance speaker notes display and improve file workspace functionality

- Updated the speaker notes section to include a styled placeholder for empty notes, improving user experience.
- Changed the icon in the file workspace from "slides" to "panel-left" for better clarity.
- Introduced new sub-category filtering options in the file workspace, enhancing organization and navigation.
- Improved styling for the page creator search input and added visual feedback for loading states.

These changes aim to enhance usability and visual consistency across the file viewer and workspace components.

* test(web): align workspace tests with pages switcher

* test(e2e): open design files through pages menu

* fix(deck): keep source mode reachable

* fix(workspace): restore uploaded file tabs

* feat(deck-thumbnail): implement virtualized thumbnail rail for improved performance

- Introduced a new `DeckThumbnailRail` component to optimize the rendering of deck thumbnails, reducing memory usage and improving load times.
- Enhanced the `FileViewer` to integrate the new thumbnail rail, allowing for lazy loading of thumbnails based on viewport visibility.
- Added tests for the `DeckThumbnailRail` to ensure correct functionality and caching behavior.
- Updated styles for better visual consistency and performance during thumbnail rendering.

These changes aim to enhance user experience by providing a smoother and more efficient way to navigate through deck slides.

* feat(deck): enhance deck rendering with thumbnail support and keyboard shortcuts

- Integrated `DeckSlideThumbnail` for rendering individual slides in a shadow root, improving performance and reducing main-thread load.
- Updated `DeckThumbnailRail` to utilize the new thumbnail component, allowing for efficient slide navigation.
- Implemented keyboard shortcuts for deck navigation, enhancing user experience during presentations.
- Added tests for `buildDeckRenderInput` to ensure proper functionality of the deck stage fallback.
- Improved styling for loading states in the viewer, providing a smoother visual experience.

These changes aim to optimize the deck viewing experience by reducing resource consumption and enhancing interactivity.

* feat(deck): improve thumbnail loading and speaker notes editing experience

- Refactored `DeckSlideThumbnail` to enhance loading states with a new shimmer effect, improving visual feedback during thumbnail rendering.
- Updated `DeckThumbnailRail` to manage loading states for thumbnails, ensuring a smoother user experience.
- Simplified the speaker notes editing interface by removing the toggle switch, allowing direct editing from the notes body.
- Enhanced accessibility by adding roles and focus states to speaker notes elements.
- Added tests to verify the new loading states and speaker notes functionality.

These changes aim to optimize the user experience in deck navigation and speaker notes management, providing a more intuitive and responsive interface.

* feat(file-viewer): enhance version export functionality and speaker notes interaction

- Introduced new export options for file versions, including PDF and image formats, improving the ability to download and share content.
- Implemented a streamlined process for exporting version content, with progress indicators and error handling for a better user experience.
- Enhanced speaker notes editing by refining event handling to prevent unintended focus shifts, ensuring a smoother editing experience.
- Updated styles for improved visual consistency in the file version download interface.
- Added tests to verify the functionality of version exports and speaker notes interactions.

These changes aim to optimize the file viewer's usability and enhance the overall experience when managing and exporting project files.

* fix(web): polish export menus and creator filters

* fix(file-viewer): fall back to preview exports

* test(file-viewer): cover version export fallbacks

* test(file-viewer): assert loading skeleton by role

* fix(file-viewer): restore html source mode affordance

* fix(file-viewer): capture full version fallback snapshots

* docs: trim slides handoff eof

* test(exports): cover full preview snapshot requests

* test(file-viewer): account for snapshot options arg

* refactor(file-viewer, file-workspace): streamline export functions and enhance page creator dialog visibility

* test(file-workspace): enhance slide template creation and mock plugin responses

* feat(export): add support for versioned exports and enhance HTML export functionality

* feat(export): implement versioned export support across multiple formats and enhance related tests

* feat(file-viewer): enhance manual edit functionality with commit sequence tracking and add test for inline text session saving

* style(speaker-notes): update filmstrip and thumb-frame styles for improved responsiveness and add corresponding tests

* feat(file-viewer): enhance comment composer functionality with portal metrics and improve draw toolbar layout for mobile views

* feat(analytics): track slide deck viewing experience

Add analytics for the new deck/slides experience introduced in this PR:
the deck viewer surface, in-deck slide navigation, thumbnail rail, and
speaker-notes authoring. Presentation entry stays on the existing
present_popover click to avoid double-counting.

- contracts: DeckViewerSurfaceViewProps (surface_view),
  DeckViewerClickProps (ui_click), SpeakerNotesSaveResultProps
  (new speaker_notes_save_result event), wired into the event unions.
- web: track* wrappers + fire helpers in FileViewer — deck_viewer
  surface_view on deck mount, slide_prev/next/reset, thumbnail_select,
  thumbnail_rail_toggle (expand/collapse), speaker_notes_edit, and
  speaker_notes_save_result (preview + presenter surfaces).
- tests: deck analytics suite asserting the emitted (event, props) pairs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(file-viewer, file-workspace): enhance export toast functionality and improve localization for page creation

* feat(home-view, plugins-home-section, agent-skill): update default facet preference, enhance category selection handling, and improve localization support

- Set `preferDefaultFacet` to true in HomeView component for better user experience.
- Introduced `handlePickCategory` function in PluginsHomeSection to manage category selection based on card layout visibility.
- Updated PRIMARY_CATEGORIES to reflect the new order and added localization for titles and descriptions in adaptAgentSkill.
- Enhanced tests to verify the correct handling of display copy and tags from SKILL frontmatter.

* feat(plugins-home, file-workspace): introduce commercial category labels and enhance deck handling

- Added a new module for resolving and localizing commercial category labels for plugins.
- Updated the FileWorkspace and PluginCard components to display the commercial category label on deck cards.
- Enhanced the PageCreatorPresetFrame to adjust frame classes based on deck presets.
- Refactored subcategory handling in facets to align with the new commercial category taxonomy.
- Improved localization support for new category labels across multiple languages.

* feat(deck-preview): implement deck preview scaling and enhance home hero layout

- Introduced `useDeckPreviewScale` hook to manage scaling of deck previews within a fixed 1280×720 logical viewport, ensuring proportional rendering across various templates.
- Updated `FileWorkspace`, `HomeHero`, and `PluginCard` components to utilize the new scaling functionality for improved visual consistency.
- Enhanced the layout of the home hero section to accommodate new subtype chips and descriptions, including a "More" dropdown for overflow handling.
- Improved localization support for new UI elements across multiple languages.

* fix(PreviewModal): clarify sidebar prop documentation and update test description

- Updated the documentation for the `PreviewSidebar` interface to specify that the sidebar's stage-edge handles are not shown when the sidebar prop is absent.
- Modified the test suite description for `PreviewModal` to be more concise and relevant.

* test(e2e): select prototype visual card

---------

Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 09:41:47 +00:00
cd07d359df feat(chat): surface daemon failure_detail to sharpen run-error guidance (#5321)
* feat(chat): surface daemon failure_detail to sharpen run-error guidance

The daemon already classifies each run failure into failure_category /
failure_detail (run-failure-classification.ts) for retry policy and
telemetry, but kept it daemon-internal — so the chat could only key error
guidance off the coarse errorCode. That collapses distinct causes that
share one code: a hard quota and a transient 429 both arrive as
RATE_LIMITED, and a text-detected missing CLI leaks in as the opaque
AGENT_EXECUTION_FAILED.

Propagate the classification to the client (contract DTO + SSE end frame,
mirroring the proven `resumable` path) and let resolveRunFailureUi take
`detail` as a first-class input that can override a too-coarse code:

- hard_quota / workspace_credits_exhausted -> a distinct "Quota exhausted"
  card that drops the futile Retry (primaryAction 'none') and steers to the
  hosted-AMR switch card instead.
- cli_not_installed (text-detected) -> the same install-the-CLI card the
  AGENT_UNAVAILABLE code path already renders.

A transient 429 without a hard-quota detail still offers Retry, and the
Antigravity terminal switch-model flow still wins over the generic detail
override.

Stacks on #5308 (the code->guidance mapping this detail layer refines).

* chore(i18n): rebrand user-facing "AMR" to "Open Design Cloud"

The hosted model service is now Open Design Cloud, but the run-error
cards this PR sharpens (switch / auth / balance), plus settings account +
wallet labels, avatar console, onboarding gate, and the handoff website
link, still showed the old "AMR" (or bare "Open Design") name. Normalize
every user-visible brand mention to "Open Design Cloud" across all 19
locales. Value-only: no keys, agent ids, URLs, or raw daemon error
strings touched.

* fix(chat): persist daemon failure classification onto assistant message

The daemon emits the terminal `error` SSE frame from the child-close
handler before a run is finalized, so `failureCategory` / `failureDetail`
(computed at finalize) weren't known when that frame was first persisted
to the assistant message — the stored `status:error` event kept only
`{ detail, code }`. A failure persisted without the live web error
handler saving the message, or a conversation reloaded before that save
landed, therefore fell back to the coarse errorCode UI and lost the
specific fix guidance this PR adds.

Stamp the finalize-time classification onto the persisted assistant
message: enrich the last `status:error` event in place (append one only
if a failed run somehow never persisted an error frame). Covered by a
daemon regression that reads the stored assistant message via the
conversation-messages API after a failed hard-quota run.

* fix(chat): keep daemon failure classification on the live web reload/recovery paths

Two live-propagation gaps let the sharpened run-error card degrade back to
the coarse/raw UI even though the daemon now sends the classification:

- appendErrorStatusEvent() no-op'd whenever the message already had a
  trailing status:error with the same detail. In the reload/reattach race
  (reload reads the daemon-persisted error frame first, then the run
  finishes and onError arrives with the finalize-time classification) that
  dropped the new code / failureCategory / failureDetail. Merge those into
  the existing event in place instead; no-op only when nothing is added.

- The error-frame-then-status recovery probe in providers/daemon.ts broke
  out on a terminal failed status without copying failureCategory /
  failureDetail (unlike the post-loop status fallback), so markErrorRunFailure
  stamped null and the failureDetail mapping fell back to the generic card on
  the reconnect path. Mirror the two assignments before the break.

Covered by chat-events (in-place merge) and daemon-sse-reconnect (error
frame + terminal status fetch, no end frame) regressions.

* fix(web): update AMR tier-upgrade test for 3-arg resolveRunFailureUi

The main merge kept a two-argument call site that passed agentId as
detail after the signature gained an explicit detail parameter.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci: re-trigger validate workspace on approved head

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: align AMR rebrand assertions with Open Design Cloud copy

Update unit and e2e selectors that still expected pre-rebrand strings
for account status, handoff website link, and switch-and-retry CTA.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 03:46:01 +00:00
9ef4f0daa6 [codex] Refine privacy consent choices (#5384)
* Refine privacy consent choices

* Add privacy consent value copy

* Polish privacy consent copy

* Refine privacy banner wording

* Fix privacy consent review issues

* chore: retrigger privacy consent CI

* Fix privacy banner dismissal in AMR onboarding tests

* Fix privacy choice telemetry preservation

* Preserve settings draft during privacy choice

* Preserve pending settings privacy choices

* Align privacy policy consent labels

---------

Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
2026-07-10 02:39:28 +00:00
lefarcenandGitHub d5aeab77fc fix(ci): push the release-cut baked manifest as a ruleset bypass bot (#5358)
* fix(ci): push the release-cut baked manifest as a ruleset bypass bot

bake-plugin-previews-release.yml renders every preview on a release/** cut and
pushes the authoritative manifest straight onto the release branch. But release/**
is guarded by the 'Protected branches (preview/*, release/v*)' ruleset whose
pull_request rule rejects direct pushes — and the workflow pushed with
PREVIEW_BAKE_TOKEN||github.token, i.e. github-actions[bot], which is NOT a bypass
actor. So the writeback failed with 'GH013: Repository rule violations found for
refs/heads/release/vX.Y.Z' (observed on release/v0.14.2), and the release branch's
bundled data/plugin-previews/manifest.json was never refreshed — the packaged app
then falls back to rendering each preview as a live iframe (pathological for the
WebGL example templates).

Mint an open-design-bot token (app id 3640364 — already a bypass actor on that
ruleset, same App the metrics/blog/contributors workflows use) after the render
and push with it via an explicit token URL. persist-credentials: false on checkout
so the leftover github.token extraheader can't override the inline bot token on
push (same failure mode fixed in #5357). No ruleset change required — the bypass
actor already exists; the workflow just wasn't using it.

* test(ci): lock the release-cut bake's ruleset-bypass push contract

Per .github/AGENTS.md, cross-workflow auth changes must update topology coverage
in e2e/tests/packaged-smoke-workflow.test.ts rather than rely on YAML review.
Add a companion assertion (alongside the existing bake-plugin-previews.yml one)
that locks bake-plugin-previews-release.yml's three co-dependent auth invariants:
persist-credentials: false on checkout, the open-design-bot token mint
(BOT_APP_CLIENT_ID/BOT_APP_PRIVATE_KEY — the App that is a ruleset bypass actor,
not RELEASE_BOT_APP_ID), and the explicit x-access-token:${BOT_TOKEN} push. A
refactor dropping any one silently reintroduces the GH013 rejection that stranded
release/v0.14.2's manifest.
2026-07-09 11:10:06 +00:00
Marc ChanGitHubopen-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
b3356807b6 fix(amr): improve unavailable model gating (#5335)
* fix(amr): honor full model list metadata

* fix(amr): classify tier entitlement failures

* fix(amr): refine unavailable model upgrade affordance

* fix(web): restore stale AMR model fallback

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

* fix(web): surface AMR tier upgrade failures

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

* fix(amr): mirror chat upgrade attribution

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

* fix(amr): skip disabled default models

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

* fix(amr): honor catalog default model

* chore(pack): bump vela cli to 0.0.20

* fix(amr): align locked catalog fallback state

* chore(nix): refresh pnpm deps hash

* fix(amr): refresh entitlement-aware model state

* fix(amr): refresh models after upgrade return

* fix(amr): preserve default model analytics

* fix(amr): tighten model gating follow-ups

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

* fix(amr): repair default and refresh follow-ups

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

* fix(amr): resolve explicit default before spawn

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

* fix(amr): concretize default model in smoke paths

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

* fix(amr): repair default model gating

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

* fix(amr): share connection-test model cache

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

---------

Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
2026-07-09 10:37:26 +00:00
AmyandGitHub 39c8eea0c5 test: cover recent daemon and e2e follow-ups (#5354)
* test(e2e): cover recent main follow-ups

* test: cover recent main automation gaps
2026-07-09 10:18:59 +00:00
lefarcenandGitHub d41cdd9404 feat(release): post an immediate "branch cut" Feishu card on both release cuts (#5323)
Cutting a release branch (Tuesday minor via cut-release, Thursday patch via
cut-patch-release) pushes to release/**, which triggers notify-release-feishu to
build a full prerelease before it posts the download card — 20-40 minutes during
which the team has no signal the cut even happened.

Post an eager green "🌿 已切出 release 分支" card the moment the branch exists, from
the cut workflow itself, reusing feishu-notice.ts (node:crypto only, so the
existing setup-node suffices). The card names:
  - the branch and whether it is a 大版本 (minor) or 小版本 (patch),
  - the exact backport label (`backport release/vX.Y.Z`) so people know which
    label to apply for backports,
  - a note that the prerelease build is running and a download card will follow.

The download card from notify-release-feishu is unchanged; each cut now yields
an immediate ack card plus the eventual download card. The patch workflow's eager
card only fires on the happy (published) path, alongside the existing skip card.

Topology test asserts both workflows post the eager card after the branch push and
name the backport label.
2026-07-09 04:01:52 +00:00
lefarcenandGitHub dbc70663aa fix(release): let the Thursday patch cut survive an already-matching version (#5320)
The first scheduled cut-patch-release run (2026-07-09) failed at "Create branch +
bump version + push" with "nothing to commit, working tree clean". The guard had
passed (open-design-v0.14.0 published → happy path) and it computed patch v0.14.1,
but main's apps/packaged/package.json was already 0.14.1: in steady state main
leads stable by exactly one patch (finalize-release bumps main to X.Y.(Z+1) after
X.Y.Z ships), and the Thursday patch equals that same X.Y.(Z+1). So
`npm pkg set version=0.14.1` is an idempotent no-op, `git commit -am` finds an
empty tree, and under `set -euo pipefail` the step dies before the push — no
branch cut, no prerelease, no Feishu card. This collides every Thursday the
version already matches, so it is structural, not a one-off.

Commit the release marker with `--allow-empty` so the branch is always cut and
pushed (which is what triggers notify-release-feishu); a real version bump still
commits the change normally. Add a topology assertion that the release commit
tolerates an empty tree.

Verified locally: with a real `npm pkg set version=<same>` (idempotent, no diff)
the old `git commit -am` fails exactly as CI did while `--allow-empty` succeeds
with a marker commit; a differing version still commits the bump.
2026-07-09 03:22:52 +00:00
08da51b3b4 test: expand daemon and UI e2e coverage (#5107)
* test: expand daemon and UI e2e coverage

* fix: refresh retry assistant metadata

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* fix: persist inherited run session mode

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* fix: stabilize AMR inline auth retry

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* fix: reconcile legacy chat assistant pins

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* test: stabilize fake AMR auth recovery

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* test: restore e2e interaction actionability

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* test: address review feedback for PR 5107

* test: stabilize PR 5107 CI interactions

* test: tighten PR 5107 review coverage

* fix: inherit run session mode after conversation fallback

* test: assert AMR promotion retry renders recovery

Generated-By: looper 0.10.1 (runner=fixer, agent=codex)

---------

Co-authored-by: Amy <1184569493@qq.com>
2026-07-09 03:05:42 +00:00
lefarcenandGitHub b0e4f83e85 ci(release): Thursday patch-release cut, gated on the Tuesday minor shipping (#5294)
* ci(release): add Thursday patch-release cut gated on the Tuesday minor shipping

cut-release cuts a minor every Tuesday (from main HEAD) and, via the release/**
push, fires notify-release-feishu to build a prerelease and post a Feishu card.

Add cut-patch-release: the same flow one weekday later (Thursday 09:00
Asia/Shanghai) with three deltas:

- Bumps the PATCH of the highest release/vX.Y.Z branch (X.Y.(Z+1)), still cut
  from main HEAD like cut-release.
- Gates on this week's minor having actually shipped: the line's minor base
  X.Y.0 must be a PUBLISHED stable GitHub Release (open-design-vX.Y.0,
  isDraft=false, isPrerelease=false — i.e. release-stable ran
  `gh release edit --draft=false --latest`, which rolls the tag back on
  failure). If it is not published, no branch is cut and nothing is built;
  instead a Feishu notice card announces the skip.
- On the happy path it is otherwise identical to cut-release: create the branch,
  bump apps/packaged, App-token push (which triggers notify-release-feishu), and
  create the backport label.

feishu-notice.ts is a lightweight sibling of feishu.ts for non-build events
(here: the skip card). It reuses the same signed-webhook + retry transport and
depends only on node:crypto, so it runs under the existing setup-node with no
pnpm install.

packaged-smoke-workflow.test.ts locks the guard: Thursday cron + patch bump, the
published-stable predicate, and that every branch-cutting step is gated so the
skip path posts the notice without cutting or building.

* fix(release): gate the patch cut on the final version's minor, not the highest branch

The guard target MINOR_BASE was derived from the highest release/v* branch before
the manual workflow_dispatch `version=` override was applied. On the auto path V
comes from that same branch, so it matched — but a manual `version=0.15.1` while
the latest branch is release/v0.14.0 would check open-design-v0.14.0 (the wrong
minor), letting the workflow cut release/v0.15.1 even when 0.15.0 has not shipped
(and the inverse could wrongly block a valid manual patch on another line).

Decide V first (validated override wins, else patch-bump the highest branch),
then derive MINOR_BASE from V's own major/minor. Add topology assertions that
MINOR_BASE comes from V and never from the highest branch, covering the manual
dispatch path.
2026-07-08 09:22:47 +00:00
open-design-release-bot[bot]GitHubopen-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
38c20ef6fa chore(release): bump main to 0.14.1 ahead of stable 0.14.0 (#5287)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com>
2026-07-08 06:06:47 +00:00
8ca68bbfe6 feat(web): pre-run Open Design Cloud balance gate with subscription dialog (#5103)
* feat(web): Open Design Cloud pre-run balance gate — two-tier dialogs with auto-resume

Gate every AMR chat send before any run spawns. HARD tier (definitive
empty wallet <= $0, or signed out) blocks with a subscription-invitation
dialog: distinct copy and CTA per cause (console wallet page vs in-app
sign-in), benefits card, and auto-resume — a completed sign-in or a
recharge observed by a bounded wallet watch resumes the parked task on
its own. SOFT tier (<= $4) shows a dismissible reminder with top-up /
start-anyway / persisted don't-ask-again (never silences the hard tier).

Blocked project sends park in the conversation queue with their full
payload and pause auto-drain until a send passes the gate; home submits
hold the create open (continuation promise) so drafts and staged context
are never lost, and the home decision carries into the project's first
auto-send to avoid double-prompting. Fail-open contract: unknown or
unparseable wallet answers never block; hard blocks are confirmed with
refresh=1 before firing. New amr_entry sources cover both surfaces and
tiers; nine chat.amrBalanceGate.* / chat.amrLowBalance.* i18n key groups
ship in all 19 locales.

* fix(web): a stale wallet-refresh echo must not confirm a hard block

A failed refresh returns the previous cached snapshot with stale=true and
an error; treating it as a fresh definitive $0 (or signed-out) answer
violated the gate's fail-open contract and could strand a user who just
topped up while the wallet endpoint hiccuped. (Review feedback.)

* chore: retrigger ci

* test(e2e): align AMR UI checks with balance gate

* fix(web): preserve AMR gated sends across conversation switches

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: Amy <1184569493@qq.com>
Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com>
2026-07-07 09:20:54 +00:00
Denis RedozubovandGitHub 8f177cf0f2 Expose run workspace provenance (#4264)
* Model orchestrator scratch workspaces

* Address scratch workspace contract review

* Expose run workspace provenance

* Type run workspace provenance responses

* fix(daemon): refresh routine run workspace metadata

* test(e2e): allow UI P0 cold start margin

* fix(daemon): allow realpath opencode cwd aliases
2026-07-05 14:35:43 +00:00
dde6228121 feat(daemon): add OD_ALLOWED_INTERNAL_HOSTS allowlist for internal provider endpoints (#4250)
* feat(daemon): add OD_ALLOWED_INTERNAL_HOSTS allowlist for internal provider endpoints

Fixes #3225. The 0.8.0 SSRF hardening blocks provider base URLs that
resolve to private/internal ranges, with no way to opt a trusted host
out -- breaking internally-hosted LiteLLM/Ollama gateways on VPN-only
RFC1918 addresses.

Add an operator opt-in: OD_ALLOWED_INTERNAL_HOSTS exempts listed hosts
from the internal-IP guard, threaded only into user-configured provider
endpoints (connection test, model discovery, BYOK chat). The
attacker-controllable asset-download guard stays strict. Malformed and
CIDR entries are dropped with a warning rather than silently trusted.
The Settings base-URL field no longer rejects internal-IP URLs
client-side, deferring the authoritative decision to the daemon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(e2e): update BYOK base-URL P0 for relaxed internal-IP validation (#3225)

The P0 spec used an internal-IP URL as its 'invalid' example to assert the
client-side error; that URL is now accepted (the daemon owns the allowlist
decision). Switch to a non-http scheme, which is still rejected client-side.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(web): defer internal-IP base URL to the daemon in BYOK draft validation (#3225)

isValidApiBaseUrl was relaxed, but the Test and model-fetch actions gate
on validateByokDraft -> blockingByokDraftIssues, which still treated any
validateBaseUrl error (including a forbidden internal IP) as a blocking
base_url_invalid. So the Test button appeared but the action stayed
blocked client-side and never reached the daemon allowlist. Treat a
syntactically-valid forbidden URL as non-blocking here too; genuinely
malformed / non-http URLs still block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 14:35:28 +00:00
Christopher BeaulieuandGitHub f6fb7c204c fix(web): reattach spuriously-failed messages on reload when daemon run succeeded (#4607) (#4651)
* test(e2e): add red regression for #4607 artifact persistence after reload

Failing test that captures the user-reported symptom from #4607: when
a real daemon run is reloaded mid-flight, the run does not reach
'succeeded' and producedFiles is not populated on the assistant
message.

Separates the runStatus and producedFiles polls so that the race
between reattach and artifact-write finalisation is observable; the
existing bundled assertion in the prior test was masking the bug.

Refs #4607

* fix(web): reattach spuriously-failed messages on reload when daemon run succeeded (#4607)

When the SSE reconnect fallback fires while the daemon run is still
in flight (e.g. the client retried up to the reconnect limit before
any events arrived), the message's run_status is written as 'failed'.
The reconcileAssistantMessageOnRunEnd SQL UPDATE cannot rescue this
because its WHERE clause excludes the 'failed' state.

After page reload, attachRecoverableRuns skips these messages because
neither isActiveRunStatus nor shouldReplayTerminalRunMessage matches
a 'failed' entry, so no reattach fires, the artifact is never written,
and producedFiles stays empty.

Expand needsFullReplay to also cover messages with runStatus='failed'
that have a runId but no content and no producedFiles — the signature
of a spurious failure rather than a real one.  The existing status
fetch (fetchChatRunStatus) below the needsFullReplay check returns
the daemon's authoritative status; if the run was still active, it
updates the message to 'running', which triggers full reattach on
the next effect fire; if the run already succeeded, reattachDaemonRun
replays the buffered events and onDone writes the artifact.

Legitimately failed runs (with content or producedFiles) are not
affected, and completedReattachRunsRef prevents duplicate reattach.

Confirmed with: e2e/ui/real-daemon-run.test.ts lines 161, 187, 221, 278, 299 — all pass.

* review: address PR #4651 feedback

- heuristic gate: spuriouslyFailedPending is now a separate predicate
  from needsFullReplay; the recovery path is gated on the fetched daemon
  status (queued/running/succeeded) — genuine failed runs are left intact
  with their persisted error context and resumable flag

- register live-stream failures in completedReattachRunsRef via a new
  currentRunId closure variable so attachRecoverableRuns never races a run
  the client already knows failed (fixes threads-resumable-flag regression)

- error clear: setError(null) is called when spuriously-failed recovery
  moves back to running/succeeded (on detection and on reattach onDone)
  so the stale "daemon stream disconnected" banner does not persist after
  successful reattach

- e2e test tighten: capture {projectId,conversationId} BEFORE page.reload()
  so the post-reload polls assert the same conversation; use
  expectRestoredDelayedAssistantMessage with requireRunId:true to prove
  the assistant was reattached in-place rather than recreated

* review: address PR #4651 round 2 feedback

- apply status.resumable from daemon when daemon-confirmed failed
- assert runId equality post-reload to prove in-place reattach

* review: address PR #4651 round 3 — keep null-status retryable

Only add runId to completedReattachRunsRef when fetchChatRunStatus
returns an authoritative status. A null return covers any non-OK
response or fetch exception (per providers/daemon.ts:686), not
only authoritative not-found, so a transient reload-time hiccup
must remain retryable on the next effect tick.

* review: address PR #4651 round 4 — bounded retry on transient null status

When fetchChatRunStatus returns null for a spuriouslyFailedPending
row, schedule up to MAX_TRANSIENT_RETRIES (2) retries via a 3s
timer that triggers effect re-entry. After the cap is reached the
run is added to completedReattachRunsRef so we never spin
indefinitely. Timers are tracked in a ref Set and cleared on
effect cleanup.

* review: fix unbounded retry — preserve attempt counts across effect re-entry

The Map reset at effect entry zeroed retry counts every time the
timer-driven recoveryTick bumped, so attempts >= MAX_TRANSIENT_RETRIES
never held and the cap promotion to completedReattachRunsRef never
fired. Refs persist across renders, so the unconditional reset was
both redundant and the source of the unboundedness.

Also clear stale Map entries on success and on cap promotion so the
Map doesn't grow without bound for long-lived sessions.

Caught by local codex review before merge.

* review: clear endedAt on spurious-failure recovery (#4651)

streamViaDaemon.onError stamps endedAt at SSE-disconnect time. When
attachRecoverableRuns later flips the row from 'failed' back to a
live daemon status, the stale disconnect timestamp lingered and the
replay finalizers (prev.endedAt ?? Date.now()) preserved it,
skewing the assistant elapsed-time footer for exactly the runs this
PR repairs.

Clear endedAt at the recovery-detection site so the existing
finalizers naturally stamp Date.now() when replay completes.

* review: scope endedAt to spurious-failure + don't seal generic disconnects (#4651)

- endedAt: undefined clear is now gated on spuriouslyFailedPending,
  so already-succeeded rows reaching needsFullReplay via
  shouldReplayTerminalRunMessage keep their original terminal time.
  Fixes UI P0 regression where unconditional clear broke workspace
  mount and drifted persisted run durations forward.

- streamViaDaemon.onError now only adds runId to
  completedReattachRunsRef on authoritative terminal failures.
  Generic "daemon stream disconnected before run completed"
  (browser SSE reconnect-budget exhausted while daemon still
  queued/running) stays eligible for attachRecoverableRuns to
  re-query daemon authoritative status next tick. Supersedes
  follow-up issue #4717.

* review: mirror generic-disconnect gate to reattach onError (#4651)

- Bound generic-disconnect retries by reusing transientFailedRetriesRef
  / MAX_TRANSIENT_RETRIES so a flapping daemon can't trigger an unbounded
  reattach loop. Caught by local codex review before push.
- Use a separate genericDisconnectRetriesRef so null-status and
  generic-disconnect transient errors don't share one per-run budget
  and cause premature sealing. Caught by second codex pass.

* review: reset transient retry budgets on recovery (#4651)

Round-8 nettee BLOCKING + codex P2. The genericDisconnectRetriesRef
counter was only cleared on terminal outcomes, so two intermittent
SSE disconnects separated by a clean recovery still hit the cap and
sealed an otherwise-live daemon run.

Reset both transient retry budgets at three recovery sites:
- attachRecoverableRuns: after a successful fetchChatRunStatus
  for a still-active run (queued/running/succeeded).
- reattachDaemonRun onRunStatus: when the live stream reports
  running/succeeded.
- startDaemonRun onRunStatus: same, guarded by currentRunId.

Cap behavior now matches reviewer intent — fires only on
*consecutive* disconnects without intervening recovery.

* review: reset retry budgets on stream progress, not status fetch (#4651)

Round-9 nettee BLOCKING + codex P2. The round-8 fix reset
genericDisconnectRetriesRef on fetchChatRunStatus recoverable
result and on onRunStatus running/succeeded, but neither is
real stream progress: status fetch is REST-only, onRunStatus
transitions to running before any SSE data. If SSE keeps
flapping while daemon REST stays running, every reattach tick
reset the counter and the cap never fired — infinite reattach
loop.

Move resets to actual stream-data callbacks (onDelta and
onAgentEvent) in both reattach and live-stream paths. SSE
payload arrival is real recovery; a queued→running transition
is not.

Remove the three round-8 resets; add resets at four stream-data
callbacks (reattach onDelta + onAgentEvent, live-stream onDelta
+ onAgentEvent). Cap now correctly fires on consecutive SSE
disconnects without intervening data, and never fires when
the daemon is actually delivering events.

* fix(web): keep active disconnect recovery retryable

* refactor(web): share generic disconnect marker

* fix(web): preserve terminal success after disconnect

* fix(web): back off active disconnect retries

* fix: preserve terminal daemon status after disconnect

* fix: preserve recovered daemon terminal state

* fix: harden daemon disconnect recovery

* fix: clear transient disconnect error pills

* fix: use structured daemon disconnect errors

* fix: replay truncated reattach terminal success

* test: pin generic disconnect code path

* fix: preserve canceled reload recovery state

* test: align daemon disconnect mocks

* fix: replay partial live disconnect success

* fix: clear reattach streaming before status probe

* fix: preserve replay terminal status

* fix: keep live disconnect recovery eligible

* fix(web): finalize canceled reattach telemetry

* fix(web): defer reattach telemetry finalization

* test(web): lock reload recovery from spurious-failed run daemon already succeeded

Regression test for the #4607 reload-artifact-recovery path, covering the
client/daemon mismatch raised in review on #4651: a persisted assistant row at
runStatus=failed with empty producedFiles while the daemon run authoritatively
reached succeeded before the client's reload/reattach recovery pass runs.

Asserts post-reload recovery: content restored, producedFiles repopulated, run
reattached in-place (same runId + conversationId, not recreated), artifact
project file present. Distinct timing window from the existing "reload while
still running" coverage in real-daemon-run.test.ts.

Browser SSE fault-injected via page.route (connectionreset on /api/runs GET +
events); daemon process untouched; state seeded through production HTTP only, no
source-level backdoor. content:'' is informational only — the daemon persists
content via an internal write path the browser network stack (and thus
page.route) cannot intercept, so it cannot be held empty reliably at the UI
layer; the precondition gate holds on runStatus/producedFiles/runId/daemonStatus,
which reproduce the artifact-missing symptom class stably.

* fix(web): reconcile endedAt from daemon terminal time on generic-disconnect recovery

When a run's live SSE stream or a reattach stream hits the generic
daemon-disconnect error, the client stamps endedAt to the disconnect
time. After the retry budget is exhausted it probes fetchChatRunStatus;
when that probe returns an authoritative terminal status, the
reconciliation kept the stale disconnect-time endedAt via
`prev.endedAt ?? ...`, leaving persisted duration/finalization wrong
after recovery.

Adopt the daemon's authoritative terminal timestamp
(ChatRunStatusResponse.updatedAt) for endedAt in the generic-disconnect
terminal reconciliation branches:

- Live onError retry-exhaustion terminal branches -> latestRunStatus.updatedAt.
- Reattach onError retry-exhaustion terminal branches -> latestRunStatus.updatedAt.
- Reattach effect loop-level probe: onDone finalization now uses
  status.updatedAt; the canceled short-circuit now sets endedAt
  (previously left unset, preserving the stale disconnect stamp).

Adds regression tests locking endedAt to the daemon terminal time for
reattach-succeeded, reattach-canceled, and live-succeeded generic
disconnect recovery. Addresses review feedback on #4607.

* fix(web): stamp recovered endedAt from daemon terminal time, not stale snapshots (#4607)

Reload-during-active-run reattach and generic-disconnect recovery were
persisting endedAt from a non-authoritative timestamp -- the pre-reattach
`running` snapshot or the disconnect-time stamp -- instead of the daemon's
terminal `updatedAt`. This corrupted persisted run duration and reordered
conversations after recovery.

Introduce resolveTerminalEndedAt(runId, candidate) whose docblock states the
invariant: every terminal-recovery branch stamps endedAt from an authoritative
terminal updatedAt observed at the end of recovery, threaded to both the
assistant message row and updateConversationLatestRun. Route the recovery
branches through it; thread the terminal updatedAt into the conversation-level
metadata on generic-disconnect recovery; and on the reattach onError
recoverable-artifact branch overwrite endedAt only on confirmed-succeeded --
the earlier synchronous disconnect stamp otherwise poisoned a `prev.endedAt`
nullish guard, silently keeping the stale time.

Adds 4 app-local regressions pinning: reload-active endedAt advances past the
running snapshot; conversation latestRun metadata reconciles; generic-disconnect
message row + conversation both advance; and reattach-artifact-after-non-generic-
error adopts the terminal time.

Addresses review threads at ProjectView.tsx:3940, :5313, :5278.

* fix(web): recognize legacy no-code generic-disconnect events in reload recovery (#4607)

hasGenericDisconnectFailureEvent matched only the new structured
DAEMON_STREAM_DISCONNECTED code, so failed messages persisted before
this PR started tagging disconnects with a code (legacy status/error
events carrying only the detail string, no code) were skipped by
attachRecoverableRuns on reload even when the daemon reported the run
succeeded. Broaden the predicate to also match the canonical
GENERIC_DAEMON_DISCONNECT_MESSAGE detail, mirroring isGenericDaemonDisconnect
which already checks code-or-message on live Error objects.

Adds a frozen regression seeding a failed row with partial content plus a
message-only legacy disconnect event; verified red with the impl stashed
(reattach never entered) and green with the fix.
2026-07-05 14:33:23 +00:00
32011bdfcc [codex] Route BYOK API providers through OpenCode (#4876)
* feat(byok): route API providers through OpenCode

* test(web): align BYOK OpenCode expectations

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* fix(byok): preserve OpenCode media defaults

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* fix(byok): forward OpenCode media defaults

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* test(e2e): align API empty stream smoke with daemon runs

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* fix(byok): guard OpenCode API reroute

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* test(e2e): expose BYOK OpenCode in API smoke

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* fix(byok): preserve Azure and Ollama provider contracts

* fix(byok): preserve media default fallbacks

* fix(byok): keep api history for opencode routing

* fix(daemon): alias byok-opencode detection env

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* fix(daemon): isolate BYOK provider config

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* fix(web): keep Bedrock BYOK unsupported client-side

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* fix(byok): allow keyless local Ollama OpenCode runs

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* fix(web): label remaining BYOK OpenCode agents

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* fix(daemon): reject Azure BYOK without base URL

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* fix(byok): preserve keyless vLLM OpenCode runs

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

* fix(byok): omit auth for keyless OpenCode providers

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

---------

Co-authored-by: Siri-Ray <2667192167@qq.com>
Co-authored-by: Looper <looper@noreply.github.com>
2026-07-03 09:18:08 +00:00
b5b2b2c974 fix(web): stop MCP server snippet code from inheriting the inline-code chip (#4516)
In Settings -> MCP server, the generated CLI command (`claude mcp add-json …`)
renders inside a dark `<pre><code>` block, but the inner `<code>` had no style
of its own and inherited the global inline-`code` chip rule from primitives.css
(`background: var(--bg-subtle)` + `padding: 1px 5px` + rounded corners). On the
wrapped bash one-liner each wrapped segment painted its own light rounded
rectangle, reading as permanent selection highlights (#4509).

Reset the inner `<code>` to a transparent, padding-less, inherit-color/font
wrapper so it stops inheriting the inline-code chip styling and instead picks up
the dark block's own color and monospace font. Applies to every CLI client
(Claude / Codex / …) since they share the snippet block.

Add a Playwright regression check that the snippet `<code>` computed background
is transparent (it was the light `--bg-subtle` chip before this change).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 02:59:42 +00:00
AmyandGitHub cd6fb44c4d test: expand e2e coverage for main updates (#4998)
* test: expand e2e coverage for main updates

* test: remove unrelated landing contract coverage
2026-07-03 02:58:21 +00:00
ab45324124 [codex] Restore Kimi ACP and retire Gemini CLI runtime (#5023)
* fix(runtime): restore kimi acp and retire gemini cli

* test(e2e): drop retired gemini fake runtime from p0 sweep

* fix(daemon): clear retired Gemini config

Generated-By: looper 0.9.11 (runner=fixer, agent=codex)

---------

Co-authored-by: Siri-Ray <2667192167@qq.com>
Co-authored-by: Looper <looper@noreply.github.com>
2026-07-02 07:47:05 +00:00