Commit graph

1691 commits

Author SHA1 Message Date
chaoxiaoche
831208b823
Refine Studio preview interactions (#3000)
* Refine studio preview interactions

* Fix deck toolbar navigation for transform tracks

* Fix manual edit preview close

* Fix Simple Deck toolbar scrolling

* Fix preview screenshot capture

* Fix deck preview progress sync

* Refine edit target selection for grouped elements (#3068)

* Prefer child edit targets over grouped parents

* Keep edit inspector header and footer fixed

* Shorten floating edit inspector

* Show readable edit target names

* Allow dragging the floating edit inspector

* Add explicit edit inspector actions

* Show preview comment count in toolbar

* Separate annotation and comment toolbar groups

* Remove annotation toolbar divider

* Close edit inspector from footer actions

* Hide edit inspector until target hover

---------

Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local>

* Fix manual edit iframe regression test

* Fix Studio interaction review feedback

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

* Fix saved comment link classification

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

---------

Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local>
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-05-28 12:52:37 +00:00
lefarcen
4abc08bb17
fix(web): restore changes silently dropped by PR #2461 sync merge (#3210)
* fix(web): remove Ingest source panel from Automations tab (#2711)

* fix(web): remove Ingest source panel from Automations tab

The Automations tab carried a free-form "Ingest source" composer that
let users paste arbitrary content (URL, repo path, connector event,
chat snippet) and turn it into a source packet plus evolution proposals.
The form was confusing next to the routine/template flow on the same
page, exposed an internal canonicalization concept users don't need to
think about, and shipped before the surrounding evolution-proposal flow
was wired into a coherent end-to-end story.

Drop the UI surface only:

- Remove the <section className="automations-ingest"> block, the
  Template / Source / Compression / Connector selects, the title/source
  ref/content fields, the recent-packets list, and the Ingest button.
- Drop the now-dead local state (sourcePackets / sourceForm /
  ingestingSource), the patchSourceForm and submitSourceIngestion
  helpers, the SOURCE_KIND_OPTIONS / COMPRESSION_OPTIONS constants, the
  SourceIngestionForm type and DEFAULT_SOURCE_FORM, the
  /api/automation-source-packets refresh leg, and the sourcePackets
  side-write inside crystallizeRun.
- Remove the matching .automations-ingest / .automation-ingest-* CSS
  block (plus the two responsive overrides) from tasks.css.
- Delete the test case that drove the form in TasksView.templates.test.

Backend stays intact: apps/daemon/src/automation-ingestions.ts, the
POST /api/automation-ingestions route, `od automation ingest` CLI, the
routine-evolution call site, and the AutomationContentPacket /
AutomationSourceKind / AutomationTokenCompressionMode contracts all
remain, since routine scheduling still depends on them.

* fix(web): drop crystallize test assertion on removed packet list

The crystallize test was asserting that the new content packet's title
shows up on the page. That assertion only passed because the daemon
response was being side-written into the deleted sourcePackets state
and rendered in the Ingest source recent-packets strip. With that UI
removed, the packet title has no surface to land on; the proposal title
(`Skill: Artifact polish loop run`) is still asserted and remains the
real signal that crystallize succeeded.

* test(e2e): restore #2305 / #2578 e2e regressions lost in PR #2461 merge

Sync merge c14baf07d (Merge origin/main into release/v0.8.0 inside PR
#2461) took the release-side blob of these three files, silently
reverting #2305 (chore(e2e): improve test framework quality) and #2578
([codex] test(e2e): harden settings and entry regressions):

- e2e/ui/settings-memory-routines.test.ts: 363 -> 2120 lines
- e2e/ui/project-management-flows.test.ts: 758 -> 1080 lines
- e2e/ui/settings-api-protocol.test.ts: 205 -> 390 lines

Restore each file to the version at the main parent of the merge
(866661ac6). No new edits — pure restoration of merged-out content.

* chore(assets): restore #2561 / #2401 brand mark refreshes lost in PR #2461 merge

Sync merge c14baf07d also reverted these three asset blobs to the
release-side (pre-refresh) versions:

- apps/landing-page/public/apple-touch-icon.png: 6122 -> 7983 bytes (#2561)
- apps/landing-page/public/favicon.png: 916 -> 1504 bytes (#2561)
- apps/web/public/app-icon.svg: 672 -> 4964 bytes (#2401/#2439 — optically
  centered title-bar inner mark)

The companion landing changes from #2561 (sub-page-layout.astro,
index.astro, favicon.ico, logo.webp) survived the merge; only the
PNG/SVG blobs landed back at the release-side. Restore each to the
version at the main parent of the merge (866661ac6).

* test(web): drop dead automation-ingest-select.test.ts (follow-up to #2711)

#2733 (preserve ingest select chevron) and its #2609 follow-up shipped
on top of the broken main from PR #2461, which kept the Ingest source
panel that #2711 had already deleted on release. Now that the cherry-
pick of #2711 in this PR removes that panel and its .automation-ingest*
CSS, this test loses its subject (".automation-ingest-field select"
class no longer exists) and goes red.

Remove the test instead of keeping a broken assertion against deleted
markup. The shared readExpandedIndexCss helper is still used by other
style tests.
2026-05-28 12:50:05 +00:00
Stooby with two y's
9032fbb689
Fix #3169: Show confirmation toast after export/download (#3183)
* Fix #3169: Show confirmation toast after export/download

Adds a success toast ("Export started") after any export/download action
completes. The toast uses the existing Toast component with the same
pattern as commentSavedToast and templateSavedToast (2.2s auto-dismiss).

The toast fires from within fireShareExport on both sync and async
success paths, covering all export formats: PDF, PPTX, ZIP, HTML,
Markdown, image, JSX, and React HTML.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>

* Gate export toast to file export formats only

The toast was previously wired inside fireShareExport for all callers,
which incorrectly showed "Export started" for template save and deploy
modal opens. Gate to pdf/pptx/zip/html/markdown only. Also fix comma
to semicolon in types.ts.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>

---------

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-05-28 12:35:39 +00:00
lefarcen
b8cdf5f0ea
feat(mcp): generation loop + one-click Codex install (#3141)
* feat(mcp): add project creation, capability discovery, and generation tools

Lets an external coding agent (Codex, Cursor, …) drive a full design
loop over `od mcp`, not just read/write files: create a project,
discover what Open Design can make, commission a generation run, poll
it, and open the result in a browser. Complements the existing
write_file / delete_file / delete_project management tools.

New tools:
- create_project — make an empty project to generate into (start_run
  needs one). Derives a slug id from the name unless given.
- list_skills / list_plugins — discover what you can ask OD to make.
- start_run / get_run / cancel_run — commission a run (OD spawns its
  own agent), poll to completion, cancel. start+poll because MCP is
  request/response and generation is minutes-long.
- get_run / get_project now return a browser-openable previewUrl
  (entry file served raw; HTML entries render directly).

The external agent never runs a skill itself — it commissions OD to,
so the prior "skills not on MCP" boundary no longer applies.

* feat(mcp): make get_run preview hint directive

Reword the hint MCP clients receive when a run finishes so the agent
is more likely to surface the previewUrl to the user proactively —
mention the user-facing browser explicitly and call out that clients
with a built-in browser pane (e.g. Codex CLI's right-side browser)
should navigate to it directly. Also nudge start_run's hint to flag
that a previewUrl will arrive on success, so the agent knows what to
do with it before it ever sees get_run.

Pure text change; no behavior change in the tool surface or daemon.

* feat(mcp): one-click Install / Remove for Codex from Settings

Adds a toggle button on Settings → Integrations → Codex panel that runs
`codex mcp add open-design …` / `codex mcp remove open-design` via the
daemon, so users no longer need to copy TOML and paste it into
~/.codex/config.toml by hand. The copy-snippet path is unchanged and
remains the fallback when the Codex CLI isn't on PATH.

The daemon shells out to Codex CLI rather than rewriting config.toml
itself — that way we inherit Codex's own merge / dedupe / validation
rules and only track its argv. The runner is dependency-injected for
testability.

New endpoints (under /api/mcp/install/codex/*):
- GET status — probes `codex mcp get open-design`; returns
  { available, installed } so the UI can render the toggle state.
- POST — runs `codex mcp add open-design --env K=V … -- <node> <cli.js> mcp`,
  reusing the same payload as /api/mcp/install-info.
- DELETE — runs `codex mcp remove open-design`.

The web UI renders the toggle only inside the Codex client panel
(`client.id === 'codex'`). When Codex CLI is missing it shows a
disabled button with an explanatory hint instead of vanishing, so users
know why one-click isn't available.

* feat(mcp): teach agents to clarify ambiguous format requests

When the user asks for a "PPT" / "deck" / "slides" / "PDF" / "doc",
that's two very different deliverables: Open Design natively produces
browser-viewable HTML/SVG (including HTML-rendered decks), but the
user may actually want a binary .pptx / .docx / .pdf — which OD does
NOT produce and which the agent would have to export from OD's output
itself. Add a paragraph to the MCP server instructions telling the
agent to ASK which one is wanted before kicking off work, rather than
silently picking one or dual-tracking both paths.

Pure prompt-text change in the instructions block; no tool surface or
behavior change. Costs ~10 lines of session-init context (one-time
per MCP session), versus dual-tracked .pptx hedging Codex was
otherwise doing on every ambiguous request.

* feat(mcp): surface agent messages, skip OD discovery, slim list_plugins

Three fixes uncovered while exercising the full MCP-driven generation
loop end-to-end with a real Codex client. Each one is a real
blocker / footgun for the external agent.

1. get_run now includes agentMessage — the inner agent's textual
   output reassembled from the SSE event stream. Without this, runs
   that ended in a discovery-style clarifying question (e.g. a
   <question-form>) looked like "succeeded with empty output" mysteries
   to the outer agent. The hint now branches on whether previewUrl
   exists: with preview = show preview + relay agentMessage as the
   inner agent's note; no preview = relay agentMessage as the actual
   deliverable (almost always a clarifying question).

2. create_project sets skipDiscoveryBrief:true by default. The outer
   agent IS the user-facing surface for MCP-driven runs, so OD's own
   interactive discovery stage just creates a confusing
   nested-clarification loop where its question form ends up dropped
   (no files = no artifact). Better to let the outer agent gather
   requirements and pass a precise prompt or plugin to start_run.

3. list_plugins flattens the daemon's bulky 16-field plugin record
   (fsPath, sourceMarketplaceId, installedAt, …) into the few fields
   an agent actually picks plugins on: id, title, description, kind,
   tags. description / kind come from manifest.description /
   manifest.od.{taskKind,kind} which the previous pass-through dropped
   on the floor.

* feat(mcp): smart entry fallback + list_agents

Two fixes uncovered by exercising the full Codex-driven loop on a real
machine. Both close the gap between "Open Design has the data" and
"the external agent can find it".

1. get_project / get_run now fall back to scanning the project's file
   list when metadata.entryFile is missing. We hit the case where
   write_file (and a half-finished inner-agent run) put a perfectly
   viewable index.html into the project, but metadata.entryFile stayed
   null — so the outer agent got no previewUrl from MCP and resorted
   to guessing a file:// path. Priority: declared entryFile, then
   index.html anywhere, then a single .html at the project root.
   Pure read-side change; no extra fetch when entryFile is already
   set.

2. list_agents lets the outer agent stop guessing 'claude' / 'codex' /
   'gemini' for start_run.agent. The daemon already exposed
   /api/agents with 19 supported CLIs and an `available` flag. The
   MCP wrapper defaults to filtering to installed agents only (so the
   agent never picks one whose binary won't spawn), with
   includeUnavailable:true as an opt-in to see uninstalled ones plus
   their installUrl. Models truncated to 10 with modelsCount carrying
   the real total — keeps the response token-economical even for
   agents (opencode) with 100+ models.

* feat(mcp): tell the outer agent runs take 5–30 min, don't bypass

Direct response to a real Codex client observably cancelling an
in-flight run after 3 polls and substituting its own write_file
output ("文件时间戳没推进 → 我直接覆盖生成") — exactly the failure
mode this MCP surface exists to avoid.

start_run's hint and the session-init instructions block now both
state explicitly:
  - Runs typically take 5–30 minutes.
  - status:running with unchanged file mtimes is the inner agent
    thinking, NOT a hang.
  - Do not cancel_run out of impatience.
  - Do not substitute write_file as a "faster" workaround — that
    discards OD's pipeline-driven design quality.
  - Poll every 30–60 seconds; report "still working" to the user
    between polls.
  - Only call cancel_run if the user explicitly asks.

Pure prompt-text change; no surface or behavior change. Costs ~10
lines of one-time session-init tokens + ~80 more tokens per
start_run response, in exchange for the outer agent actually
trusting the run.

* feat(mcp): persist run events to disk + expose tail-able path

Closes the in-flight visibility gap that made real Codex clients
cancel a 24-min run after 3 polls and substitute their own
write_file output, simply because polling get_run showed no change.

Daemon: every SSE event is now mirrored to a JSON-Lines file at
<RUNTIME_DATA_DIR>/runs/<runId>/events.jsonl. The path is wired
through createChatRunService's new `runsLogDir` option (null
disables, preserving legacy in-memory-only behavior). statusBody
exposes the path as `eventsLogPath`. Failures are best-effort — a
broken stream destroys itself and the run keeps going on the
in-memory event log (SSE clients are unaffected).

MCP: get_run already passed statusBody through, so eventsLogPath
surfaces automatically. The new value is that get_run during a
running status now adds a directive hint telling the outer agent to
`tail -n 50 -f <path>` in its own shell to see live progress —
that's the signal that makes the agent trust the run and stop
cancelling. The succeeded-status hint mentions the path too, for
forensics. No new tool; the field rides existing get_run polls.

Spec-first throughout:
  - runs.test.ts adds 4 tests covering write-per-emit, statusBody
    field, null-runsLogDir back-compat, and the no-IO guarantee
    when persistence is disabled.
  - mcp-runs.test.ts adds 1 test for the running-status hint.

* fix(mcp): get_run hint directs callers to pass project explicitly

The success hint in get_run previously said "project defaults to this
run's project", which is misleading: get_artifact has no run context and
falls back to /api/active when project is omitted, not to the run's
project. A client following the old guidance after creating a fresh or
non-active project could fetch the wrong project's files or fail with
"no active project".

The hint now embeds the run's projectId and tells callers to pass it
explicitly: get_artifact({ project: "<id>" }). A focused regression test
in mcp-runs.test.ts verifies the hint contains the projectId and does
not contain the incorrect active-context fallback guidance.

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

* fix(contracts): add eventsLogPath to ChatRunStatusResponse

The daemon's statusBody() returns eventsLogPath but the shared DTO
lacked this field, leaving web/CLI/MCP callers without a typed
accessor.

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

* feat(mcp): bind MCP runs to OD conversations + studio deep links

Closes the last gap that made MCP-driven runs feel like a parallel
side door: the user could not see the conversation in OD's studio
page even though the run was real, finished, and had files.

Daemon side: POST /api/runs now falls back to the project's default
conversation when the caller (MCP / SDK) only supplied projectId.
It synthesizes an assistantMessageId, writes a user message with the
prompt as content, and lets the existing
`pinAssistantMessageOnRunCreate` helper create the empty assistant
row. The existing `appendMessageAgentEvent` accumulation path then
streams text_delta events into the assistant row's content — same
as the web /api/chat flow. The response body now echoes the
resolved conversationId + assistantMessageId so MCP callers can
build a deep link.

`buildMcpInstallPayload` now also surfaces `webBaseUrl` (read from
OD_WEB_PORT, the env tools-dev exports for the web listener). MCP
clients use it to build studio deep links.

MCP side: `start_run`, `get_run`, `get_project` now return a
`studioUrl` — a browser-facing OD URL pointing at the studio page
that shows the file preview AND the chat history side by side. The
hint on each tool was updated to tell the outer agent to hand
studioUrl to the user as the primary link (previewUrl falls back to
raw-file when the user only wants the rendered output). The
webBaseUrl is fetched once via /api/mcp/install-info and cached for
5s to keep per-poll cost flat; a tiny `_resetWebBaseUrlCache` export
lets tests start each case with a clean cache.

Contracts: `ChatRunCreateResponse` gains optional conversationId +
assistantMessageId; `ChatRunStatusResponse` gains optional
eventsLogPath. Both additive, no consumer breakage.

Spec-first throughout:
  - get_run includes studioUrl on success when webBaseUrl + conversationId are available
  - get_run omits studioUrl when webBaseUrl is null
  - start_run returns studioUrl and conversationId for the new run
  - get_project returns studioUrl using the project default conversation

* fix(mcp): add skill/skillId to start_run so listed skills are actionable

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

* fix(test): update mcp-get-project test to handle getWebBaseUrl fetch

The get_project handler now calls getWebBaseUrl (added with the studio
deep-link feature), which fetches /api/mcp/install-info. The test mock
only handled the /api/projects/:id URL and expected a single fetch call,
causing the assertion to fail with "called 2 times" instead of 1.

Fix: handle the /api/mcp/install-info URL in the fetch mock (returning
webBaseUrl: null), update the call count expectation to 2, and call
_resetWebBaseUrlCache in afterEach to prevent cache bleed between tests.

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

* feat(mcp): tell agents to render studioUrl as a clickable markdown link

Observed in a real Codex client: Codex received studioUrl correctly
but rendered it as inline code (gray code-span), which its built-in
browser pane does NOT make clickable. The user had to copy-paste the
URL into a browser by hand even though Codex / Cursor / Zed all
auto-link markdown `[label](url)` syntax and would navigate it in
their right-side preview pane.

The three studioUrl-mentioning hints now explicitly tell the agent
to render the URL as a markdown link (e.g.
`[Open Open Design studio](URL)`) and never as inline code or bare
text. Pure prompt-text change.

* fix(runs): resolve default agent when MCP caller omits agentId; add McpRunCreateRequest contract type

- POST /api/runs: when no agentId is provided, resolve from app-config
  or first available CLI before spawning — mirrors the pattern the
  routine handler already uses. Prevents 'unknown agent: undefined'
  failures on the create_project -> start_run(prompt) MCP path.
- packages/contracts: add McpRunCreateRequest interface for the
  projectId-only / SDK caller shape so typed callers can construct the
  request without casts. Exported via index.ts's existing chat re-export.
- packages/contracts/tests: add compile fixture verifying projectId-only,
  projectId+message, and projectId+message+agentId shapes all type-check.
- apps/daemon/tests: add mcp-runs test asserting agent arg omitted in
  start_run does not include agentId in the POSTed body.

Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)
2026-05-28 11:29:11 +00:00
Jane
9d65e26c0f
feat(landing-page): card grid + share popover for /plugins/templates/ (#3185)
* feat(landing-page): YouMind-style grid + share popover for /plugins/templates/

The list-style catalog rows that landed in PR #3010 read as a long
table of items rather than a discoverable grid. Product feedback (after
benchmarking against youmind.com/zh-CN/seedance-2-0-prompts) wanted:

- A YouMind-shape card with a top accent band, video / poster preview
  area, author + attribution row, an excerpt frame, and a primary CTA
  paired with a share button.
- Hover-autoplay on the 46 video templates whose manifest carries a
  Cloudflare Stream MP4. The data was already there since PR #3010;
  the catalog row just rendered the poster as a static `<img>`.
- A counter chip on the right of the hero that surfaces the live total
  (`Total · 231`) instead of baking the number into the H1
  ("231 runnable templates."). The hero now reads as
  `OPEN SOURCE CLAUDE DESIGN` eyebrow + `Templates.` static H1, which
  also threads the brand keyword into the page's SEO surface.
- A six-question FAQ block below the grid covering license, BYOK keys,
  contribution, and the "open source Claude Design alternative"
  positioning explicitly.

Implementation:

- `_components/template-card.astro` — new card component. Accent band
  hue is derived from `od.mode` so artifacts of the same kind get a
  consistent color (video green, prototype blue, deck mustard, image
  wisteria, hyperframes coral, audio amber, live-artifact teal),
  falling back to a stable per-index hue for unrecognized modes.
  Featured tag (yellow, on-brand) is visible when the manifest tag
  list contains `featured`; the rest of the card is locale-resolved
  via the same `resolveBundledTitle` / `resolveBundledDescription`
  helpers PR #3010 added.

- `pages/plugins/templates/index.astro` + `[kind]/index.astro` — grid
  layout (`.tpl-grid`, `repeat(auto-fill, minmax(340px, 1fr))`),
  hero with counter chip, FAQ section on the parent only. Adjacent
  filter strips share a single divider rather than drawing one each,
  so the kind + scene chip block reads as one filter unit instead of
  three stacked horizontal cuts.

- Hover-autoplay observer + share button click handler bundled into
  one `<script>` per page so they share the same boot lifecycle. The
  earlier split version dispatched `astro:page-load` from the autoplay
  block before the share block's listener attached, which dropped
  the share click on the floor; the merged init() runs eagerly when
  DOM is ready, re-runs idempotently on `astro:page-load` (Astro view
  transition), and uses `data-tpl-init` / `data-tpl-share-bound`
  markers to prevent double-binding.

- Card share is a popover, not a system share sheet. The detail page's
  `<dialog class="detail-share-dialog">` UI is reused (single instance
  per page populated per click), but `<dialog>.show()` runs in
  non-modal mode and JS positions it via `getBoundingClientRect()` to
  unfold above-right of the trigger button. Outside-click and Escape
  close the popover; the existing `data-share-copy` / `data-copy-link`
  handlers in `header-enhancer.astro` wire Copy text + Copy link
  automatically. Width tuned to 420px so it fits next to a 340px-wide
  card without spilling onto the next column.

- `_redirects` already covers retired Skills + Craft routes (PR #3010)
  so this grid pivot doesn't need new redirects.

Out of scope for this PR (kept lean):
- Multi-locale hero + FAQ copy. Hero / FAQ render in English on every
  locale right now; the `pcopy.tileTemplates` chip rail and per-card
  title/description still localize per PR #3010. Locale rollout for
  the hero + FAQ is a follow-up.
- Sort + filter buttons in the YouMind reference top-right (we still
  show artifact-kind chips only). Sort by featured weight is the
  most likely next step.
- `od.featured` weight as a featured proxy. We currently key off
  `tags?.includes('featured')` which is 0-match across the catalog
  today; promoting the numeric weight into `BundledPluginRecord` is a
  separate small commit.

`pnpm --filter @open-design/landing-page typecheck` clean (0 errors).

* feat(landing-page): localize templates chrome + FAQPage JSON-LD + hover-only autoplay

Three follow-ups Looper flagged on the YouMind-style grid (PR #3185):

- **Localizable hero / FAQ / card chrome.** PR #3185 wired the grid
  through `pcopy` for record titles + descriptions but hard-coded the
  surrounding chrome — hero eyebrow / lead / counter label, FAQ head,
  Featured tag, "Read full prompt", "Use this template", and the
  share-button `aria-label` — to English. `/ja/plugins/templates/`,
  `/zh-CN/plugins/templates/video/`, etc. now ship those strings via
  `pcopy.*` keys (`templatesHeroEyebrow`, `templatesHeroLead`,
  `templatesCounterLabel`, `cardFeaturedTag`, `cardReadFullPrompt`,
  `cardUseTemplate`, `cardShareAria`, `faqHead`, `faqItems`). English
  is the base; per-locale overrides for hero copy + 6 FAQ Q&A pairs
  remain a follow-up (the PR-#3185 "Out of scope" item), so the 17
  non-English locales fall back to English chrome instead of showing
  undefined values.

- **`FAQPage` JSON-LD entity.** The visible accordion was a SEO
  surface but `jsonLd` was still a single `CollectionPage`. Switched
  it to an array and appended a `FAQPage` whose `mainEntity` is each
  question + answer from `pcopy.faqItems`, so the structured-data
  payload search engines see and the visible <details> share one
  source of truth — drift between them is now mechanical, not
  editorial.

- **Hover-only autoplay (not viewport autoplay).** The previous
  observer played every video the moment its card scrolled into the
  viewport, which contradicted the PR's stated hover-autoplay
  contract and spawned N simultaneous decoders on a casual scroll.
  The IntersectionObserver now hydrates `data-src` -> `src` lazily
  (one-shot, then unobserve) at a 300px rootMargin; `play()` and
  `pause()` are gated to `pointerenter` / `pointerleave` (plus
  `focusin` / `focusout` for keyboard users) on the parent
  `.tpl-media` host so hovering anywhere on the preview frame
  triggers playback. Same change applied to the `[kind]` route so
  faceted pages behave identically.

Validation: pnpm --filter @open-design/landing-page typecheck -> 0
errors / 0 warnings; local dev (port 3061) renders 231 cards / 46
data-tpl-autoplay markers / FAQPage entity present in jsonLd / 6 FAQ
summaries; zh-CN locale falls back to English chrome (expected, the
locale routes themselves remain out of scope per PR #3185).

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

---------

Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 11:17:56 +00:00
Mason
50f85b509a
fix(analytics): fill run and feedback metadata (#3194)
* fix(analytics): fill run and feedback metadata

* fix(analytics): map feedback API providers
2026-05-28 11:05:56 +00:00
Denis Redozubov
71ad9eb292
fix(media): enforce legacy media policy for run tokens (#3205) 2026-05-28 11:02:04 +00:00
feliciaZH
b746efefe2
fix: clear selected preview comments (#3144)
* fix: The "clear" button for comments is not functioning; the comments no longer have serial numbers.

* fix: The active pin always renders {visibleComments.length + 1}, but showActivePin (= commentCreateMode) is also true while editing an existing comment: onOpenComment at line 6821 calls setCommentCreateMode(true) and setActiveCommentTarget(snapshot) against the saved comment the user just clicked. In that path the overlay now stamps a stale number on top of an existing saved marker (e.g. clicking the pin showing 2 paints an additional 3 at the same position), which contradicts the invariant this PR is restoring — that preview-area numbers match the side-panel numbers.

---------

Co-authored-by: 郑惠 <14549727+felicia-study@user.noreply.gitee.com>
2026-05-28 10:56:21 +00:00
Lucas-FManager
ed16de6f92
Fix workspace tab separator artifact (#3105)
Co-authored-by: Lucas-FManager <luong.nguyen188@gmail.com>
2026-05-28 10:41:34 +00:00
Denis Redozubov
f70fa0eb35
docs(media): describe external media composition (#3201) 2026-05-28 10:41:02 +00:00
Kushaal Kankane
d4d4ee245a
feat(designs): implement empty state with CTA for new projects and update translations (#3060) 2026-05-28 10:24:10 +00:00
lefarcen
551f967d2c
ci(agent-pr-explore): rewrite prompt — non-lazy disposition + mandatory probe list (#3156)
* ci(agent-pr-explore): rewrite prompt — non-lazy disposition + mandatory probe list

Background: on PR #2355 (large AMR runtime add) the agent stopped at smoke level
because the positive path was gated on a missing `vela` binary. The old prompt
explicitly instructed "if setup prerequisites block, return inconclusive
immediately" + "do not spend more than two attempts on test data" + "do not run
arbitrary host shell commands", which made the agent give up rather than:
- use the PR's own `tests/fixtures/fake-vela.mjs`
- set the `VELA_BIN` env the runtime reads
- probe `/api/integrations/vela/*` directly via fetch

This rewrite shifts disposition + adds 4 structured unblock steps:

- **Mindset**: each /explore is a precious, expensive run; be thorough, not lazy.
- **STEP 0** Read PR body for `## Test Plan` section — declared cases = MUST-COVER.
- **STEP 1** Extract diff-driven probe list (new routes / components / env vars /
  fixtures / CLI flags). Anything skipped requires explicit written reason.
- **STEP 2** Before giving up, try (a) PR-provided fixtures, (b) build minimal stub
  inside container, (c) probe APIs directly via page.evaluate fetch,
  (d) search repo / related PRs / docs for unknown terms.
- **STEP 3** 4-7 cases for substantive PRs (was hard cap 3).
- **STEP 4** Login / multi-tab / OAuth — use Playwright multi-page handling;
  read creds from env, never echo.
- **SECURITY** strengthened: env vars matching common secret patterns are
  confidential; never echo / log / write to file / page.evaluate / report.
- **Report** new required §Mitigations Attempted for Inconclusive verdicts —
  must list what was tried + why each didn't unblock.

Kept unchanged: 3-min keepalive constraint, untrusted-data rule, no-host-shell
rule, report markdown structure (/⚠️// + case emoji).

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

* ci(agent-pr-explore): truncation-aware + soft-request protocol + capability fix

Addresses review feedback from @mrcfps:

(1) STEP 1 — diff probe list was instructed to enumerate "every new
    route/component/env/fixture/flag" without acknowledging that the harness
    already truncates the diff upstream (file_patch_max_chars + context_max_bytes).
    On exactly the large PRs this prompt targets, the agent only sees a slice.

    Fix: prompt now explicitly tells the agent the context MAY be truncated and
    to (a) note the truncation in §🧭 Scope and (b) emit §📎 Needs to ask the
    maintainer to attach the missing source files into the private workspace
    for the next run.

(2) STEP 2 — old text told the agent to "create stubs inside the sandbox
    container", "rewire env / PATH", and "run gh / grep searches". The harness
    does not expose docker exec or arbitrary shell to the agent (capabilities
    are fs:write on host + Playwright on host driving the dockerized app via
    HTTP). The instructions promised things the agent literally cannot do.

    Fix: STEP 2 now spells out the actual capabilities (host fs:write, Playwright
    page.evaluate / page.request, host-side $WORKSPACE_DIR if maintainer pre-
    attached one). The "unblock by stub" path is rewritten honestly: build a
    host-side stub if useful, but acknowledge container env is fixed at
    docker run and signal what's needed via §🔑 Needs / §📎 Needs for the next
    iteration. The "search repo for unknown term" step (which required gh/grep)
    is dropped in favor of using $WORKSPACE_DIR materials.

(3) Soft-request protocol (new):

    The agent is READ-ONLY for secrets and workspace — it cannot self-attach.
    But it can SIGNAL what was needed via two new optional report sections:

    - §🔑 Needs — secret request ("VELA_RUNTIME_KEY: needed to verify ...")
    - §📎 Needs — workspace file request ("amr-auth-spec.md: clarifies ...")

    The dashboard (synclo platform; see nexu-io/synclo#79 RFC §6.8) will parse
    these structurally and surface as one-click attach hints to the maintainer
    on the run detail screen. Pure passive signal; no auto-action; zero prompt-
    injection risk (no code path takes the values).

    Hard rules:
    - No pasting of existing secret values (security)
    - Each item MUST tie back to a specific blocked case in §🧪 Cases — not
      speculative
    - Workspace privacy: agent may reference workspace files BY PURPOSE
      ("verified positive-1 from test-plan.md") but NEVER paste their content
      into the report

This commit is non-trivial (~65 lines net) but the changes are tightly scoped:
honesty about capabilities + a new signaling channel that replaces the
impossible direct-action promises.

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

* ci(agent-pr-explore): remove gh-search + container-exec language from prompt

MINDSET bullet: replace 'gh search issues/prs/code' with in-scope
materials (PR body, diff context, workspace files) plus
page.request/page.evaluate probes, matching actual harness capabilities.

SECURITY bullet: replace the contradictory 'You may run commands INSIDE
the sandbox container' with a clear statement that the agent has no shell
or container exec access, only the Playwright browser.

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

* ci(agent-pr-explore): fix Needs section misuse and unawaited fetch body

Move fixture env-var wiring request from §🔑 Needs to §📎 Needs and
remove the concrete host path from the example; §🔑 Needs is
secret-name-only and must not carry filesystem paths. Await r.text() in
the page.evaluate fetch example so the body field resolves to a string
instead of an unresolved Promise.

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

* ci(agent-pr-explore): broaden 📎 Needs to cover env/config wiring alongside file attachments

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

* ci(agent-pr-explore): fix step-2b Needs routing and split AMR_USER/AMR_PASS example

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

* ci(agent-pr-explore): fix mindset bullet — env var cannot be set mid-run

Replace "set the env var" in the MINDSET mitigations list with
"identify the env var and request the needed startup wiring in §📎 Needs"
to match the actual capability boundary: container env is fixed at
docker run time and cannot be changed by the agent mid-run.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:51:19 +00:00
Bernardxu123
0f1c95e1de
feat(agents): add DeepSeek Reasonix CLI support via ACP protocol (#2952)
* feat(agents): add DeepSeek Reasonix CLI support via ACP protocol

* fix(auth): tighten Reasonix auth detection + add live model discovery

- Anchor isReasonixAuthFailureText to Reasonix-specific markers
  (~/.reasonix/config.json instead of generic api_key matches)
- Add fetchModels with detectAcpModels for live model discovery
  in the agent picker (matches kimi/hermes/kilo/kiro/vibe pattern)

* feat(web): add Reasonix short description in agent picker

---------

Co-authored-by: Bernardxu123 <Bernardxu123@users.noreply.github.com>
2026-05-28 09:36:22 +00:00
Nicholas-Xiong
ae22e9f851
fix: prevent toolbar buttons from wrapping when video preview opens (#2896)
Fixes #2883

When the video preview panel opens, the Design Files toolbar buttons
would wrap into multiple rows due to reduced available width.

This fix adds:
- flex-wrap: nowrap to keep buttons on a single row
- flex-shrink: 0 to prevent buttons from being squeezed

The action buttons now remain stable and aligned even when the preview
pane reduces the available width.

Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-05-28 09:25:10 +00:00
Denis Redozubov
c847ace554
Add run-scoped media execution policy (#3106)
* feat(contracts): add run media execution policy

* feat(daemon): enforce run media execution policy

* test(daemon): cover media execution policy gates
2026-05-28 09:19:40 +00:00
kami
0bfb4803e7
feat(daemon): add Phase 2C CLI wrappers (#2179)
* feat(daemon): add phase 2c cli wrappers

Co-authored-by: multica-agent <github@multica.ai>

* fix: handle desktop-gated CLI imports

Co-authored-by: multica-agent <github@multica.ai>

* fix: pass sidecar ipc path to agent wrappers

Co-authored-by: multica-agent <github@multica.ai>

* fix: make agent wrapper env explicit

Co-authored-by: multica-agent <github@multica.ai>

* fix(daemon): preserve CLI import and diff edge cases

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-28 09:08:31 +00:00
lefarcen
9a4816b101
feat(plugins): site-wide plugin detail pages, share-to-site links, landing deploy trigger (#2999)
* feat(plugins): site-wide plugin detail pages, share-to-site links, landing deploy trigger

Why: a merged plugin PR didn't redeploy the landing site (plugins/** was missing from the deploy paths), and the desktop Share menu copied a local/404 link instead of the public marketplace URL. The landing plugin routing left by the detail-page rework also 404'd: the locale listing's cards used a multi-segment href while detail pages were single-segment, and only 388 bundled _official plugins had pages.

What changed:
- Deploy: landing-page deploy/ci trigger on plugins/**, and skip the slow previews step on an exact cache hit (cache key aligned across both workflows so a PR-built cache is reused by main).
- Share URL: packages/contracts/plugin-url.ts owns the single-segment plugin URL scheme; the web Share menu and the landing site both derive links from it. Web links now point at https://open-design.ai/plugins/<slug>/.
- Full detail coverage: detail pages now cover all 403 local plugins (_official incl. atoms + community), each rendered from its local manifest. Fixes the locale-listing 404s and the community manifest-name/catalog-id (- vs /) mismatch.
- Self-host: daemon exposes OD_SITE_ORIGIN via /api/app-config; web falls back to the canonical origin until the daemon answers.

Validation: pnpm guard, pnpm typecheck (all packages), contracts + web tests green, and a full build E2E confirming all 403 catalog ids and locale-listing cards resolve to built detail pages (0 missing).

* chore: retrigger CI

* ci(landing): carry plugins/** trigger + previews cache-hit into #2994 split workflows

Merged origin/main, which split landing deploy into staging + manual production (#2994). git auto-migrated my landing-page-deploy.yml changes into landing-page-staging.yml via rename detection (plugins/** path, fallback-preview-card.ts cache key, cache-hit skip all carried). The new manual landing-page-production.yml didn't have them, so add the previews cache-key alignment + cache-hit skip there too (plugins/** path is N/A — production is workflow_dispatch only).

* fix(ci): wrangler-action uses pnpm so it tolerates landing's workspace dep

This PR added @open-design/contracts (workspace:*) to apps/landing-page/package.json so the landing site can share the plugin-url slug rules. But the landing deploy/preview steps run cloudflare/wrangler-action with packageManager: npm in workingDirectory apps/landing-page, and 'npm i wrangler' chokes on the workspace: protocol (EUNSUPPORTEDPROTOCOL), failing 'Validate landing page'. Switch all three landing wrangler-action steps (staging / ci preview / production) to packageManager: pnpm, which is workspace-aware.

* test(e2e): bundled plugins now offer the README badge

After this branch, buildPluginShareUrl returns a public open-design.ai link for bundled plugins (not just official-marketplace ones), so the home-starter share menu now shows 'Copy README badge'. Update the assertion from toHaveCount(0) to toBeVisible().

* fix(landing): drop @open-design/contracts dep, use a landing-local slug helper

Per review on #2999: the marketing site must not import @open-design/contracts (AGENTS.md boundary — it's the web/daemon product-runtime contract layer). Move the slug/path helpers into landing-local app/_lib/plugin-slug.ts; the web client keeps contracts' plugin-url. The two derive the same scheme and are verified in lockstep by the e2e route check (403 share URLs -> 403 detail pages, 0 missing). landing no longer has a workspace dep, so revert the wrangler-action packageManager back to npm.

* fix(landing): include plugins/_official in previews cache key

Per review on #2999: generate-previews.ts builds bundled-plugin preview jobs from plugins/_official/**/open-design.json and renders fallback cards from manifest fields (title/description/mode/scenario/tags). With plugins/** now triggering the workflow but the cache key not hashing plugin inputs, a plugin-only PR/merge could exact-hit an old cache and skip the preview regen, shipping with a stale or missing /previews/plugins/<manifest-id>.png. Add plugins/_official/** to the cache key in all three landing workflows (ci, staging, production). community is not currently covered by generate-previews so its glob is omitted.

* fix(plugins): include community marketplace installs in share gate

hasPublicPage now covers sourceMarketplaceId === 'community' so the
README badge and public detail link surface for community installs.
Community manifest names carry a community- prefix that diverges from
the landing-page route slug, so URL derivation uses sourceMarketplaceEntryName
(community/<folder>) instead — pluginDetailSlug takes the last segment,
matching the /plugins/<folder>/ route the landing page emits.
Adds component tests for buildPluginShareUrl, badge copy, and the
Open-in-marketplace link for a community/registry-starter record.

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

---------

Co-authored-by: mrcfps <mrc@powerformer.com>
2026-05-28 09:07:12 +00:00
hahalolo
92f7ff86b5
fix(web): constrain staged attachment tray height (#3168)
* fix(docker): fix container startup crash due to missing OD_API_TOKEN

* fix(docker): forward OD_API_TOKEN to fix docker container boot loop

* fix(docker): enforce non-empty OD_API_TOKEN for docker-compose

* fix(deploy): automate OD_API_TOKEN generation in installer and close compose loop

* docs(readme): guide manual deployment users to configure OD_API_TOKEN

* docs(readme): align working directory paths for manual deployment instructions

* docs(readme): align working directory paths for manual deployment instructions

* docs(readme): restore git clone context for first-time users

* fix(web): Limit the maximum height of the StagedAttachments component.

related issue 3155

* fix(web): limit attachment area overflow to vertical only
2026-05-28 08:56:19 +00:00
leessju
381e9a96e2
Make share deploys visibly complete (#2843)
* Make share deploys visibly complete

Share deploys were uploading only the referenced entry graph, so sibling screens could fall through to provider fallback pages after deployment. They also completed silently except for the result link block inside the deploy dialog, leaving users unsure whether a redeploy finished.

This includes visible files for Open Design-managed projects in real deploy/preflight payloads while preserving the selected entry as provider-root index.html. Linked-folder projects stay on the referenced-file graph so repo files that are visible in the file panel, like README.md or src/**, do not become public by accident. The web UI also shows a localized success toast at the top of the app after a successful Vercel or Cloudflare Pages upload.

Constraint: Cloudflare Pages Direct Upload serves missing files through its fallback behavior, so deployment payload completeness must be handled before upload.

Constraint: Linked-folder projects can expose arbitrary repository content through the file panel, so whole-project deploy expansion is limited to Open Design-managed project directories.

Rejected: Reintroduce an entry-file dropdown | users wanted full project deployment semantics rather than selecting a root-only artifact.

Rejected: Upload every visible linked-folder file | would make non-runtime repo content publicly reachable after Share deploy.

Confidence: high

Scope-risk: moderate

Directive: Do not remove the selected-entry-to-index.html mapping; it keeps alternate entries like index-v1.html deployable as the root without overwriting them with the launcher.

Directive: Do not expand linked-folder deploys beyond referenced web assets without an explicit user opt-in and review of the privacy model.

Tested: pnpm --filter @open-design/daemon test tests/deploy.test.ts tests/deploy-routes.test.ts

Tested: pnpm --filter @open-design/web test tests/components/FileViewer.test.tsx

Tested: pnpm --filter @open-design/web typecheck

Tested: pnpm guard

* fix(web): gate share-deploy ready hint on actual ready state

The 'Ready · Deployed URL' hint was unconditionally rendered whenever
deployResultCards was non-empty, so a successful deploy that came back
as link-delayed or protected showed contradictory copy next to the
'Public link pending' / 'Deployment protection enabled' badge.

Render the hint only when deployResultState(activeDeployment?.status)
is 'ready' so the success line stays consistent with the badge below.

---------

Co-authored-by: nicejames <nicejames@gmail.com>
2026-05-28 08:56:11 +00:00
hahalolo
fe24c8addf
chore: remove dead inline /api/agents route handler in server.ts (#2945)
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-05-28 08:54:11 +00:00
初晨
a7e7d5db18
Fix memory action alignment (#3175) 2026-05-28 08:53:01 +00:00
初晨
693176457e
fix(daemon): skip chat probe for SenseAudio media models (#3181) 2026-05-28 08:52:34 +00:00
hahalolo
a4c447eea6
Fix staged preview modal overlay stacking (#3186)
* fix(docker): fix container startup crash due to missing OD_API_TOKEN

* fix(docker): forward OD_API_TOKEN to fix docker container boot loop

* fix(docker): enforce non-empty OD_API_TOKEN for docker-compose

* fix(deploy): automate OD_API_TOKEN generation in installer and close compose loop

* docs(readme): guide manual deployment users to configure OD_API_TOKEN

* docs(readme): align working directory paths for manual deployment instructions

* docs(readme): align working directory paths for manual deployment instructions

* docs(readme): restore git clone context for first-time users

* fix: portal staged-preview-modal to document.body to escape .composer stacking context

The .staged-preview-modal was rendered inside .composer (position:relative;
z-index:1), which created a local stacking context. The modal's z-index:1200
was scoped within .composer's context instead of the root stacking context,
preventing it from properly covering .app-chrome-header when active.

Fix by rendering the modal via createPortal to document.body, consistent
with other modals in the codebase (ConversationsMenu, FileViewer, etc.).

related issue 3154
2026-05-28 08:33:35 +00:00
leessju
ece3d71cdd
fix(web): guard ChatPane scroll setState to prevent Maximum update depth error (#3187)
The scroll handler in ChatPane called setScrolledFromBottom on every
scroll event. During streaming, programmatic scrollTop assignments
(followLatestIfPinned via rAF + ResizeObserver) emit synchronous scroll
events while React is still committing prior updates, scheduling a
setState per tick. React eventually trips its update-depth guard with
'Maximum update depth exceeded' (issue surfaces in Next.js 16
Turbopack dev consoles).

Use a functional updater that returns prev when the boolean is
unchanged so React skips the re-render entirely. This breaks the
cascade at the call site without touching the auto-follow logic.

Co-authored-by: nicejames <nicejames@gmail.com>
2026-05-28 08:32:28 +00:00
Tuola-waj
d9623da4af
skills: enhance frontend design (#3171)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
actionlint / Lint GitHub Actions workflows (push) Failing after 2s
ci / Detect CI change scopes (push) Successful in 0s
landing-page-ci / Validate landing page (push) Failing after 1s
landing-page-staging / Deploy landing page to staging (push) Has been skipped
nix-check / build (push) Failing after 1s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 2s
ci / Workspace unit tests (push) Failing after 1s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
Co-authored-by: tuolaji <tuola@tuolajideMacBook-Air.local>
2026-05-28 07:29:53 +00:00
David Z
efa808f1db
fix(i18n): refresh French locale (#2963) 2026-05-28 06:55:57 +00:00
Marc Chan
338cb4d423
fix(platform): support live system proxy changes (#3093)
* fix(platform): support live system proxy changes

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Honor lowercase proxy env vars within a single source before merging proxy-aware envs.\n\nGenerated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Refresh provider request proxy env on each dispatcher creation and cover it with a focused regression test.

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): enable node env proxy for user proxy vars

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(platform): support live system proxy changes

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)
2026-05-28 06:11:47 +00:00
open-design-bot[bot]
9de5ecd87c
docs(readme): refresh contributors wall (#3161)
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
2026-05-28 05:38:40 +00:00
lefarcen
df8a0faff6
feat(runtimes): register AMR (vela) as an ACP stdio agent (#2355)
* feat(runtimes): register AMR (vela) as an ACP stdio agent

AMR is the vela CLI's ACP runtime mode. `vela agent run --runtime opencode`
speaks ACP JSON-RPC over stdio (see vela's
`specs/current/runtime/manual-agent-run-openrouter.md`); per
`docs/new-agent-runtime-acp.md` we expose it through the same `streamFormat:
'acp-json-rpc'` transport that already powers Hermes, Devin, Kimi, etc.

The new `defs/amr.ts` is the entire wiring — `buildArgs` returns
`['agent', 'run', '--runtime', 'opencode']`, `fetchModels` reuses
`detectAcpModels`, and the fallback list seeds the OpenRouter ids vela's
e2e baseline uses. `executables.ts`/`app-config.ts`/`metadata.ts` get the
matching `VELA_BIN`/`VELA_LINK_URL`/`VELA_RUNTIME_KEY`/`VELA_OPENCODE_BIN`
allowlist + install/docs URLs, so users can configure the per-agent env in
Settings without leaking into other adapters.

Coverage: `tests/fixtures/fake-vela.mjs` is a minimal ACP stub that returns
the documented `initialize` / `session/new` / `session/set_model` /
`session/prompt` shapes; `tests/amr-acp-integration.test.ts` spawns it via
`child_process.spawn` and drives a full turn through `attachAcpSession` and
`detectAcpModels`, so the ACP transport contract for AMR is end-to-end
verified locally even before a real `vela` binary is installed.

Validated:
- pnpm guard
- pnpm typecheck (all workspace projects)
- pnpm --filter @open-design/daemon test (2881/2881)

Deferred: real OpenRouter-backed turn through a built `vela` binary —
the runtime def needs no changes for that path, only `VELA_RUNTIME_KEY`
and `VELA_LINK_URL` in env (or Settings).

* fix(runtimes/amr): pin a concrete default model and bare openai ids

End-to-end validation against a freshly-built `vela` (nexu-io/vela@main)
+ OpenRouter surfaced two contract details the first AMR runtime def
got wrong:

1. vela rejects `session/prompt` with `session/set_model must be called
   before session/prompt`. attachAcpSession in apps/daemon/src/acp.ts
   skips set_model whenever the picked model is the synthetic 'default'
   id, so AMR's fallback list must NOT include DEFAULT_MODEL_OPTION. The
   def now ships a concrete `gpt-5.4-mini` as both `fetchModels`'
   default option and `fallbackModels[0]`, which makes attachAcpSession
   always send a real `session/set_model` for AMR turns.

2. `vela --runtime opencode` auto-prepends `openai/` to whatever modelId
   it forwards to opencode's openai provider. With OpenRouter-style ids
   like `openai/gpt-5.4-mini`, opencode receives the double-prefixed
   `openai/openai/gpt-5.4-mini` and replies `ProviderModelNotFoundError`.
   The new fallback list ships the bare ids opencode's openai registry
   actually knows about (gpt-5.4, gpt-5.4-mini, gpt-5.4-fast, etc.).

Stub + tests:
- tests/fixtures/fake-vela.mjs now enforces the set_model gate the same
  way real vela does, so a regression that silently goes back to
  model: 'default' would surface as a fatal error in tests instead of a
  hidden production failure.
- tests/amr-acp-integration.test.ts pins both contracts: no 'default' /
  no 'openai/' prefix in fallbackModels, and a negative case that
  asserts session/prompt fails when no model is set.

Adds `apps/daemon/scripts/verify-amr-real-vela.mjs` — a small dev-time
runner that drives `attachAcpSession` against a real `vela` binary and
prints the daemon's chat events, so future protocol drift can be checked
against an actual OpenRouter call.

Verified locally: `vela agent run --runtime opencode` + OpenRouter
returns the prompted string ("AMR-E2E-PASS") through the full daemon
pipeline; daemon test suite stays 2883/2883.

* fix(runtimes/amr): substitute concrete model when chat run sends 'default'

A plugin-driven AMR run from the UI surfaced a real-world hole in the
prior commit:

  json-rpc id 3: session/set_model must be called before session/prompt

The Default-design-router plugin (and any caller that doesn't pin a
real model) sends `model: 'default'` straight through, which the AMR
runtime def cannot accept — vela rejects `session/prompt` without
`session/set_model` and attachAcpSession skips set_model whenever
model === 'default'. Just leaving DEFAULT_MODEL_OPTION out of the
adapter's `fallbackModels` is not enough: the chat-run handler in
server.ts still forwarded 'default' verbatim.

This adds `resolveModelForAgent(def, resolved, env?)` as the
single source of truth for the substitution:

  1. If the caller picked a real id, pass it through.
  2. Else, if `def.defaultModelEnvVar` is set and the daemon process
     env has a non-empty value for it, return that (operator escape
     hatch — see below).
  3. Else, if the def's `fallbackModels` does NOT contain a 'default'
     id, return `fallbackModels[0].id`.
  4. Else, return the original value (the historic shape — defs that
     list 'default' themselves are untouched).

AMR sets `defaultModelEnvVar: 'VELA_DEFAULT_MODEL'`, so when
opencode's openai-provider registry deprecates `gpt-5.4-mini`
upstream, an operator can swap the fallback id without a code change
by exporting `VELA_DEFAULT_MODEL=gpt-5.5` before launching tools-dev
/ od. Worth noting the env var must live in the daemon's `process.env`
(Settings-UI per-agent env values only reach the spawned child, not
the daemon's resolver) — the new field's docblock spells this out.

Coverage:
- `tests/runtimes/resolve-model.test.ts` — 8 unit tests covering all
  four resolver branches plus the env-override happy path / fallback /
  ignore-when-user-picked-a-real-id case.
- `pnpm --filter @open-design/daemon typecheck` clean.

* chore(runtimes/amr): move AMR to the top of the base agent list

So `AMR (vela)` shows up first in the agent picker / status views,
ahead of claude / codex. Pure ordering change; no behavior delta.

* feat(amr): Sign-in / Sign-out button on the AMR Settings card

The first half of the AMR work assumed the operator would set
VELA_RUNTIME_KEY / VELA_LINK_URL on the daemon process and never
surfaced login state to users. This adds the missing UX so a fresh
install can drive the full path from Settings:

  - GET  /api/integrations/vela/status   reads ~/.vela/config.json
    for the active profile and returns { loggedIn, profile, user }
    (without leaking the runtime/control keys themselves).
  - POST /api/integrations/vela/login    spawns `vela login` once
    (409 if one is already in flight). The vela CLI opens the user's
    browser to the device-authorization page itself — Open Design
    only needs to kick the subprocess off.
  - POST /api/integrations/vela/logout   removes ~/.vela/config.json
    so the next status read returns logged-out.

`AmrAgentCard` is a dedicated agent-card component for AMR because
the existing `<button>` row can't host an interactive sub-control
(nested interactive elements). It polls /status after a login click
until the daemon reports loggedIn=true (or 5 minutes elapse), and
exposes a Sign-out action on hover. Other adapters (claude, codex,
hermes, …) keep their existing `<button>` card.

i18n: 8 new keys (settings.amrLogin / Logout / LoggingIn / etc.)
added to en + zh-CN. Other locales spread `en` and inherit the
English copy until translations land.

Coverage:
- `tests/integrations/vela.test.ts` pins the config.json reader
  against a tmp HOME — including the negative case where a profile
  has user info but no runtimeKey (still logged-out), and the
  secret-leak guard ("rt-secret-*" must not appear in the projection
  payload).
- `tests/components/AmrAgentCard.test.tsx` covers all four UI
  states (logged-out, logging-in, logged-in, logging-out) plus the
  click-propagation invariant the divergent card was built to keep.

`pnpm --filter @open-design/daemon test` 2901 / 2901 passing.
`pnpm --filter @open-design/web test` 1719 / 1719 passing.
`pnpm typecheck` + `pnpm guard` clean.

Dev script side-effects: `apps/daemon/scripts/verify-amr-real-vela.mjs`
no longer requires both VELA_RUNTIME_KEY and VELA_LINK_URL — if
VELA_PROFILE is set, the vela CLI is allowed to resolve credentials
from `~/.vela/config.json`. Added the two AMR `.mjs` fixtures to
`scripts/guard.ts` allowlist with the executable-fixture / dev-runner
rationale.

* fix(connection-test): substitute model for AMR before attachAcpSession

The chat-run path in server.ts already routes the requested model through
`resolveModelForAgent` so AMR / vela (whose CLI demands an explicit
`session/set_model` before `session/prompt`) gets the def's first
concrete fallback id when the chat run ships `model: 'default'`.
`connectionTest.ts` was wiring `attachAcpSession({ ..., model: model ?? null })`
directly, which made the Test Connection button on the AMR Settings
card deadlock with the same `session/set_model must be called before
session/prompt` error the chat-run path already handles — surfaced as a
permanent "Testing connection…" spinner in the UI.

Reuse the same helper here so Test Connection mirrors chat-run behavior.

* test(amr): three-layer end-to-end coverage for the AMR login + turn flow

The PR up to this point shipped runtime + UI code with unit-level Vitest
coverage. This commit adds the cross-layer regression net the live demo
relied on:

1. apps/daemon/tests/integrations/vela.routes.test.ts (HTTP, Vitest)
   Spins up the real daemon Express app via `startServer({port:0,...})`,
   persists `agentCliEnv.amr.VELA_BIN = <fake>` into app-config.json,
   and exercises every /api/integrations/vela/* endpoint against the
   extended fake-vela stub:
     - status reads ~/.vela/config.json under various states
     - login spawns the fake, waits for config.json to appear, returns
       pid + startedAt + profile
     - 409 already-running guard with the stub's delay knob
     - logout removes the file (idempotent)
     - secrets (runtimeKey / controlKey) never leak in the projection
     - login → status round-trip flips loggedIn=false → true

2. e2e/tests/amr/turn.test.ts (tools-dev orchestrated, Vitest)
   Boots a namespaced daemon + web pair through `createSmokeSuite`,
   inlines a self-contained fake `vela` binary that handles BOTH
   `vela login` (writes ~/.vela/config.json) and
   `vela agent run --runtime opencode` (ACP stdio with the
   `session/set_model must precede session/prompt` gate the real binary
   enforces), then drives a complete /api/runs lifecycle for
   `agentId: 'amr', model: 'default'` and asserts the assistant message
   captures the fake's streamed text. This is the test that would have
   surfaced today's plugin-default-model regression (the `set_model
   before prompt` error) at PR time instead of demo time.

3. e2e/ui/amr-login-pill.test.ts (Playwright)
   Mocks /api/agents + /api/integrations/vela/{status,login,logout}
   to drive the Settings AMR card through the full Sign in → Signed in
   → Sign out cycle. Pins the AmrLoginPill polling contract and the
   aria-label semantics (the pill's accessible name is "Sign out" once
   logged in, regardless of which label the hover-state text shows).

fake-vela.mjs extensions:
   - Handles `vela login` argv by writing
     ~/.vela/config.json for the active VELA_PROFILE and exiting 0 —
     mirrors real vela's on-disk side-effect without the device-auth
     loop.
   - FAKE_VELA_LOGIN_DELAY_MS knob so route tests can observe the
     in-flight state of the spawn lifecycle.
   - FAKE_VELA_LOGIN_USER_EMAIL / _USER_PLAN to assert the surfaced
     user fields end-to-end.

Validated:
   - `pnpm guard` + `pnpm typecheck` (all workspace projects)
   - `pnpm --filter @open-design/daemon test`: 2998 / 2998 passing,
     including the new 8-test integration suite.
   - `cd e2e && pnpm test tests/amr`: 1 / 1 passing.
   - `cd e2e && pnpm exec playwright test ui/amr-login-pill.test.ts`:
     1 / 1 passing (6.7s).

* feat(amr): package native cli and refine login ui

* feat(amr): wire vela cli beta packaging

* docs(amr): document vela ci packaging review

* docs(amr): refine vela ci integration review

* fix(ci): refresh nix pnpm dependency hashes

* fix(pack): clean up Vela CLI packaging

* fix(pack): bundle Vela CLI support files

* fix(amr): recover login attempts from stale auth state

* test: expand AMR and automations coverage

* fix(amr): address review follow-ups

* test(web): align tasks fixtures with contracts

* fix(daemon): type wildcard route params

* fix(ci): refresh PR merge validation

* fix(amr): clear env credentials on logout

* feat(settings): inline local CLI model configuration

* fix(amr): recognize daemon env credentials

* [codex] Fix Vela companion packaging (#2979)

* Fix Vela companion packaging

* Update Nix pnpm dependency hashes

* [codex] Surface AMR account failures (#2980)

* fix: surface AMR account failures

* fix: cover AMR recovery error guidance

* chore: bump beta base version to 0.8.1 (#2990)

* Fix AMR profile and packaged runtime review issues

* Detect packaged AMR OpenCode companion tree

* feat(web): polish AMR frontend flows

* Polish AMR onboarding card

* fix: read AMR login state from dot-amr config (#3048)

* test: tighten AMR credential and packaging coverage

* test: restore AMR executable test env helper

* [codex] Fix packaged mac Dock identity and AMR label (#3076)

* Fix packaged mac sidecar Dock identity

* Rename AMR assistant label

* Fix AMR live models and dot-amr login state (#3073)

* fix: read AMR login state from dot-amr config

* fix: load live AMR models before runs

* fix: point AMR onboarding link to production wallet

* fix: address AMR model review feedback

* fix: persist live AMR model fallback

* [codex] Fix AMR link catalog model ids (#3088)

* Fix packaged mac sidecar Dock identity

* Rename AMR assistant label

* Fix AMR link catalog model ids

* Fix AMR model normalization typecheck

* Use live AMR model for default runs

* fix: polish AMR runtime settings UI

* Accelerate AMR startup defaults (#3092)

* Surface AMR insufficient balance wallet URL (#3099)

* fix(web): polish onboarding controls (#3112)

* fix(web): show CLI scan loading state

* Avoid duplicate AMR wallet recharge links (#3117)

* Avoid duplicate AMR wallet recharge links

* Use Vela CLI 0.0.3 test package

* chore(nix): refresh pnpm deps hash

* Fix AMR wallet guidance display

---------

Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>

* chore(pack): pin Vela CLI 0.0.3-test.1 (#3127)

* chore(nix): refresh pnpm deps hash

* chore(pack): pin Vela CLI 0.0.3

* chore(nix): refresh pnpm deps hash

* fix(web): suppress AMR exit 130 fallback (#3136)

* feat(web): nudge users to hosted AMR on model/auth/quota failures (#3083)

* feat(web): nudge users to hosted AMR on model/auth/quota failures

When a non-AMR agent run fails with an auth / quota / upstream model
error, surface an inline nudge under the error pill linking to Open
Design's hosted AMR gateway (https://open-design.ai/amr). The nudge
fires `surface_view` (element=run_failed_toast) on impression and
`ui_click` (element=go_amr) on the link.

Also teach the daemon to classify CLI-agent auth/quota/upstream failures
(Claude Code, codex, ...) into specific API error codes
(AGENT_AUTH_REQUIRED / RATE_LIMITED / UPSTREAM_UNAVAILABLE) instead of
the generic AGENT_EXECUTION_FAILED, so both the error message and the
nudge key off accurate codes. AMR's own runs are excluded from the
nudge — they keep the dedicated sign-in / recharge affordances.

* feat(web): rework failed-run AMR guidance into per-case error UI

Replace the single inline nudge with a per-case failed-run experience
driven by the run's error code + agent:

- The error card is now neutral gray (was red) and always carries a
  retry button; it is driven by the persisted per-message error event so
  it survives a reload.
- Non-AMR agent hitting a model/auth/quota wall: a theme-color promotion
  card under the error card offers "switch to AMR & retry" — switches the
  run to AMR, opens Settings on the AMR card, and auto-retries once the
  account signs in (ProjectView polls vela login status, independent of
  the Settings pill lifecycle, with success / 5-min-timeout / unmount
  exits).
- AMR agent unauthorized: clearer copy + an "authorize & retry" button.
- AMR agent out of balance: clearer copy + a "top up" button to the AMR
  wallet, with manual retry.
- Settings AMR card: when opened from the nudge, it scrolls into view and
  pulses, and an authorize-button coachmark (a fake hand cursor that
  rises in and dismisses on hover) points at the sign-in control when not
  yet authorized.

analytics: surface_view (run_failed_toast) on the promotion card and
ui_click (go_amr) on its action are retained. i18n adds chat.amrCard.*
and chat.amrError.* (en / zh-CN / zh-TW translated; other locales fall
back to en) and drops the old chat.amrErrorGuidance keys.

* fix(daemon): require status context for numeric service-failure codes

Per review on #3083: the model-service classifier matched bare HTTP
status numbers (`500`, `502`, `429`, `401`), so ordinary CLI output like
`line 500`, `read 502 bytes`, or `exit code 401` could be misclassified
as a provider outage / auth wall and wrongly surface the AMR nudge. Now
a status number only counts when it carries explicit context (`HTTP 500`,
`status 503`, `code: 401`, `502 Bad Gateway`); textual provider phrases
(overloaded, bad gateway, service unavailable, rate limit, …) are
unchanged. Adds fixtures proving unrelated numeric output stays null.

* fix(web): keep error pill for failed runs ChatPane's card doesn't cover

Per review on #3083: the per-message gray error pill was suppressed for
every persisted error status event, but ChatPane only renders the
replacement top-level error card for `retryableAssistantMessage` (the
last failed assistant). So a failed turn that is no longer last (after a
follow-up) or an older failed run in history showed neither the pill nor
the card — its error detail vanished, undercutting reload/history
survival. ChatPane now passes `errorCardOwnerId` (the assistant id whose
error the card represents); AssistantMessage suppresses only that one
pill and keeps rendering StatusPill for all other error events.

* fix(daemon): don't treat a process exit code as an HTTP status

Follow-up to review on #3083: the status-context helper accepted a bare
`code` prefix, so `exit code 401` / `process exited with code 429` still
matched and got classified as AGENT_AUTH_REQUIRED / RATE_LIMITED (the
very `exit code 401` case the comment calls out as noise). `code` now
only counts when qualified (`status code` / `error code` / `response
code`) or punctuation-bound (`code: 401`); bare `exit code N` no longer
matches. Adds fixtures for exit-code lines returning null.

* chore(web): translate AMR card / error keys for 16 remaining locales

PR #3083 added 10 new `chat.amrCard.*` / `chat.amrError.*` keys but only
provided en/zh-CN/zh-TW translations; the other 16 locales fell back to
English. Translate the card title/body, three chips, primary CTA, and
the AMR self-error (auth / balance) messages and buttons for ar, de,
es-ES, fa, fr, hu, id, it, ja, ko, pl, pt-BR, ru, th, tr, uk.

* fix(amr): address review feedback on #2355

Targeted fixes for the unresolved review threads on #2355. Each fix
includes / updates a focused test.

- runtimes/executables.ts: `packagedVelaOpenCodeCompanionTree` now
  verifies the inner `opencode` executable exists + is runnable, not
  just the directory. This closes the false-positive availability path
  that let `detectAgents()` surface AMR as available even when the
  packaged companion was empty / partially copied (mrcfps, 4 threads).

- runtimes/executables.ts: `resolveAmrOpenCodeExecutable` now prefers
  the bundled `<OD_RESOURCE_ROOT>/bin/libexec/opencode/opencode` over a
  stale `opencode` on the user's PATH, so packaged AMR builds can't be
  hijacked by a global installation.

- web/EntryShell.tsx: when the Local CLI scan returns an available
  agent and the previously-selected agent is AMR, switch the selection
  to the first available local agent so the runtime and persisted
  agent agree before Continue.

- server.ts (model-probe branch): for AMR, check `readVelaLoginStatus`
  BEFORE rejecting on an empty live-model catalog — a signed-out user
  was getting `AMR_MODEL_UNAVAILABLE` ("choose a model") instead of
  the correct `AMR_AUTH_REQUIRED` (sign-in affordance).

- server.ts (default model fallback): if the user asked for the AMR
  agent default and the cached id is no longer in the FRESH catalog,
  fall back to `liveModels[0]` from the probe instead of rejecting the
  run as `AMR_MODEL_UNAVAILABLE`.

- integrations/vela.ts: route `vela login` through
  `createCommandInvocation` so an npm/Node-style `vela.cmd` / `.bat`
  shim on Windows gets the correct `cmd.exe /d /s /c …` wrapping with
  verbatim args (matches `execAgentFile` / chat-run spawning).

- tools/pack/src/linux.ts: in containerized Linux builds, bind-mount
  the host directory of `OPEN_DESIGN_VELA_CLI_BIN` and rewrite the env
  to the container-side path. The host path was being passed in as-is
  even though the default container only mounts /project, /tools-pack
  and cache/home — `copyOptionalVelaCliBinary` saw a missing path.

Deferred (out of scope for this PR):
- `od amr status/login/logout/cancel` CLI subcommands (AGENTS.md
  UI/CLI dual-track rule, server.ts:5763) — sizable surface; tracked
  for a separate focused PR.
- Strict `--require-vela-cli` for Windows + mac-x64 beta builds:
  prematurely blocked — `@powerformer/vela-cli` only publishes the
  `darwin-arm64` platform binary today; adding the flag elsewhere
  would fail the builds. Revisit once win/x64/linux binaries ship.

* fix(amr): hoist sendAmrAccountFailure above the AMR catalog preflight (TDZ)

The new signed-out AMR branch in the catalog preflight at server.ts:10875
calls `sendAmrAccountFailure(...)` to emit AMR_AUTH_REQUIRED, but the
const declaration sat ~100 lines below at the outer function scope. Because
`const` is TDZ-aware, that branch would have thrown `ReferenceError:
Cannot access 'sendAmrAccountFailure' before initialization` for the
exact users it tries to help — defeating the original intent.

Hoist the helper to just above the AMR preflight block so it's available
to every AMR code path in this function. Behavior elsewhere is unchanged.

Also rerun the daemon test suite: `launch.test.ts > resolveAgentLaunch
uses packaged built-in Vela for AMR` was creating the
`<resourceRoot>/bin/libexec/opencode/` companion *directory* only, but
this PR's earlier tightening of `packagedVelaOpenCodeCompanionTree`
also requires the inner `opencode` executable. Add it to that fixture
to match the new contract; the test was a sibling of the executables /
env-and-detection fixtures already updated in 13fc4f4.

Addresses #2355 review (mrcfps, 2026-05-28).

* feat(web): add hover cancel for AMR login (#3158)

* feat(web): add hover cancel for AMR login

* fix(web): don't bounce AmrLoginPill back to 'Signing in…' after local cancel

Both codex-connector (P2) and looper (CHANGES_REQUESTED) on this PR
flagged the same race in the new local-cancel path: `handleCancelLogin`
dispatches `notifyAmrLoginStatusChanged('login-canceled')` immediately
after `/login/cancel` returns, but the `AMR_LOGIN_STATUS_EVENT` listener
unconditionally re-enters `refresh()` and then restarts polling
whenever `/api/integrations/vela/status` still reports
`loginInFlight: true`.

That is a real race because the daemon's `cancelVelaLogin()` only sends
SIGTERM (escalating to SIGKILL after `LOGIN_CANCEL_KILL_GRACE_MS` =
2000 ms) and keeps the child in `activeLoginProcs` until it actually
exits — so the first `/status` read after a successful cancel can
legally still come back as in-flight. Under that window the pill flips
back to 'Signing in…' and can later surface the timeout/error path even
though the user already canceled, defeating the behavior promised in
the PR description.

Fix the listener instead of every dispatch site: in the
`login-canceled` branch, after the local reset (stopPolling +
setPending(null) + clear refs), optimistically mark every subscribed
pill instance as not-in-flight (`setStatus((c) => c ? { ...c,
loginInFlight: false } : c)`) and `return` — skip the
refresh-and-reconcile branch below entirely. The next explicit refresh
(component mount, user interaction, or a `status-changed` event) will
pick up the daemon's confirmed state once the child has actually
exited.

Add a focused regression test that holds `/api/integrations/vela/status`
at `loginInFlight: true` even after a successful `/login/cancel`,
asserting that the pill stays at the Canceled → Authorize sequence and
never bounces back to 'Signing in…'. This test fails on the pre-fix
listener and passes on the new behavior; existing
'cancels an in-flight AMR sign-in…' and 'reconciles late AMR browser
completion to Signed in after local cancel' tests continue to pass.

Addresses review feedback on #3158 (chatgpt-codex-connector, nettee).

---------

Co-authored-by: lefarcen <935902669@qq.com>

---------

Co-authored-by: a1chzt <chizblank@gmail.com>
Co-authored-by: Amy <1184569493@qq.com>
Co-authored-by: Mason <jinmeihong0201@gmail.com>
Co-authored-by: Caprika <56862773+alchemistklk@users.noreply.github.com>
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
2026-05-28 05:09:55 +00:00
吴杨帆
72426b942a
fix(daemon): align Codex launch permissions on Windows and WSL (#3037)
Use danger-full-access when WSL_DISTRO_NAME is set and pass
default_permissions=":workspace" so newer Codex builds can write
inside the project directory instead of staying read-only.
2026-05-28 04:12:26 +00:00
jaehanbyun
62972f14a3
fix(web): portal plugin details modal (#3065)
Signed-off-by: jaehanbyun <awbrg789@naver.com>
2026-05-28 04:03:41 +00:00
Denis Redozubov
39ae2cbc57
docs: add media execution policy proposal (#3096) 2026-05-28 04:03:05 +00:00
YOMXXX
269a385ee2
fix(daemon): reconcile missing artifact manifests on run end (#2893) (#3110)
* fix(daemon): reconcile missing artifact manifests on run end (#2893)

When an agent writes HTML via write_file instead of create_artifact,
no .artifact.json manifest sidecar is created. If the run then
terminates (inactivity watchdog, user cancel, or process exit), the
HTML file exists on disk but the manifest is missing — breaking the
artifact panel, finalize, and export flows.

Add a best-effort reconciliation step in the child.on('close') handler
that lists project HTML files and calls reconcileHtmlArtifactManifest
for any missing sidecars. The IIFE runs asynchronously after
design.runs.finish() so it never blocks run finalisation.

* fix(daemon): scope run-end reconciliation to files modified during the run

The review on #3110 flagged that listing the entire project tree and
reconciling every HTML file without a sidecar is too broad — for
imported-folder projects (metadata.baseDir), pre-existing HTML files
would receive spurious manifests.

Record runStartTimeMs at the beginning of startChatRun and filter the
reconciliation loop to only touch HTML files whose mtime >= that
timestamp. Add a regression test that backdates a pre-existing HTML
file and verifies it is skipped while a new file is reconciled.

* test(daemon): fix mtime ordering in reconciliation regression test

The runStartTimeMs was recorded after writing the new file, so its
mtime fell before the threshold and the reconciliation filter skipped
it. Move the timestamp capture to before the write to match the real
startChatRun semantics.
2026-05-28 03:54:48 +00:00
PerishFire
d3a5e2901b
Add release-beta-s self-hosted workflow placeholder (#3150)
* Add self-hosted beta release workflow placeholder

* ci: register self-hosted runner labels for release-beta-s lint

actionlint flagged the custom self-hosted labels nexu-win and release-beta
as unknown. Add them to .github/actionlint.yaml so the release-beta-s
workflow passes the lint gate.

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

---------

Co-authored-by: libertecode <libertecode@proton.me>
2026-05-28 03:44:52 +00:00
Nicholas-Xiong
4a15903165
fix: increase subtab-pill gap to prevent active tab overlap (#3134)
Increase gap from 2px to 4px in .subtab-pill to prevent the active
tab's box-shadow from visually overlapping adjacent tabs. The previous
2px gap was insufficient for the shadow to render cleanly between tabs,
causing the Style tab to obscure neighboring Content/Attributes/HTML tabs.

Closes #2904
2026-05-28 03:44:09 +00:00
lefarcen
abe72af2a2
fix(ci): resolve blog-SEO base ref to a SHA in merge queue (#3137)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
actionlint / Lint GitHub Actions workflows (push) Failing after 1s
ci / Detect CI change scopes (push) Successful in 0s
landing-page-ci / Validate landing page (push) Failing after 1s
landing-page-staging / Deploy landing page to staging (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 2s
ci / Workspace unit tests (push) Failing after 2s
ci / Daemon workspace tests (push) Failing after 2s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 0s
ci / Runtime trace (push) Has been skipped
The "Lint changed blog SEO" and "Guard blog URL changes" steps in
landing-page-ci.yml fell back to the literal "HEAD^" when no base SHA was
available. On merge_group (and first-push) events there is no
pull_request.base.sha / before, so BASE became "HEAD^", and the
blog-indexing scripts' assertSafeGitRef (regex /^[A-Za-z0-9_./:-]+$/,
which forbids "^") threw `Unsafe git ref for base: HEAD^`, failing the
merge queue for every PR.

Resolve the fallback to a concrete commit with `git rev-parse HEAD^`
(checkout uses fetch-depth: 0, so it is always available), mirroring
blog-indexing-on-deploy.yml. The resulting 40-hex SHA passes the ref guard.
2026-05-27 17:07:09 +00:00
lefarcen
ce9fa687ca
ci: trigger PR exploration via maintainer /explore comment (no approval) (#3139)
* ci: trigger PR exploration via maintainer "/explore" comment (no approval)

Add a low-friction way to run the sandbox exploration: a maintainer
comments "/explore" on a PR.

- on: issue_comment (kept workflow_dispatch). The job `if` allows the
  comment path only when it is on a PR and the commenter has write access
  (author_association OWNER/MEMBER/COLLABORATOR), so randoms cannot trigger
  it; untrusted PR code still runs only inside the Docker sandbox.
- Drop the agent-pr-explore environment approval gate: both triggers are
  already write-gated and there is no auto-trigger, so the extra manual
  approval is redundant. R2 creds are repo-level secrets (no env-scoped
  secrets), so they stay available without the environment.
- Feedback: 👀 reaction on the command + a placeholder comment carrying
  the report marker (so the run yields one evolving comment), 🚀 on
  success, and 👎 + a failure note (with the run link) on failure.

Does not auto-run on every PR, so unrelated PRs stay clean.

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

* ci: don't clobber a produced report with the /explore failure note

Review: the failure-feedback step ran after the always() report step, so
on the failure-with-report case (sandbox wrote a report then exited
non-zero) it overwrote the just-posted report with the generic "failed
before producing a report" note — losing the useful output.

Guard it: if the report file exists, leave the posted report in place and
skip the failure note/reaction. Only post the short failure note when no
report was produced.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 16:48:58 +00:00
elihahah666
dadf8a5bc3
Add tracking for Automations, Plugin Detail & Loop (#3103)
* Add tracking for Automations, Plugin Detail, and Plugin Loop

- RoutinesSection: track new_automation, create, save, cancel, run_now,
  edit, pause, resume, delete, history button clicks
- PluginDetailView: track back navigation and use_plugin action
- PluginLoopHome: track clear_active, submit, card_details, card_use
- Extend AutomationsClickProps with new CRUD elements
- Add PluginDetailClickProps and PluginLoopClickProps contracts

* fix: address review comments on plugin/automation tracking

- Extract onBack handler in PluginDetailView to cover both error-path
  and success-path back buttons with tracking
- Move create/save tracking from submit button onClick into the form
  submit handler to capture keyboard submissions and avoid false
  positives from validation failures

* fix: move submit tracking into submit() handler in PluginLoopHome

Same fix as RoutinesSection: tracking now fires inside submit() so
keyboard Enter submissions are captured and the !trimmed guard
prevents false positives.

---------

Co-authored-by: qiongyu1999 <2694684348@qq.com>
2026-05-27 16:48:41 +00:00
Marc Chan
279da4f3b6
chore(contributors): remove migrated data files (#3138) 2026-05-27 16:24:06 +00:00
lefarcen
2302efa5db
fix(amr): close ACP stdin on abort so vela tears down OpenCode (#3097)
* fix(amr): close ACP stdin on abort so vela tears down OpenCode

When an AMR (vela) run is cancelled, attachAcpSession.abort() sent a
`session/cancel` RPC but left the child's stdin open. The vela ACP bridge
keeps running until it sees EOF (or is signaled), and it only shuts down
its private OpenCode `serve` process on a clean exit — so on abort the
OpenCode server lingered until the caller's SIGTERM fallback, and leaked
entirely if the parent was killed before cleanup ran.

End stdin after sending the cancel (mirroring the clean-completion path)
so the agent receives EOF and shuts down its own runtime promptly,
independent of signal timing.

* fix(amr): end stdin on abort even before session/new resolves

Addresses review on #3097: abort() still returned early when sessionId
was unset, so the stdin EOF only happened after session/new completed.
Cancelling during ACP startup (before the session exists) left the
OpenCode-teardown window open until the caller's SIGTERM fallback — and a
parent hard-kill before that could still strand the private OpenCode
process.

Move stdin.end() out of the sessionId guard so abort always closes stdin
when the pipe is writable; gate only the session/cancel RPC on sessionId.
Add a regression test that aborts during startup and asserts stdin is
ended with no session/cancel emitted.
2026-05-27 15:40:22 +00:00
hahalolo
68bbcfe9e2
Fix/projects page/button (#3130)
* fix(docker): fix container startup crash due to missing OD_API_TOKEN

* fix(docker): forward OD_API_TOKEN to fix docker container boot loop

* fix(docker): enforce non-empty OD_API_TOKEN for docker-compose

* fix(deploy): automate OD_API_TOKEN generation in installer and close compose loop

* docs(readme): guide manual deployment users to configure OD_API_TOKEN

* docs(readme): align working directory paths for manual deployment instructions

* docs(readme): align working directory paths for manual deployment instructions

* docs(readme): restore git clone context for first-time users

* fix(web): directly import remixicon.css in layout to fix icon font loading

* fix(web): resolve remixicon font not rendering via public/ directory

RemixIcon icons were blank in Chrome and showed garbled PUA codepoints
in Firefox. The font file was referenced with a relative URL in CSS
loaded through @import, which Turbopack does not resolve.

Fix: copy remixicon.woff2 to public/ and change the @font-face src
to an absolute URL (url("/remixicon.woff2")), so the browser fetches
it directly from the server root without relying on the bundler.
2026-05-27 14:54:58 +00:00
Jane
1b1fba165f
fix(landing-page): drop Homepage CTA from plugin detail page (#3129)
Some checks failed
visual-baseline / Capture visual baselines (push) Waiting to run
actionlint / Lint GitHub Actions workflows (push) Failing after 1s
ci / Detect CI change scopes (push) Successful in 1s
landing-page-ci / Validate landing page (push) Failing after 1s
landing-page-staging / Deploy landing page to staging (push) Has been skipped
nix-check / build (push) Failing after 2s
ci / Validate Nix flake (push) Has been skipped
ci / Preflight (push) Failing after 2s
ci / Workspace unit tests (push) Failing after 1s
ci / Daemon workspace tests (push) Failing after 1s
ci / Web workspace tests (push) Failing after 1s
ci / Browser tests (push) Failing after 1s
ci / Build workspaces (push) Failing after 1s
ci / Validate workspace (push) Failing after 1s
ci / Runtime trace (push) Has been skipped
Per product feedback: the Homepage button (rendered when a plugin's
manifest carries a `homepage` field that differs from its
`sourceUrl`) sent visitors off to upstream-author or original-source
URLs before they had a chance to explore the plugin via Open Design
itself. On a marketing detail page that's a leak, not a feature.

Removes the conditional block that rendered
`<a class="btn btn-ghost">Homepage ↗</a>` between the GitHub link
and the Share button. The header-action row is now exactly three
controls everywhere: Use this plugin → · Find on GitHub → · Share ↗.

The `plugin.homepage` data field stays available on the
`BundledPluginRecord` shape since the in-app catalog row, JSON-LD,
and any future author-bio surface can still consume it. The
`pcopy.detailHomepage` i18n key (with full 18-locale coverage) stays
for the same reason — `PluginsCopy` is `Partial<>` everywhere and
removing it would mean a 18-locale block edit for zero functional
gain.

apps/landing-page typecheck stays at 0 errors.

Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
2026-05-27 14:54:54 +00:00
lefarcen
ae7a417208
ci: add idempotent provision script for the agent-pr-explore runner (#3122)
* ci: add idempotent provision script for the agent-pr-explore runner

The self-hosted runner's setup was hand-assembled and easy to lose on a
rebuild — most dangerously the codex-acp pin: expect-cli bundles
codex-acp 0.10, which is incompatible with ChatGPT-account auth (every
model rejected); we run 0.15, but any expect-cli reinstall silently
reverts it and breaks the agent.

Add a self-contained, idempotent provision script that brings the
runner's config layer back to a working state and is safe to re-run:
codex model pin (gpt-5.4), the codex-acp 0.15 pin (npm pack + extract +
chmod), deploy-key generation, base-repo git mirror seed/refresh,
pnpm-store/reports dirs, the weekly image-refresh helper + cron, and the
readiness self-check helper. The header documents the manual/secret
steps it intentionally does not automate (base toolchain + colima, the
interactive `codex login`, registering the deploy key on the repo, and
registering the Actions runner service).

Verified idempotent against the live runner (all checks pass, no config
disturbed).

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

* ci: provision — update codex model key in place, don't truncate config.toml

Review: step 2 overwrote the whole ~/.codex/config.toml with just the
model line whenever the exact pin wasn't already present, dropping any
other Codex settings on a re-run — destructive, contradicting the
idempotent goal. Now: replace an existing `model =` line in place (sed),
append only when the key is absent, and leave the rest of config.toml
untouched. Verified preservation locally.

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

* ci: provision — create ~/.ssh before ssh-keygen on fresh host

Review: on the fresh-rebuild path this script targets, ~/.ssh usually
does not exist, so `ssh-keygen -f ~/.ssh/od_agent_deploy` fails with
"No such file or directory" and the deploy key (and downstream mirror
bootstrap) never gets created. mkdir -p the key's parent dir (chmod 700)
before keygen, and only print the pubkey when it actually exists.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:51:59 +00:00
lefarcen
b8cfee0c60
fix(diagnostics): capture daemon/web logs in packaged bundles (#3126)
Packaged diagnostics bundles never contained the daemon or web
`latest.log` — the very logs that hold the agent/critique run flow — so
support exports could not explain "sent prompt to the agent, then
nothing happened" reports.

Root cause: the sidecar `base` means different things per launch path.
tools-dev passes the pre-namespace source root, so
`resolveNamespaceRoot(base, namespace)` is correct. But the packaged
orchestrator launches every child with `base = <namespaceRoot>/runtime`
(apps/packaged/src/{paths,sidecars}.ts) while logs live a level up at
`<namespaceRoot>/logs`. The diagnostics builders re-appended the
namespace and resolved every log to
`<namespaceRoot>/runtime/<namespace>/logs/...` → ENOENT. renderer.log
only survived by accident: the desktop main process wrote it to the
same wrong path the reader looked in.

Add `resolveRuntimeNamespaceRoot(runtime, contract, runtimeMode)` to
`@open-design/sidecar` which walks up out of the `runtime/` dir in
packaged (runtime-mode) launches and falls back to the dev layout
otherwise. Route the desktop renderer-log path and both diagnostics
exporters (desktop IPC + daemon HTTP) through it so writer and reader
stay in lockstep and renderer.log lands next to the desktop log dir.

Tests: sidecar unit specs for both layouts; a daemon export spec that
writes a real `<namespaceRoot>/logs/daemon/latest.log` and asserts the
bundle captures its contents (red on main → ENOENT placeholder, green
here).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:48:14 +00:00
Marc Chan
bc44a8add3
ci: relay contributor card events to worker (#3113) 2026-05-27 14:48:01 +00:00
icc
74fa8a754a
fix: dismiss tab search on outside mouse down (#3102)
Co-authored-by: icc <iccccccccccccc@users.noreply.github.com>
2026-05-27 14:45:35 +00:00
吴杨帆
c554f14973
fix(web): refresh chat skills after Settings skill mutations (#3020)
SkillsSection kept its own skills list in sync after create/delete, but
App-level skills (used by the chat composer) were only loaded at boot.
Propagate a refresh callback so new skills appear in chat immediately.

Fixes #3017
2026-05-27 14:44:28 +00:00
初晨
3abcb3a4d2
fix(connectors): expire stale auth credentials (#2385)
* fix(connectors): expire stale auth credentials

Mark connector credentials as expired when provider reads report auth-shaped failures so Memory stops presenting stale connected apps as healthy.

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

* fix(connectors): avoid expiring grants on platform 401

Only delete connector credentials for provider tool errors attributable to the current connector so Composio platform auth failures do not wipe valid grants.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 14:44:10 +00:00
lefarcen
54f225d6b3
ci: retry PR-context gh calls so a transient API blip doesn't abort the run (#3128)
* ci: retry PR-context gh calls so a transient API blip doesn't abort the run

The early PR-context gathering calls `gh pr diff`, `gh pr view`, and
`gh api .../files`. gh hits api.github.com under the hood, and a single
transient timeout/5xx there aborts the whole run before any exploration
(seen on #3083: "could not find pull request diff: Get \"https://api.
github.com/...\": net/http timeout"). These were the only network calls
in the run without a retry (source fetch + npm already retry).

Add a small gh_retry helper (4 attempts, linear backoff) and wrap the
three read-only context calls. gh writes nothing to stdout on a failed
API call, so retrying is safe even for the calls piped into the context
file; the retry warning goes to stderr.

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

* ci: address review — buffer gh retries to files (no paginated duplication)

Review (Siri-Ray): wrapping `gh api --paginate` retries inline while the
context block is redirected to the file means a mid-pagination failure
leaves partial pages in the context, and the retry appends them again —
duplicating the patches section and burning the context budget the agent
reads.

Replace the in-pipe gh_retry with gh_retry_file: each call buffers to its
own file per attempt (`>` truncates on open, so a failed/partial attempt
is discarded before the next), and the context block just cats the
finished files. Fetch PR body + patches to files up front, then assemble.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:42:08 +00:00
lefarcen
a6a56099ca
ci: show per-case pass/fail status emoji in agent report (#3118)
Reviewers asked for at-a-glance outcomes. Instruct the agent to begin each
"Cases Tested" bullet with a status emoji ( pass /  fail / ⚠️ warning /
 inconclusive) and a bold case name, so the report shows which checks
passed or failed without reading each line.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:34:29 +00:00