Commit graph

805 commits

Author SHA1 Message Date
qiongyu1999
75c17f2422 fix(web): remove pet button from chat composer
The Pets entry in the composer toolbar is no longer needed here.
Users can still manage pets via /pet slash commands and Settings.
2026-05-30 14:03:55 +08:00
lefarcen
7b00e1de87 Merge remote-tracking branch 'origin/main' into release/v0.9.0 2026-05-29 20:59:20 +08:00
lefarcen
17a0f612fe
fix(web): re-detect agents after AMR sign-in so the model list isn't empty on first install (#3305)
* fix(web): re-detect agents after AMR sign-in so the model list isn't empty on first install

On a fresh install the agent catalog is detected at app start, *before* the
user has signed in to Open Design AMR. A signed-out `vela models` returns
nothing and AMR's adapter is deliberately fail-closed (`fallbackModels: []`),
so the AMR entry comes back with an empty model list. When the user then signs
in, login completion only updated the login pill's own status — nothing
re-ran `/api/agents` — so `agents[amr].models` stayed empty and the
"live model list" dropdown (`renderAgentModelConfig` returns null when there
are no models) never appeared. Restarting the app re-detected agents while
signed in and fixed it; sometimes vela's catalog lagged the credential write
and even a restart wasn't enough, hence the "reinstall to fix" reports.

Re-detect agents on the signed-out -> signed-in edge in both surfaces:
- SettingsDialog: a new effect watches the AMR login state and re-detects on
  the false->true transition, retrying briefly (4x / 1.5s) while AMR is logged
  in but still reports an empty catalog, to cover the propagation window.
- EntryShell onboarding: refresh agents after device authorization completes,
  before advancing the step, so the model list is populated before Settings
  is ever opened.

Red specs added in SettingsDialog.execution and EntryShell.onboarding (both
go red on release/v0.9.0 and green here).

* fix(web): show a loading state in the AMR model picker until the live catalog arrives

After sign-in the live `vela models` catalog lands a beat later (the link
backend has to register the freshly authorized device), so re-detecting agents
still leaves a short window where AMR is signed in but its model list is empty.
Previously that window rendered nothing, so the picker "popped in" seconds
after sign-in.

Render the picker in a loading state whenever AMR is signed in but its model
list hasn't arrived yet (a spinner + the existing `common.loading` label, no
new i18n key), so the dropdown appears at sign-in and simply fills in.

Also harden the post-sign-in re-detect so the loading row can't flash and
vanish: drive `onRefreshAgents` through a ref and depend only on the login
edge. Before this, the rescan changed `onRefreshAgents`/config identity, which
re-ran the effect and tore the retry loop down mid-flight — clearing the
in-flight state before the catalog arrived. The loading display is now derived
purely from `loggedIn && !models`, independent of the retry loop's lifecycle.

* fix(web): chase the AMR catalog whenever signed in with an empty model list

Addresses review feedback on #3305: the re-detect effect only fired on the
signed-out -> signed-in edge, so it never ran when Settings mounted already
signed in with an empty model list. Onboarding signs in and re-detects exactly
once, so if that single call lands during vela's catalog-propagation window,
the agent list is persisted empty and Settings later opens already signed in —
the edge guard returns early, nothing retries, and the new loading row spins
forever (the same first-install symptom this PR removes).

Trigger the bounded catalog chase on `loggedIn === true` + "AMR has no models"
instead of the login edge, so it covers both the in-Settings sign-in and the
already-signed-in-but-empty mount. An in-flight ref keeps a single loop running
across renders; `onRefreshAgents`/`agents` are read through refs so re-detects
can't tear the loop down mid-flight.

Adds a regression test for the laggy-onboarding mount (red on the edge-only
logic, green here).
2026-05-29 20:48:05 +08:00
lefarcen
6f532ca35c
fix(web): snapshot the srcDoc bridge frame in Mark mode so deck capture works (#3304)
The Mark tool (#3081/#3277) captured the preview via the *active* iframe. For
URL-load previews — decks especially — the active frame is the bridgeless URL
iframe, while the snapshot bridge lives only in the (mounted but hidden) srcDoc
transport frame. So Send on a deck timed out and showed 'Could not capture the
preview. Try again to avoid sending only ink.'

Snapshot the srcDoc-render-mode frame instead (capture mode already keeps it on
full content, so it carries the bridge), with a short retry while it finishes
swapping to full content. Falls back to the active frame for the non-URL-load
case where they are the same.

Red spec: PreviewDrawOverlay.test 'snapshots the srcDoc bridge iframe, not the
visible URL-load frame' fails on main (targets the URL frame), passes here.
2026-05-29 11:50:37 +00:00
lefarcen
dacd2be814 Merge remote-tracking branch 'origin/main' into release/v0.9.0 2026-05-29 18:15:17 +08:00
Bassiiiii
0c4b7e50be
fix(web/router): defer popstate dispatch to microtask (#2490)
* fix(web/router): defer popstate dispatch to microtask

navigate() previously dispatched a synchronous popstate event after
mutating window.history, which caused React 18 to emit:

  Cannot update a component (Router) while rendering a different
  component (App). To locate the bad setState() call inside App,
  follow the stack trace as described in
  https://react.dev/link/setstate-in-render

This happens whenever a caller invokes navigate() from inside a
useState updater (e.g. App.tsx:479 routing first-run users through
the onboarding panel from inside the setConfig() update). The
synchronous popstate dispatch reaches useRoute() subscribers which
then call setRoute() while the parent component is still rendering.

Defer the popstate dispatch to a microtask. The window.history call
itself stays synchronous so the URL bar updates immediately; only
subscriber updates are pushed past the current render commit, which
removes the warning without changing observable behaviour for any
existing caller.

* fix(web/router): cover deferred navigation timing

---------

Co-authored-by: Visionboost <contact@visionboost.fr>
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-05-29 09:37:55 +00:00
Md Mushfiqur Rahim
8ec162bb26
fix: pet hover card gets cut off at screen edges (#2860)
* fix: pet hover card gets cut off at screen edges

* fix: address review feedback - viewport clamping + unadopted pet wake

- Add window.innerHeight check to prevent bottom-edge clipping
- Increase menuH estimate for safer positioning
- Open pet settings instead of no-op Wake for unadopted pets

* fix: address review feedback on pet menu positioning and wake action

- Add viewport height check (viewH) to prevent bottom-edge clipping
- Increase menuH estimate for safer positioning
- Open pet settings instead of no-op Wake for unadopted pets
2026-05-29 09:05:51 +00:00
byte92
cdf34897ba
add comment composer keyboard submit shortcut (#2941)
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-05-29 08:46:15 +00:00
saifullakhan
73b2dc853f
Fix project empty state create action (#3082)
Co-authored-by: saifulla-khan <saifulla-khan@users.noreply.github.com>
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-05-29 08:30:43 +00:00
maybeyourking
881571dea7
fix(media): route custom-image edits through images API (#3087)
* fix(media): route custom-image edits through images API

* fix(media): normalize custom-image endpoint suffixes

---------

Co-authored-by: Artist Ning <dingkuake@yeah.net>
Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-05-29 08:09:44 +00:00
Aria Shishegaran
fe58db2ba1
fix(web): target comment picker elements precisely (#3263)
Resolve Comment picker hit testing against meaningful visible DOM leaves before falling back to annotated ancestors, while preserving Inspect mode's annotation-first selector behavior.

Filter generated React root annotations from Comment targets, keep real element bounds separate from hoverPoint, and avoid rendering the comments drawer inline when a configured dock portal is not mounted.
2026-05-29 07:47:08 +00:00
Mason
1006efa2f6
Improve onboarding AMR runtime card (#3276)
* Improve onboarding AMR runtime card

* Fix onboarding AMR test expectations
2026-05-29 07:45:23 +00:00
freshtemp-labs
593bf2f03c
fix(composer): ellipsis overflow for referenced filenames (#3269)
Inline @-mentioned filenames in the composer can be very lengthy,
causing line wrapping and visual crowding in the input area.

- Switch display from inline to inline-block for max-width
- Cap width at min(240px, 25vw)
- Apply overflow: hidden + text-overflow: ellipsis + white-space: nowrap
- Remove box-decoration-break (unused since content won't wrap)
- Tweak vertical-align for consistent inline-block alignment

Closes #3261

Co-authored-by: freshtemp-labs <freshtemp-labs@users.noreply.github.com>
2026-05-29 07:41:16 +00:00
ziyan2006
071db7ca1b
[codex] Stabilize HTML deck navigation state (#3142)
* fix: stabilize html deck navigation state

* fix: avoid misclassifying transform decks as scroll decks

* fix: detect default root-scroller decks

---------

Co-authored-by: Nongzi <3051966228@qq.com>
2026-05-29 07:41:10 +00:00
lefarcen
e69306ecbe chore(release): prepare 0.9.0 2026-05-29 15:40:34 +08:00
elihahah666
be09fe92da
fix: keep settings/handoff/avatar buttons fixed to the right in project header (#3279)
Move the three buttons (settings, handoff, avatar) from fileActionsBefore
to the actions slot so they always stay pinned to the right edge of the
header, regardless of how many extra controls (Share, Present, etc.) are
injected via portal during HTML preview.

Co-authored-by: qiongyu1999 <2694684348@qq.com>
Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
2026-05-29 07:33:57 +00:00
Amy
937946c6fa
Improve model picker search and shared BYOK catalogs (#3262) (#3278) 2026-05-29 07:07:40 +00:00
lefarcen
755d84e64c
feat(web): merge Draw + Screenshot into one Studio mark tool (#3081) (#3277)
Forward-ports chaoxiaoche's Studio toolbar work from #3081 onto current
main. The preview toolbar drops to 4 controls — Comment, Mark (the merged
Draw/Screenshot tool with box-select + pen sub-tools), Edit, Comments —
matching the latest design. The standalone Screenshot button and its
copy-to-clipboard path are removed; capture now flows through the mark
overlay. Also carries #3081's comment select-all/clear-selection panel and
keeps the Draw send guard added in #3270 (Send disabled mid-run, Queue stays).

Reconciled with main work that postdates #3081's base so nothing is lost:
- Preserves #2190's preview iframe keep-alive pool and the AnnotationHoverPopover
  hover card (re-added on top of #3081's BoardComposerPopover, with its own
  anchor helper so it doesn't clash with the composer popover anchoring).
- i18n: keeps every locale key main added; adopts #3081's mark wording.

Behavior change: the comment side-panel Clear now deselects instead of
batch-deleting selected comments (per #3081); per-comment delete and
send-selected remain.

Validation: pnpm --filter @open-design/web typecheck (clean),
full web vitest (2354 passed), pnpm guard.

Co-authored-by: chaoxiaoche <fanzhen910412@gmail.com>
2026-05-29 06:51:38 +00:00
lefarcen
98a2c63973
feat(daemon): add Antigravity agent adapter (#3157)
* feat(daemon): add Antigravity agent adapter

Adds Google Antigravity (`agy` CLI) as a coding-agent runtime. Detection
picks up `agy` on PATH, the daemon spawns `agy -p "<prompt>"` for a
single non-interactive turn, and the assistant text reply streams back
on stdout. OAuth is shared with the Antigravity IDE through the system
keyring, so users who have signed into the desktop app are authenticated
on first run with no extra step.

`agy` v1.0.3 has no JSON / stream-json / ACP output mode (upstream issue
#119), no `--model` flag (issue #35), and no MCP forwarding hook yet —
the adapter ships with `streamFormat: 'plain'` and a single `default`
fallback model so the model picker doesn't mislead users into thinking
their choice is wired through. We will upgrade buildArgs + add a
dedicated event parser when upstream ships structured output.

Also gitignores `.antigravitycli/`, the project-local config directory
`agy` auto-creates on every run (upstream issue #175).

* fix(daemon): Antigravity adapter — stdin prompt, brand icon, form loop, empty-output guard

- Switch prompt delivery from argv to stdin (`agy -p -`) to avoid the
  30KB maxPromptArgBytes limit that blocked real-world composed prompts
- Add official Antigravity brand SVG icon to agent picker
- Fix repeated question-form loop for plain agents by injecting an
  OVERRIDE block when form answers are already present in the transcript
- Add empty-output guard for plain agents so expired auth or silent
  failures surface a user-visible error instead of a blank "Done" turn

* feat(daemon): expand Antigravity adapter — model picker, form-loop fix, OAuth launcher, log-file classification

PR #3157 follow-up integrating four iterations from end-to-end manual
testing on Gemini 3.5 Flash + GPT-OSS 120B Medium through `agy` v1.0.3.
Each section is independently verifiable; combined they're what made
the first successful artifact generation work end-to-end.

## Model picker via settings.json (agy has no --model flag)

agy v1.0.3 ships no `--model` CLI flag (upstream issue #35), but the
TUI Switch-Model picker writes the chosen label to
`~/.gemini/antigravity-cli/settings.json`'s `"model"` field, and every
`-p` invocation re-reads that file on startup — verified by capturing
the `--log-file` line `Propagating selected model override to backend:
label="<model>"`. Antigravity's `fallbackModels` now lists the 8
labels its TUI exposes (Gemini 3.1 Pro / 3.5 Flash variants, Claude
Sonnet/Opus 4.6 Thinking, GPT-OSS 120B Medium) and `buildArgs`
persists the user's choice to settings.json right before spawn. The
synthetic `default` id is preserved — picking it leaves settings.json
untouched so a user who switches models from agy's own TUI keeps
their choice.

Introduces `RuntimeAgentDef.supportsCustomModel?: boolean`. AMR's
hardcoded blocklist in `SettingsDialog.tsx` migrates to the
declarative flag (it rejects free-form ids at the ACP layer), and
antigravity opts out because its label set is a server-side enum that
silently fails on unrecognised strings.

## Form-loop fix (transcript sanitizer + stronger OVERRIDE)

The discovery form loop on weak/medium plain-stream models (GPT-OSS
120B Medium, Gemini 3.5 Flash) had two reinforcing causes:

  1. `buildDaemonTranscript` packed the prior assistant turn's
     literal `<question-form>` markup into the user request on the
     next turn, giving the model a template to echo. New
     `sanitizePriorAssistantTurnForTranscript` strips
     `<question-form>...</question-form>` blocks and ```json fences
     that match form-schema shape, replacing them with a brief
     placeholder. User content is preserved verbatim (a user who
     legitimately mentions `<question-form>` in chat keeps their
     message intact).
  2. The OVERRIDE block on form-answered turns was 4 lines and only
     banned the bare `<question-form>` tag — models still emitted the
     fenced JSON, form-asking prose ("Got it — tell me the following"),
     and fake system events ("subagents stopped"). The new
     `FORM_ANSWERED_SYSTEM_OVERRIDE` enumerates each anti-pattern and
     pins them via tests, so silently weakening any line reintroduces
     the regression.

Also adds RuntimeAgentDef.resumesSessionViaCli + RuntimeContext.
hasPriorAssistantTurn as forward-looking abstractions (skipTranscript
option on composeChatUserRequestForAgent). Antigravity does NOT opt
in — agy's `-c` resume activates an internal agentic loop with tool
retries and fallback-to-cached-response on tool errors that the OD
system prompt cannot steer; reverted after seeing byte-identical
form re-emissions caused by agy's own retry logic, not OD's transcript.

## One-click OAuth via system terminal

agy print mode can't complete Google Sign-In on its own (the OAuth
callback page asks the user to paste an auth code back into agy, but
`-p` has no input field). Before this commit the auth banner only
told the user to "open a terminal yourself."

Adds `POST /api/agents/antigravity/oauth-launch` and a cross-platform
launcher in `runtimes/terminal-launch.ts`:

  - macOS:    osascript → Terminal.app `do script "agy"` + activate
  - Linux:    tries x-terminal-emulator, gnome-terminal, konsole,
              xfce4-terminal, xterm in order
  - Windows:  `cmd /c start "Open Design" cmd /k agy`

The endpoint hardcodes the `agy` command (no user input → no shell
injection surface) and is loopback-gated like the other daemon
endpoints. The chat's `AGENT_AUTH_REQUIRED` banner now renders a
"Sign in via terminal" button next to Retry; clicking it spawns the
terminal so the user can finish OAuth in one click.

## Silent-failure classification (auth vs quota via --log-file)

agy print mode is silent on stdout/stderr for both missing-OAuth AND
quota-exhausted failures — the upstream
`RESOURCE_EXHAUSTED (code 429): Individual quota reached` and the
`not logged into Antigravity` line only surface in agy's
`--log-file`. Without log inspection the daemon misread quota as
"auth required" and showed the wrong banner.

`RuntimeContext.agentLogFilePath` carries a daemon-owned per-run temp
path that antigravity's buildArgs translates to `--log-file <path>`.
The empty-output guard now reads that log on a `code === 0 &&
!childStdoutSeen` exit, feeds the tail to
`classifyAgentServiceFailure`, and routes:

  - "not logged into Antigravity"     → AGENT_AUTH_REQUIRED with
                                        antigravityAuthGuidance
  - "RESOURCE_EXHAUSTED" / "quota" /  → RATE_LIMITED with
    "Individual quota reached"          antigravityQuotaGuidance
  - none of the above (rare)          → fall back to auth guidance
                                        as the most likely cause

Both surface a terminal launcher in the auth banner: auth gets "Sign
in via terminal", quota gets "Switch model in terminal" — same
endpoint, contextual label. The handler is identical (open agy in a
terminal); the user either signs in or uses agy's Switch Model
picker to pick a model with available quota.

## Validation

- `pnpm guard` pass
- `pnpm --filter @open-design/daemon` runtime + telemetry suites:
  192 passed, 1 skipped (the 1 pre-existing `task-type` failure on
  origin/main is unrelated to this change)
- `pnpm --filter @open-design/web` typecheck pass; sse / amr-guidance
  / AgentIcon suites pass (51 web tests)
- Manual end-to-end on darwin + Gemini 3.5 Flash and GPT-OSS 120B
  Medium: turn-1 question-form rendered correctly, turn-2 produced
  `<artifact>` with full HTML (3.3KB Modern Minimal design) instead
  of re-emitting the form. agy `--log-file` content correctly
  classified as RATE_LIMITED when Gemini Pro quota was exhausted,
  and as AGENT_AUTH_REQUIRED when keychain was cleared.

* fix(web/test): align amrAgent fixture with supportsCustomModel contract

The AMR agent definition in the daemon ships `supportsCustomModel: false`
so the Settings model picker hides the free-text "Custom…" option. The
PR changed `allowCustomModel` from `selected.id !== 'amr'` (hardcoded)
to `selected.supportsCustomModel !== false` (declarative), but the test
fixture was not updated to carry the same field — causing the
`__custom__` sentinel to appear in the picker under test.

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

* fix(daemon): align formAnswerTransition wording with main + scope build directive to discovery

CI surfaced two failures on the merge with main:
- chat-route.test marks submitted discovery form answers ... expected
  the main-version wording 'Do not emit another <formId> form.'
- telemetry-message-finalization keeps non-discovery form answers
  active ... expected task-type to fall through the else branch
  ('Treat these form answers as the active user turn'), not the
  discovery RULE 2/RULE 3 build branch.

The colleague's earlier fba1e40b form-loop fix tightened both pieces
(stronger wording + grouped discovery|task-type into the build branch)
but didn't update the tests that pin the contract. Revert the
transition wording to main and re-scope the build directive to
'discovery' only. The aggressive form-loop suppression we added in
this PR now lives in the system-prompt FORM_ANSWERED_SYSTEM_OVERRIDE
block, which is far stronger than the user-request transition text
this commit reverts.

* fix(daemon): scope formOverride by form id, detach Linux terminal, move agy log cleanup to finally

- FORM_ANSWERED_GENERIC_OVERRIDE: new exported constant for non-discovery/
  non-task-type form ids; contains only the "do not re-ask" suppression
  without the RULE 2 / RULE 3 / artifact directive.
- formAnswerTransitionForCurrentPrompt: extend build-transition branch to
  include task-type alongside discovery, keeping user-turn and system
  override consistent.
- Prompt assembly (server.ts ~10848): derive formOverride from the parsed
  form id — FORM_ANSWERED_SYSTEM_OVERRIDE for discovery/task-type,
  FORM_ANSWERED_GENERIC_OVERRIDE for all other form ids, empty otherwise.
- launchOnLinux: replace execFileAsync (waited for terminal exit, 3 s cap)
  with spawn({ detached: true, stdio: 'ignore' }) + unref(); resolve on
  the 'spawn' event so long-lived interactive terminals (xterm, konsole)
  are not killed mid-OAuth-flow.
- Antigravity log cleanup: move fs.promises.unlink(agentLogFilePath) into
  a try/finally wrapper around the close handler so every exit path
  (success, failure, cancel, non-zero exit) cleans up the per-run temp
  file, preventing unbounded /tmp accumulation.
- Tests: rename task-type case to assert build-transition behaviour; add
  generic-form-id case (preferences) pinning the non-build path; add
  FORM_ANSWERED_GENERIC_OVERRIDE content assertions.

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

* fix(daemon): switch Antigravity buildArgs to chat subcommand invocation

Replace top-level `-p -` with `agy chat [--log-file …] -` so the adapter
uses the documented chat subcommand and stdin sentinel instead of the
unrecognised global -p flag.  Update the agent-args test description and
all four deepEqual assertions to assert the ['chat', '-'] shape.

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

* test(daemon): drop real-platform default-launch assertion from terminal-launch suite

The removed test called launchAgentInSystemTerminal('agy') with no
platform override, which invokes the real system terminal on every
developer machine running the daemon test suite (Terminal.app on macOS,
cmd.exe on Windows, xterm/gnome-terminal on Linux). That is an
unacceptable OS side effect for a unit test.

The behaviour being asserted — that omitting platform selects
process.platform — is a TypeScript default-parameter guarantee, not a
runtime invariant that needs an integration test. The remaining 'aix'
case continues to pin the unsupported-platform failure shape.

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

* fix(daemon): buffer Antigravity stdout to suppress auth URL before close-time classifier

The plain-stream close handler at code===0 can detect an agy OAuth
prompt in agentStdoutTail and emit AGENT_AUTH_REQUIRED, but by the
time close fires the stdout chunk has already been forwarded to the
client via the plain-stream `send('stdout', { chunk })` path. This
leaves both the raw OAuth URL and the terminal-launch guidance visible
in chat.

Buffer all stdout chunks for the `antigravity` agent instead of
forwarding them immediately. The existing close-time auth-prompt guard
(code===0, !trackingSubstantiveOutput, childStdoutSeen) returns early
when it detects the auth pattern, leaving the buffer unflushed and the
OAuth URL out of the SSE stream. For legitimate assistant output the
buffer is flushed in order just before design.runs.finish so the
chunks still arrive before the run's finished event.

Adds a chat-route integration test using a fake `agy` that exits 0
after printing the canonical auth prompt; asserts that the run emits
AGENT_AUTH_REQUIRED with no event: stdout delta containing the URL.

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

* test(daemon): isolate antigravity buildArgs argv test from real settings file

Pass a temp antigravitySettingsPath in the RuntimeContext for the
withModel argv assertion so unit tests do not touch
~/.gemini/antigravity-cli/settings.json. Adds the optional
antigravitySettingsPath field to RuntimeContext and threads it
through buildArgs to writeAntigravityModelSelection; production
callers leave it undefined, preserving the existing default path.

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

* fix(daemon): revert Antigravity buildArgs to `-p -` (the only working agy v1.0.3 invocation)

The looper-reviewer-bot reported `chat` as agy's headless subcommand
based on its environment's agy build, and looper-fixer applied that
shape. The installed CLI (`agy --version` reports `1.0.3`) does NOT
expose a `chat` subcommand — `agy --help`'s `Available subcommands`
section lists only `changelog / help / install / plugin / update`,
and `agy chat - < prompt` exits 0 with empty stdout (the daemon then
forwards it as a 'successful' empty reply, exactly the failure mode
the auth/quota guard at server.ts ~12090 is meant to catch — for the
wrong reason).

`-p` is the documented print-mode flag (`Short alias for --print`)
and `agy -p -` reads the prompt from stdin and prints the model
reply, which the entire end-to-end test sequence in this PR has
verified against (form-loop fix, settings.json model routing,
log-file classification all confirmed working on Gemini 3.5 Flash
+ GPT-OSS 120B Medium with this invocation).

Updates the agent-args test to pin `['-p', '-']` instead of
`['chat', '-']` and adds an inline comment in antigravity.ts noting
that `chat` may exist in a future agy build but is not the contract
on the installed CLI today.

* fix(daemon): serialize Antigravity concrete-model spawns to dodge settings.json race

Reviewer (looper) flagged a concurrency race in the model-routing path:
~/.gemini/antigravity-cli/settings.json is process-global, so two OD
runs starting close together with different concrete models can race
the file — run A writes model A, run B writes model B, then A's agy
finally reads settings.json and executes on model B. The Settings
model picker becomes nondeterministic under parallel conversations.

Adds a per-process promise chain in antigravity.ts:
  - acquireAntigravityModelLock(): chain-await + return release fn
  - waitForAgyToReadModel(logPath, expected): polls agy's --log-file
    for the upstream signal
      'Propagating selected model override to backend: label="<X>"'
    which model_config_manager.go emits once agy has finished reading
    settings.json. Returns true on observed match, false on timeout.
    Regex-escapes the expected label so '(' / ')' in 'GPT-OSS 120B
    (Medium)' match literally, not as a capture group.

server.ts spawn pipeline now acquires the lock BEFORE buildArgs (which
performs the settings.json write) and schedules a release-once handler
that fires when EITHER (a) the log-file confirms agy read the model
or (b) the child exits — the exit fallback prevents a stuck/crashed
agy from starving the queue for every subsequent antigravity spawn.

Default-model spawns bypass the lock entirely: their buildArgs doesn't
touch settings.json, so there's nothing to serialize.

Tests pin:
  - FIFO ordering across 2 / 3 concurrent acquirers
  - Wait helper's regex correctly matches parenthesized labels
  - Wait helper does NOT match a different model with shared prefix
  - Wait helper swallows missing-log-file errors and returns false on
    timeout (no spawn-pipeline crash if the log never appears)

194 → 198 passing runtime tests, 0 regressions.

* fix(daemon): close Antigravity lock release race on slow agy startup (looper #263fd2fe7)

Reviewer flagged that the previous serialization scheduled
`releaseOnce` in `.finally()` on waitForAgyToReadModel — meaning the
helper's `false` timeout return ALSO released the lock. If agy took
longer than the 15s polling window to read settings.json (cold start,
swap-thrash, slow network handshake to the upstream backend), run A's
lock dropped at 15s, run B rewrote settings.json with model B, and
run A's still-starting agy then read the wrong model. Same race the
original mutex was meant to close.

Fix the release semantics to be release-on-confirmation-only:

  - waitForAgyToReadModel: `false` now strictly means 'I gave up
    polling,' not 'agy definitely did not read this.' Document the
    contract so a future caller can't conflate the two. Add an
    optional AbortSignal so server.ts can stop polling when the child
    exits — without it, the leftover watcher could outlive the run
    and accidentally match a later concurrent run's log content,
    releasing the wrong lock.
  - server.ts: schedule `releaseOnce` only when waitForAgyToReadModel
    returns true. The exit handler (which fires for crashes, fast
    exits, normal completion) is now the canonical fallback that
    releases the lock no matter what — the queue can't starve
    permanently because agy always exits eventually. The exit
    handler also fires the AbortController so the watcher cleans up.

New tests pin:
  - timeout returns false WITHOUT any release-implying side effect
  - already-aborted signal short-circuits (no readFile calls)
  - abort mid-poll wakes the helper from its setTimeout (no
    multi-hundred-ms hang waiting out a poll interval that no longer
    matters)

198 → 201 passing runtime tests, 0 regressions.

---------

Co-authored-by: qiongyu1999 <2694684348@qq.com>
2026-05-29 05:43:37 +00:00
lefarcen
bf7152dbdc
fix(web): disable Draw direct-send during an active run, keep Queue (#3270)
Reinstates the Studio tool hardening from #3081 on top of current main:
while a task is streaming, the Draw/annotation primary Send action and its
Enter shortcut are disabled, so an annotation can no longer leak into the
active run while the button shows a disabled reason.

This is the synthesis of two stacked-merge-divergent changes rather than a
wholesale revert: Queue stays available, so the value from #1961 (kami) is
preserved — an annotation made during a run is still staged for the next
turn instead of being dropped. Only the button/Enter availability changes;
the downstream queue/streaming-staging handler in ChatComposer is untouched.

- PreviewDrawOverlay: send('send') and canSend now respect sendDisabled.
- Reframed the streaming Draw test to assert Send is disabled while Queue
  still emits a queued annotation (preserving the "annotate during a run"
  coverage).
- Added unit coverage for the Enter/Send guard and Queue availability while
  a task is running.
2026-05-29 05:28:18 +00:00
Hashem Aldhaheri
bbf4809a7e
fix(web): use surface-appropriate noun in plugin/template preview unavailable copy (#3229)
After #2840 wired plugin and design-template 404s into the same
"no shipped preview" placeholder the skills tab uses, the placeholder
copy still hard-coded "skill" — so users opening a Community/Plugins
card whose manifest declares a preview entry that doesn't ship saw
"No shipped preview for this skill." on a card that is clearly not a
skill.

Adds a noun discriminator to PreviewView.unavailable so the placeholder
reads with the right word per surface — "this skill" on the Skills
tab, "this plugin" on Community/Plugins, "this template" on deck-mode
design-templates. Locales gain three new preview.noun* strings (with
appropriate per-language demonstrative+article) and the existing
unavailable title/body interpolate a {noun} placeholder.

Also fixes a CSS gap in .ds-modal-unavailable surfaced by the same
path: the title and body divs were collapsing onto a single line under
.ds-modal-empty's default flex-row. Mirrors the existing
.ds-modal-error column+gap layout.

Refs #897, #2840.
2026-05-29 03:23:18 +00:00
初晨
9c6a69490b
fix(web): localize mention picker copy (#3255) 2026-05-29 03:19:14 +00:00
Yuhao Chen
4a0900ca81
fix(web): remove passive video play badge (#3252) 2026-05-29 03:17:57 +00:00
lefarcen
08c350fb0f
fix(analytics): bucket feedback agent/model directly on the event (#3240)
* fix(analytics): bucket feedback agent/model directly on the event

Reason × agent / reason × model splits on
`assistant_feedback_reason_submit` were 25-74% `unknown` because the
event only carried `run_id` — analyses had to join back to
`run_created/run_finished`, which loses rows whenever the feedback is
given to a message whose run sits outside the query window (the common
case for feedback on older messages), and whose `model_id` was `null`
to begin with (the user didn't pick a specific model — went with the
agent's default).

Carry `agent_provider_id` and `model_id` directly on every feedback
event so the analyses no longer need to join. Replace `null/unknown`
with the `default` bucket via `modelIdForTracking` (and let
`agentIdToTracking` fall through to `other`) at every emit site —
`null` was an analyst-hostile mix of "no selection" and "join failed";
`default` is a real, analysable bucket. On `run_finished`, upgrade the
model to the agent-reported value from initializing/model status
events when the user did not pick one — covers ACP, claude-stream,
copilot-stream, json-event-stream, qoder, pi-rpc.

* fix(analytics): use feedbackAgentProviderIdToTracking and assistantFeedbackModelId for feedback events

Wire API-mode agent ids (anthropic-api → anthropic) and agentName-parsed
model ids through the feedback emit path. Previously the feedback props used
agentIdToTracking (no anthropic-api case) and assistantModelDetail (no
agentName fallback), causing model_id='default' and agent_provider_id='other'
for API-mode agents.

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

* fix(analytics): extend feedback/run schema for full agent/model coverage

Layered on top of the conflict resolution and the v1 emit switchover
in 0c1b30440. Three things the prior commits did not cover:

1) The v2 `assistant_feedback_*` family (page='studio') shares
   `AssistantFeedbackBase`. Add `agent_provider_id` + `model_id` once on
   the base so all four derived emits (reason_view, click, reason_click,
   reason_submit) carry the same context as the v1 family, instead of
   leaving the v2 dashboard with the same `unknown` gap the v1 PR was
   trying to close.

2) Tighten `FeedbackSubmitResultProps.model_id` and
   `feedbackAgentProviderIdToTracking` from `string | null` /
   `TrackingFeedbackProviderId | null` to non-null. The web emit paths
   already bucket null/empty through `modelIdForTracking` and the
   `?? 'other'` fallback; collapsing that at the helper / contract
   layer means `null` becomes a TS error at every new emit site, so we
   can't regress the unknown bucket again in a future event.

3) Comment on `run_finished.model_id` so reviewers reading
   `finishedModelId` see why the agent-reported value upgrades the
   request-side one.

* fix(analytics): continue event scan past usage to find agent-reported model

The reverse scan for agentReportedModel was broken: the loop broke on
the first usage event (terminal) before ever reaching the status:initializing
or status:model event (emitted at run start, lower index). This meant
run_finished.model_id always fell through to modelIdForTracking(null) =
'default' for any run that reported usage tokens.

Fix: track haveUsageTokens as a flag and defer the break until both usage
tokens are found and either the model is not needed (user picked one) or
the agent-reported model has been captured. Extract the logic into
scanRunEventsForFinishedProps for unit testability.

Tests: six new cases in run-lifecycle-analytics.test.ts cover the
initializing→usage append order, ACP status:model, detail field fallback,
early exit when reqBodyModel is set, no-status event, and empty events.

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

* fix(analytics): guard usage block with !haveUsageTokens to prevent early events overwriting terminal tokens

In the reverse-scan loop of scanRunEventsForFinishedProps, the usage block
lacked a !haveUsageTokens guard. When needAgentModel is true and the
agentReportedModel lives at the start of the run (lower index), the loop
walks all the way back past multiple usage events (one per step/turn in
multi-step runs), overwriting inputTokens/outputTokens on each pass. The
surviving values were those of the earliest step, not the terminal total.

Adding !haveUsageTokens to the usage block condition ensures only the first
(terminal) usage event seen in reverse sets the token counts; subsequent
earlier usage events are skipped while the scan continues for agentReportedModel.

Adds a test case for initializing(model) → usage(step1) → usage(terminal)
asserting both terminal token counts and agentReportedModel.

Generated-By: looper 0.9.2 (runner=fixer, agent=claude-code)
2026-05-29 03:06:06 +00:00
Nicholas-Xiong
45873a551b
fix: improve queue screenshot preview modal backdrop (#3215)
Increase the backdrop opacity from 44% to 75% and add a blur effect
to better separate the queue screenshot preview from the file-list
preview panel underneath. This prevents visual overlap and makes it
clearer which preview surface is active.

Fixes #3167
2026-05-29 03:04:28 +00:00
初晨
ef8f518b3b
Fix status detail URL parsing (#3208) 2026-05-29 03:03:46 +00:00
Nicholas-Xiong
98651ecae2
fix: localize queue UI strings in Chinese mode (#3213)
- Queued → 已排队
- to Send → 待发送
- Edit queued task → 编辑排队任务
- Save → 保存
- Cancel → 取消
- Edit → 编辑
- more queued → 个排队
- Queued follow-up → 已排队的后续任务

Fixes #3173
2026-05-29 03:03:27 +00:00
hahalolo
afc5f52445
Fix/issue/3149 (#3162)
* 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): add min-width constraints to plugin filter span and pill button

related issue 3149
2026-05-29 03:03:03 +00:00
kami
1efa1dc7b5
Add preview iframe keep-alive pool (#2190)
* Add preview iframe keep-alive pool

* Fix active preview eviction on prompt context changes

* Evict preview iframes on skill/design-system registry edits

Bridge Settings → Skills / Design Systems to App.tsx so the keep-alive
pool drops any preview iframe whose project depends on the affected id
after every successful mutation. Without this, body-only edits leave
SkillSummary / DesignSystemSummary fields untouched and ProjectView's
signature-driven eviction never fires, so the active preview keeps
serving stale prompt context. The handler also re-fetches the App
shell's skill / design-system lists so summary-field changes propagate
to ProjectView's signature on the next render.

Also extend IframeKeepAlivePool.evictMatching with an includeActive
option so the new handler can drop the currently-visible iframe along
with parked ones; the fallback pool only ever holds active entries so
includeActive is a no-op there.

Regression tests:
- App.previewKeepAlive: clicking a Settings stub that fires
  onSkillsChanged / onDesignSystemsChanged drives evictMatching with
  includeActive=true and a predicate that matches projects using the
  affected id while skipping unrelated projects.
- SkillsSection: onSkillsChanged fires after a body-only edit and
  after a delete.

* fix: reattach active keep-alive iframe after eviction

* fix(web): refresh design systems after rename

---------

Co-authored-by: kami.c <kami.c@chative.com>
2026-05-29 03:01:17 +00:00
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
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
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
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
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
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
初晨
a7e7d5db18
Fix memory action alignment (#3175) 2026-05-28 08:53:01 +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
David Z
efa808f1db
fix(i18n): refresh French locale (#2963) 2026-05-28 06:55:57 +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
jaehanbyun
62972f14a3
fix(web): portal plugin details modal (#3065)
Signed-off-by: jaehanbyun <awbrg789@naver.com>
2026-05-28 04:03:41 +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
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