mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
codex/design-system-module-only
35 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9a402b2e4c |
feat(campaign): extend the DeepSeek V4 campaign to Pro + Flash (#6861)
* feat(campaign): extend the DeepSeek V4 campaign to Pro + Flash The V4 Flash free week ends 8/13 20:00; a two-week window offering BOTH V4 Pro and V4 Flash opens at that same instant and runs to 8/27 20:00. This grows the campaign already in production rather than landing a second stack beside it. That implementation ran the free week for a week: its audience resolution (wallet balance is not a subscription), i18n wiring, localized countdown, frequency control and attribution are all already exercised, and it carries a product decision this change must not undo -- the URL review backdoors (campaign / audience / usage overrides) were deliberately removed, and reviewing the campaign is done by overriding `window.startAt`, never through query parameters. Almost all of it is the campaign module growing a second model: `isDeepSeekV4FlashCampaignModel` now matches either id, which is why the model switcher badges both without its rendering changing at all, and the modal's benefit line is an i18n key, so naming both models was copy. `「立即使用」` now switches a paid user to Pro, the headline benefit. Two things needed real edits. Model-benefit impressions fired once with a hardcoded Flash id; product compares the two models' reach separately, so they now fire once per campaign model actually on screen, each with its own `model_id`. And `campaign_id` / `element` move to the spec's `deepseek_v4_pro` naming, with `deepseek_v4_flash` kept declared so the finished campaign's warehouse rows stay valid. Copy is rewritten across all 19 locales -- dates, the two-week framing, both model names, and the peak-hours queue caveat product added. Dates follow each locale's existing format rather than one imposed format, so only the day numbers move. Frequency control is keyed on the campaign id, and the id is new, so a user who dismissed the free week is still owed this campaign's single showing. That was an invisible dependency on remembering to change the id; it now has a test. Module and i18n key names keep their `…V4Flash` spelling: renaming 28 keys across 19 locales mid-campaign is risk without user-visible benefit, and belongs in a cleanup pass once the campaign is off. * fix(web): read a rolling model window as a wait, not a failure vela's hosted gateway rejects a request that exhausted its rolling per-model window with `model_limit_exceeded`, phrased for API callers as "You have reached the 5-hour usage limit for X. Try again after <instant>. This request was not charged to Wallet Credits." Two things went wrong with that sentence on the way to a user. The daemon's `isHardQuotaText` matches its "usage limit" wording, so a window that resets on its own was classified as `hard_quota` — an exhausted quota, marked non-retryable. Nothing was charged and nothing needs topping up, so that both misnames the cause and counts a self- healing wait against reliability as a real failure. The web runtime then had nowhere to put it. The AMR branch of `resolveRunFailureUi` ends in a catch-all returning the generic "task failed" title with a null message key, which renders the raw upstream string — so a localized chat showed an English sentence under a wrong heading. Home is worse: its send path fails before a run exists, never reaches that mapping at all, and prints `err.message` verbatim. Classify the window separately, ahead of the hard-quota reading, and give both surfaces the same localized copy: high demand, when it reopens, and the reassurance that nothing was charged. The instant is rendered in the reader's locale — the gateway reports UTC. Both surfaces read the signal through one shared contracts module so they cannot drift, and degrade to copy that does not promise a time when no instant is readable. Other rate limits are untouched: a genuine session-limit exhaustion keeps its hard_quota reading, and every other business failure on Home still prints verbatim. * fix(analytics): keep a campaign entry for the whole campaign The attribution record expires after seven days, but this campaign runs fourteen. A visitor who clicks the banner on day 1 and subscribes on day 11 arrives at checkout with no entry left to attribute the payment to, so the campaign under-reports against the metric it is judged on -- 活动归因付费人数 / 金额, both defined as payments carrying its `campaign_id`. Extended only for entries that carry a `campaignId`. Raising the window globally would move the attribution口径 for every other entry point and the dashboards already built on it; the campaign is the thing that needs a longer memory, so it is the thing that gets one. Past fourteen days a campaign entry expires like any other -- the longer window is scoped to the campaign, not an open-ended exemption. * fix(campaign): carry the new campaign id through ingest and e2e fixtures Two places still recognised only the finished free week's id, both found in review. The daemon's AMR entry ingest allowlist is fail-closed: an unrecognised `campaignId` voids the WHOLE entry rather than just its campaign field. With only `deepseek_v4_flash` listed, every entry this campaign produces would have been dropped at ingest -- and its success metrics are defined as payments carrying its `campaign_id`, so it would have reported zero conversions while converting normally. Both ids are listed now, since entries minted during the free week can still arrive inside their attribution window. The Playwright suite pre-seeds a dismissal so the campaign modal never opens over a functional spec. That seeding was keyed on the old campaign id, so the modal would have covered Home in every UI spec. `home-hero- rail` also clears storage and re-seeded its own hardcoded copy of the key; it now reapplies the shared map instead, so a future campaign cannot be dismissed in one place and left interrupting specs in the other. * fix(campaign): land the workbench badge on the console, like the modal The badge opened a hardcoded `https://open-design.ai/zh/pricing/` while the modal's CTA opened the console's plan surface scoped to the caller's workspace. Both are in-product entry points for a signed-in user, so one funnel was being split across two destinations -- and only one of them lets the user actually subscribe. The hardcoded link also pinned `/zh/`, so every non-Chinese user landed on a Chinese page from a badge that renders in all 19 locales. Now shares the modal's destination, including the workspace scope, so the console can resolve the right upgrade surface for that workspace. Verified against a real browser: the badge opens `/amr/dashboard?workspaceId=…&billing=plan` carrying entry id, source, campaign id, conversion source and device id. |
||
|
|
85d2e4893c |
fix(amr): recover expired cloud sessions (#6786)
* fix(amr): recover expired cloud sessions * fix(diagnostics): redact dynamic API routes * fix(daemon): honor settings in AMR diagnostics * fix(daemon): align workspace auth revisions * fix(web): gate expired cloud sessions on sign-in * fix(web): scope cloud sign-in gate to cloud execution * test(web): align cloud auth fixture * fix(web): recover transient run authority failures * test(daemon): expect retryable authority detail * test(e2e): align BYOK auth boundary * test(e2e): align expired cloud recovery flows |
||
|
|
98d5460a2a |
perf(collab): batch team resource pulls (#6782)
* perf(collab): batch team resource pulls * fix(nix): refresh pnpm dependency hashes * fix(nix): refresh merged pnpm dependency hashes * test(e2e): cover Vela batch pull compatibility |
||
|
|
8f98973fe0 |
fix(daemon): report missing vela runtime as unavailable (#6738)
* fix(daemon): report missing vela runtime as unavailable * fix(web): keep message center public without amr runtime |
||
|
|
7251259334 |
perf(collab): bound vela push fan-out and recover failed publishes (#6564)
* perf(collab): bound vela push fan-out and recover failed publishes Three guardrails for the team-collab publish pipeline: - Merge IGNORED_PROJECT_DIR_NAMES into the vela resource push exclude list (MEMBER_MIRROR_PUSH_EXCLUDED_ENTRIES). Generated/installed trees the project UI already hides from the owner (dist, .next, .venv, coverage, .tmp, __pycache__, ...) no longer fan out to member mirrors; every secret-bearing exclusion is preserved. - Give every vela resource subcommand a wall-clock budget: push 10min (slow-uplink envelope that only reaps truly wedged children), head/shared/remove 60s, pull keeps its existing 30s. A hung CLI now surfaces as an ordinary publish failure instead of pinning the scheduler's in-flight publish forever. - Retry failed publishes on a bounded 5s/15s/45s backoff (max 3 consecutive retries). Success or a fresh author change resets the budget; a change landing mid-publish keeps the existing dirty last-write-wins path without stacking a retry; dispose() clears retry timers. * fix(collab): scope generated-tree exclusions to directories The owner side hides these names as DIRECTORIES only — `collectFiles` consults `shouldSkipDir` inside its `isDirectory()` branch — but they were handed to `vela resource push --exclude`, which matches an entry name whatever its type. A project holding a regular file called `target`, `out` or `dist` therefore had that file silently dropped from every member mirror while the owner kept seeing it, so the two views disagreed with no error anywhere. Found in QA on the previous commit. Send those names in the directory-only trailing-slash form and keep the secret-bearing entries bare, since a credential must stay behind whether it is a file or a directory. The slash doubles as the compatibility seam: a Vela build that does not understand it compares "dist/" against names that never contain a slash, matches nothing, and publishes the full tree — the pre-optimization payload, never a missing file. A new --exclude-dir flag could not degrade that way, because older CLIs reject unknown flags and every publish would fail instead. `.git` and `node_modules` are dropped from the directory-only set: they are already in the bare secret-bearing list, which covers every type. |
||
|
|
8b3ec59cfe |
feat(campaign): DeepSeek V4 Flash free-week surfaces (landing + web + attribution) (#6507)
* feat(campaign): port DeepSeek V4 Flash free-week surfaces from #6450 1:1 port of the demo net diff (base feat/workspace-team) onto main. Landing home banner, landing pricing benefits, cloud-team-demo review page, in-app home modal, workbench badge, model-switcher benefit badge, and the campaign attribution/analytics pipeline. No behavior changes in this commit; agreed behavior fixes land separately. * fix(campaign): show the DeepSeek V4 Flash modal only on the active home view The dialog portals to document.body while EntryShell hides inactive entry views with display:none, so the campaign escaped the home view and interrupted projects/tasks/plugins routes. Thread EntryShell's existing isActive signal through HomeView into a new `active` prop: when inactive the modal neither opens nor renders (and closes without being marked seen, so returning to home while still unseen re-opens it). Requirement: 弹窗只在 #/home 展示,不在编辑器或其他工作流中打断用户。 * fix(campaign): fail closed when frequency-control storage is unreadable hasSeenCampaign() answered "unseen" when localStorage threw (private mode / storage disabled), so the modal re-opened on every Home mount even though markCampaignSeen() could never persist the dismissal. The campaign promise is one appearance per activity window — an unreadable store now counts as seen. * feat(campaign): make the paid 立即使用 CTA switch to DeepSeek V4 Flash for real 产品拍板 D5 + Demo 表格「点击立即使用模型切换为DSV4」: the paid modal CTA previously only clicked the composer chip open and highlighted it. Now it performs the actual switch — agent to Cloud (amr), model to deepseek-v4-flash — through EntryShell's onAgentChange/onAgentModelChange persistence pair (the same sink InlineModelSwitcher writes through), threaded HomeView → DeepSeekV4FlashCampaign as onUseCampaignModel. The chip keeps the data-campaign-highlight pulse as feedback but no longer opens the picker; the use_now analytics element is unchanged. * fix(campaign): carry metricsConsent + device id on the unpaid modal upgrade The unpaid modal's recordAmrEntry was the only campaign touchpoint that dropped metricsConsent — the workbench badge and the model-switcher upgrade already forward it — so the consent-gated AMR analytics mirror never fired and the plans URL missed od_device_id. Thread config.telemetry.metrics and config.installationId from EntryShell through HomeView into the modal, and stamp the URL via amrHandoffDeviceId + attributedAmrUrl(plansUrl, attribution, deviceId). * fix(campaign): make the campaign/campaignAudience/campaignUsage review params production-inert 产品拍板 D7 + 需求文档「该入口仅用于本地 Demo,不作为线上产品规则」: the review URL fixtures forced campaign surfaces for real users on released builds. Web: new isCampaignReviewAllowed() gate (NODE_ENV !== 'production', inlined at build time) now fronts isDeepSeekV4FlashCampaignReview, the campaignAudience override, the modal's shouldForceCampaignReview, and the switcher's campaignUsage read. Landing (Astro): inline scripts cannot see build-time constants, so the home banner, pricing page, and cloud-team-demo inject the verdict as data-campaign-review-allowed (__OD_LANDING_NOINDEX__: staging/PR previews true, production false) and preview activation now requires it. The [locale] wrappers re-render the same components, so they inherit the gate. * fix(landing): stamp campaign attribution only inside the activity window Pricing subscribe CTAs and the cloud-team-demo upgrade/pricing clicks minted __odRecordCampaignEntry with campaignId and stamped campaign_id on analytics props unconditionally, so clicks outside the fixed Aug 6-13 window were counted toward the campaign. Both surfaces now pass the campaign id and the campaign_id prop only while campaignActive is true; od_entry_* attribution itself is still recorded outside the window, just without the campaign stamp. Inbound first-touch od_campaign_id preservation in posthog-analytics.ts is untouched. * test(campaign): use undefined-safe access for window.open mock calls Strict TS (tsc -b) flags open.mock.calls[0][0] as possibly undefined in the modal spec added for the unpaid-upgrade consent behavior; go through String(...[0]?.[0]) instead. No behavioral change. * chore(campaign): drop demo-only artifacts before shipping Remove the cloud-team-demo static review page (fake team cards and prices, noindex but publicly reachable — review-only material that must not ship), its locale wrapper and test, and five unreferenced campaign poster PNGs left over from demo iterations. The team-modal campaign surfaces the page previewed live in the real Cloud plan-selection component (powerformer/vela#1379). * fix(campaign): make review URLs a pure UI projection with zero config writes Product boundary ruling: the client-side campaign change is a badge next to DeepSeek V4 Flash (auto-hides after the window) plus the modal CTA reusing the existing model-selection callbacks — selection behavior must not change. The demo's review fixtures violated that: an effect force- switched the persisted agent to AMR under ?campaignAudience=, and the model write-back effect persisted the review projection into the user's config. Both writes are removed; review URLs now project AMR + DeepSeek purely in the UI and leave persisted state untouched. This also roots out the CI heap OOM in InlineModelSwitcher.compact-model-click.test.tsx (run 31076791463): the stateful test wrapper re-rendered on every persisted write, looping the unconditional write-back forever. With review write-back gone the file completes in ~10s. The review-URL test now asserts the projection and that onAgentChange/onAgentModelChange are never called. * refactor(campaign): remove DeepSeek V4 Flash URL review backdoors Product decision: the ?campaign= / ?campaignAudience= / ?campaignUsage= review parameter mechanisms are deleted entirely. Campaign visibility is now decided only by the real activity window plus the real subscription audience, and pre-launch acceptance switches to a temporary startAt constant override instead of URL fixtures. web: - campaigns/deepseek-v4-flash.ts: drop review param constants, review gate, review detection, and the audience URL override; visibility and audience resolution take only `now` (plus plan/loggedIn); restricted presentation kept as a reserved branch for the backend usage-limit signal (no trigger wired yet) - DeepSeekV4FlashCampaign.tsx: drop the forced review open and the synthetic openedAt+7d countdown; the countdown always runs against the real endAtExclusive boundary - InlineModelSwitcher.tsx: drop campaignUsage URL read, audience override, review agent/model fixtures and the currentAgent/model review projection (incl. data-campaign-review); 已暂停 and 升级可用 badge branches stay as reserved presentation flags with no trigger - EntryShell.tsx / use-deepseek-v4-flash-campaign.ts: no more location.search plumbing; home-hero.css review-width rule removed landing-page: - index.astro / pricing/index.astro: preview branch removed (review params, review-allowed attribute, synthetic preview countdown); campaignActive follows the fixed window only - _lib/deepseek-v4-flash-campaign.ts: reviewParam field removed tests: review-only specs deleted; convertible specs rewritten as pure time-window assertions (clock pinned inside/outside the real window); new negative specs pin that no campaign surface reads URL parameters. * fix(campaign): device id on badge handoff + impressions on window-open transition Two review findings from PerishCode, both verified real: - EntryShell workbench-badge handoff called attributedAmrUrl without the device id, so opted-in clicks omitted od_device_id (confirmed in live browser acceptance: the modal URL carried it, the badge URL did not). Now derives it via amrHandoffDeviceId like the other two touchpoints; the source-contract assertion pins the three-arg call. - The pricing page emitted campaign impressions only during initial script execution, so a tab opened before startAt showed the surfaces at launch without any surface_view. Impressions now share the reveal loop's cadence with a one-time flag, covering both load-time and mid-session window-open transitions. * temp!: open the campaign window early for pre-launch acceptance — REVERT BEFORE MERGE startAt 2026-08-06T20:00 → 00:00 (+08:00) in both front-end constants so reviewers can see the real campaign state (true countdown, no review backdoors) on local runs and the PR preview today. endAtExclusive is untouched. This commit MUST be reverted before this PR enters the merge queue — merging it would launch the landing surfaces ahead of 20:00. * test(campaign): derive the start-instant countdown from the window constants Pins the assertion to endAtExclusive - startAt instead of a literal '7天 00:00:00', so the temporary pre-launch start-time override (and any future window change) cannot silently break the suite. * Revert "temp!: open the campaign window early for pre-launch acceptance — REVERT BEFORE MERGE" This reverts commit |
||
|
|
66004e0cd7 |
test: raise CI flake budgets for waitFor and wall clocks (#6415)
* test: raise CI flake budgets for waitFor and wall clocks
Web jsdom waitFor was dying on the 1s default under runner contention.
Daemon deploy wall-clock asserts, logo body-timeout margin, and vela
fixed sleeps had zero/thin slack on slow CI CPUs.
* test: scope flake budgets and restore ReDoS guard
Keep the doctype prolog linear-time assert at 500ms so a reintroduced
lazy-body regex (~5s) cannot pass. Drop the global waitFor 5s raise and
apply a 2s budget only to the App.connectors privacy-choice waits that
flaked at 1015–1093ms under CI contention.
Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)
* test: restore CSS budget and abort-aware logo body mock
Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)
* test: wait for Vela login close before no-fallback assert
loginInFlight can go idle between child exit and close after cancel,
so the canceled late-fallback test now waits on supervisor settlement
(close/error terminal + pending fallback) instead of status idle.
Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)
* test: narrow AbortSignal for logo body mock typecheck
Preflight failed because init.signal was still possibly null after the
optional guard. Capture a local signal so tsc can prove it is defined.
Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)
* test: use global Testing Library asyncUtilTimeout 5s
Raise waitFor/findBy failure budget in the shared jsdom setup instead of
scoping a 2s override to App.connectors privacy cases. Drop no-op deploy
comment churn that did not change the ReDoS wall-clock guards.
* test: scope waitFor budget to privacy waits only
Drop suite-wide Testing Library asyncUtilTimeout 5s so unrelated
waitFor/findBy failures still surface assertion errors quickly.
Restore a 2s timeout only on App.connectors first-run privacy waits
that flaked under CI contention.
Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build)
* Revert "test: scope waitFor budget to privacy waits only"
This reverts commit
|
||
|
|
356c8c364f |
feat(workspace-team): team workspaces, shared resources, workspace-scoped billing, and the #5517 redesign (#6142)
* fix(web): scope plugin URL imports to workspace * test(web): cover billing interest remount lifecycle * feat(packaged): enable the workspace-team transport for test-profile builds The vela test backend now serves the workspace-team API, so a packaged build baked with the test AMR profile should drive team projects / collab / resource sharing there instead of leaving them dormant. Replace the single feature-test equality check with a profile -> vela web origin map so adding a backend is one entry, and keep every unlisted profile (prod above all) off. * test(daemon): cover billing catch-up after SSE reconnect * fix(collab): surface and start first materialization when opening a shared project QA P0: a brand-new member on a fresh install joins someone's workspace, opens a project from it, and sees no loading state and no files — content only appears on a SECOND open, much later. Two defects, both on the first `/collab/status` request that opening a shared project makes. 1. No pull is started. The daemon's self-materialization block was gated on `callerIsOwner`, so a non-owner member got the placeholder record registered and nothing else. The web's auto-pull is gated on `publishedVersion` advancing past its cursor, and a fresh daemon's first status response cannot carry a published head: `collab.publishedVersion()` reads an in-process map that has never been written, and the real hub head is fetched fire-and-forget into `headEnrichmentCache` for a LATER poll to consume. Materialization therefore arrived only whenever a proactive lane (hub push / reconnect catch-up / the 30s recovery floor) next fired — the "second open" the report describes. 2. No download state is shown. `useProjectCollab.downloadPending` — which picks DesignFilesPanel's "Syncing files from the team…" over its empty state and create-a-file CTAs — reads only `publishedVersion > cursor`, `contentTransferState`, and an in-flight pull. All three are blank on that first response, so the member was shown an empty project inviting them to start creating over content that was still on its way. The fix makes the placeholder stamp the load-bearing signal on both sides: - `materializePlaceholderOnOpen` (routes/collab-sync.ts) replaces the owner-only block: any viewer opening a shared project whose only local record is an unmaterialized placeholder starts its pull on that same request, through the same coalesced flow. The retracted-share heal in its catch stays owner-only — a member who hits the hub tombstone has merely lost access and must not unshare anyone's project. - `/collab/status` reports `awaitingFirstMaterialization`, a purely local, synchronous fact that needs no hub round-trip and is therefore available on the very first response. `od collab status` prints it too. - `localFilesAreNotTheContentYet` (useProjectCollab.ts) makes it always download-pending, bypassing the member-only `shouldAutoPull` gate: whether the local files are the content is a fact about the record, not about who may pull it, so the owner of an unmaterialized placeholder (the reinstall case, recvqzaDvUU6B3) stops seeing the same empty state too. Red evidence: all six new specs fail on origin/feat/workspace-team @ |
||
|
|
8518ec847b |
fix(amr): recover late login failures and trace auth stages (#5986)
* fix(amr): recover late login failures and trace auth stages * fix(amr): preserve scoped cancel attempt IDs Generated-By: looper 0.11.1 (runner=fixer, agent=codex) * fix(amr): defer unsupported Vela auth stages Generated-By: looper 0.11.1 (runner=fixer, agent=codex) * fix(amr): narrow fallback telemetry to shipped Vela Generated-By: looper 0.11.1 (runner=fixer, agent=codex) * fix(amr): preserve live login after stale cancel Generated-By: looper 0.11.1 (runner=fixer, agent=codex) * fix(amr): preserve cancel during login startup Retain cancellation intent when a provisional cancel races the delayed canonical login response, then cancel the canonical attempt before polling can begin. Generated-By: looper 0.11.1 (runner=fixer, agent=codex) * fix(amr): rejoin newer login after startup cancel Generated-By: looper 0.11.1 (runner=fixer, agent=codex) * fix(amr): preserve cancel across web login starts Generated-By: looper 0.11.1 (runner=fixer, agent=codex) * fix(amr): retain cancel when status refresh fails Generated-By: looper 0.11.1 (runner=fixer, agent=codex) * fix(amr): cancel onboarding status preflight Treat cancellation before an auth attempt exists as a local preflight cancel so a delayed status response cannot start login.\n\nGenerated-By: looper 0.11.1 (runner=fixer, agent=codex) |
||
|
|
809d45f27c |
feat(integrations): add the local Open Design Cloud flow for Codex (#6055)
* feat(daemon): expose Vela login through CLI and MCP * feat(mcp): add interactive local brief card * feat(packaged): bootstrap local MCP headlessly * feat(daemon): secure local BYOK credential profiles * feat(cli): manage secure BYOK profiles * feat(web): store BYOK keys in secure profiles * fix(runs): deduplicate cloud generation requests * feat(daemon): add end-to-end plugin observability * fix(pack): include native PTY runtime in desktop packages Lazy-load node-pty in the daemon so missing native binaries only disable Terminal and interactive Keychain operations instead of crashing startup. Ship and validate target prebuilds in macOS and Windows packages. Repair the macOS spawn helper before signing, and invalidate stale Windows packaging caches. Cover the packaged PTY path in macOS and Windows smoke tests. Require Vela CLI for every beta desktop build target. * fix(mcp): localize and stabilize plugin briefs Keep collect and confirm on one MCP Apps card, report intrinsic size changes, and remove the fragile widget-state fallback. Localize brief forms, summaries, status, and public errors from the current request or Host locale while preserving stable protocol ids and selections. Keep internal runtime identifiers out of user-facing MCP guidance. * fix(packaged): register live Studio URLs with daemon Register the packaged web sidecar loopback origin over namespace-scoped daemon IPC after dynamic binding. Invalidate install-info by the live web port and remove the second MCP URL cache so run and project responses use the current Studio address. Validate loopback origins and cover registration, rebind, and cache refresh. * fix(mcp): stabilize brief confirmation lifecycle Keep confirmed briefs locked after Host publication failures and retry only the cached follow-up message instead of confirming twice. Replace blank initialization with a compact loading state, accept immediate and delayed tool results, and bump the MCP Apps resource to v3 so Codex does not reuse the stale card. Add an executable JSDOM Host bridge harness covering double-submit, publication retry, delayed results, and repeated draft notifications. Validated with the full daemon suite, workspace typecheck, and guard. * fix(mcp): prevent brief app resize feedback loops Replace the self-observed intrinsic-height loop with deduplicated body measurements so Codex MCP app iframes do not repeatedly resize themselves and fail sandbox startup. Bump the brief resource URI to v4 to avoid cached v3 cards and cover the regression with the focused brief-app spec. Validated with daemon tests/build, workspace guard/typecheck, a verified beta DMG, and a direct packaged MCP resource smoke. * fix(mcp): use the final Open Design plugin identity Make open-design the only accepted external plugin ID across MCP tools, run attribution, artifact version origins, and telemetry. Rename the Brief MCP App resource to v5 and reject the unpublished old ID instead of aliasing it. Validated with 171 daemon tests, 5 web tests, daemon/web typechecks, and guard. * chore(pack): bump Vela CLI to 0.0.27 * chore(nix): refresh pnpm dependency hashes * fix(mcp): honor host and plugin request contracts Respect independently advertised MCP Apps capabilities and prefer the Codex-native follow-up bridge so confirmed briefs render as readable user turns instead of context-only messages. Bump the brief resource to v6 to invalidate stale cards.\n\nMark Vela login and account probes as open-world interactions, and require stable request IDs for attributed generation so lost responses cannot create a second logical run. * fix(cli): support keyless BYOK profiles Add an explicit --no-api-key save mode for local and self-hosted providers while keeping credential-bearing profiles stdin-only. Reject missing or conflicting credential modes and cover both paths in the CLI tests. * fix(pack): replace headless owner when desktop opens Advertise headless packaged runtimes as having no visible window, then shut them down before a normal desktop launch continues instead of treating SHOW as a successful focus. Validated with the focused packaged headless and launcher lifecycle tests (20 passed). * fix(pack): clean up failed headless startup Acquire the headless identity, sidecars, and IPC server through one failure-atomic lifecycle so later bootstrap errors close every previously acquired owner in reverse order. Cover MCP-install and web-identity publication failures; validated with 22 focused tests and the packaged typecheck. * fix(byok): remove raw provider request contract Expose only daemon-owned BYOK profile references from shared run types and the web transport, while retaining the daemon's fail-closed rejection for legacy or untyped raw credential payloads. Validated with 76 web provider tests plus web and contracts typechecks. * fix(daemon): preserve interrupted run idempotency Hydrate queued or running durable requests as DAEMON_RESTARTED failures before idempotency reuse, preserving the original request and workflow mappings instead of creating a second cloud operation. Share the restart transition with boot reconciliation and expose synthetic terminal events to reattaching clients. Validated with 48 focused tests and the daemon typecheck. * fix(byok): roll back failed credential metadata writes Snapshot secure-store values before mutation and compensate create, update, and delete operations when profile metadata cannot be persisted, preventing orphaned or lost credentials. Validated with nine focused credential-service tests and the daemon typecheck. * fix(web): reconcile selected BYOK profile metadata Treat the daemon's selected secure profile as authoritative for protocol, endpoint, model, and API version so CLI profile edits cannot leave the browser showing stale execution settings. Validated with 66 config tests and the web typecheck. * fix(byok): migrate legacy credentials before cleanup Import each supported legacy browser credential into a stable daemon-owned secure profile before removing plaintext copies. Keep the original browser record on secure-store or unsupported-provider failures and surface a Settings recovery path. Cover successful migration, duplicate projections, and injected secure-backend failure while preserving existing Bedrock downgrade behavior in memory. * fix(byok): secure Windows profiles with DPAPI Dispatch native Windows BYOK profiles to a CurrentUser DPAPI backend rooted under the resolved daemon data directory. Secrets enter PowerShell only through stdin, encrypted blobs stay separate from non-secret profile metadata, and API responses remain secret-free. Add platform dispatch coverage, a Windows-only real DPAPI round-trip test, and the packaged Windows smoke path for create, resolve, response-redaction, and delete behavior. * fix(web): preserve config writes during hydration * fix(byok): persist secure credentials across entry flows * test(byok): cover secure onboarding persistence --------- Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
3162da5f23 |
test: cover runtime automation gaps (#5976)
* test: cover runtime automation gaps * fix: remove duplicate runtime startup declarations Generated-By: looper 0.11.0 (runner=fixer, agent=codex) |
||
|
|
adbb0ff9a4 |
fix(message-center): honor active AMR profile for anonymous sync (#5968)
* fix(message-center): honor active AMR profile for anonymous sync * fix(vela): keep control context on one config snapshot Generated-By: looper 0.11.0 (runner=fixer, agent=codex) |
||
|
|
1226773663 |
feat(daemon): route completed-run telemetry through Vela (#5666)
* feat(daemon): route completed-run telemetry through Vela * fix(daemon): migrate test telemetry relay domain * feat(daemon): send Vela feedback scores with completed-run telemetry Map score-create into the Vela envelope and use the same Vela sink for thumbs feedback, binding scores via the client run id instead of skipping them when account-aware telemetry is selected. * fix(daemon): skip anonymous fallback for Vela feedback auth errors Score-only feedback batches must stay on the account-scoped Vela path. When Vela returns 401/403, fail the delivery instead of posting to the anonymous relay, which has no matching trace for those scores. Generated-By: looper 0.11.0-beta.1 (runner=fixer, agent=grok-build) |
||
|
|
2d1f25ac60 |
feat(web): add message center (#5920)
* feat(web): add message center demo * fix(web): align message center actions * fix(web): remove message read toggles * fix(web): show unread count on message filter * feat(web): connect message center to vela (#5777) * feat(web): connect message center to vela * fix(web): harden message center read syncing Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(web): preserve cached message center reads Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(web): repair message center read sync and visual selector Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(web): refresh message center write auth and bound pagination Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(web): preserve message center state across locale and logout Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(message-center): stabilize login and anonymous delivery (#5910) * journal: stop repeated amr login polling * fix(web): drop anonymous message-center window gate Align with Vela audience+TTL visibility by stopping startedAt local windows so anonymous clients can pull all non-expired global messages. * refactor(web): promote message center from demo * fix(daemon): guard vela message center proxy stream Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(cli): add message center command Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * test(e2e): harden visual workspace file readiness Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(web): separate message center sync empty states Generated-By: looper 0.11.0 (runner=fixer, agent=codex) --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> |
||
|
|
9d055a0fa6 |
feat(amr): add artifact-based upgrade guidance for Free users (#5591)
* feat(amr): add artifact-based upgrade guidance * fix(amr): fail open when billing plan is unavailable --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> |
||
|
|
80f94faa04 |
fix(daemon): classify structured Vela ACP auth-required details as AMR_AUTH_REQUIRED (#5158)
classifyAmrAccountFailureDetails only mapped the insufficient_balance / recharge half of the accountAction union. When vela reports an auth failure through ACP structured details while the protocol message is generic, the run fell into the opaque AGENT_EXECUTION_FAILED bucket instead of the AMR_AUTH_REQUIRED card with its relogin affordance. Add the symmetric auth_required / relogin branch, mirroring the structured balance branch that #5053 introduced. Co-authored-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com> |
||
|
|
b3356807b6 |
fix(amr): improve unavailable model gating (#5335)
* fix(amr): honor full model list metadata * fix(amr): classify tier entitlement failures * fix(amr): refine unavailable model upgrade affordance * fix(web): restore stale AMR model fallback Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(web): surface AMR tier upgrade failures Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): mirror chat upgrade attribution Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): skip disabled default models Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): honor catalog default model * chore(pack): bump vela cli to 0.0.20 * fix(amr): align locked catalog fallback state * chore(nix): refresh pnpm deps hash * fix(amr): refresh entitlement-aware model state * fix(amr): refresh models after upgrade return * fix(amr): preserve default model analytics * fix(amr): tighten model gating follow-ups Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): repair default and refresh follow-ups Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): resolve explicit default before spawn Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): concretize default model in smoke paths Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): repair default model gating Generated-By: looper 0.10.1 (runner=fixer, agent=codex) * fix(amr): share connection-test model cache Generated-By: looper 0.10.1 (runner=fixer, agent=codex) --------- Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> |
||
|
|
d21e29e263 | Classify AMR account errors from ACP details (#5053) | ||
|
|
3652fb7237 |
[codex] restore AMR wallet balance follow-ups (#4771)
* feat: show AMR wallet balance * fix: preserve AMR wallet reauth state * chore: retrigger PR validation * fix: bound AMR wallet balance fetch * chore: retrigger wallet validation * chore: bump bundled Vela CLI to 0.0.18 * chore: retrigger Vela CLI validation * chore(nix): refresh pnpm deps hash * feat(web): reposition AMR as Open Design's cloud subscription (#4684) * feat(web): reposition AMR as Open Design's cloud subscription Fold the standalone "AMR" product into Open Design's own cloud service: unify all user-facing copy, surface the signed-in account's plan + wallet balance, and add upgrade CTAs. - Copy: user-facing "AMR" / "Open Design AMR" -> "Open Design" across 19 locales, agent labels, and daemon error strings (balance/recharge wording preserved). - Account: daemon reads plan + balance via `vela billing summary` (CLI route, control-key) and injects them into the vela status; web shows plan + balance on the settings card, the model-switcher account row, and the runtime-switcher avatar row (balance field aligned with the console wallet's headline balanceUsd). - Upgrade: filled "Upgrade" CTA next to the plan on all three surfaces when the tier is below max; opens the console wallet at ?view=plans. - Runtime-switcher Open Design row restructured to carry account info inline, with a selected-state highlight. - Analytics: new amr_entry elements settings_amr_upgrade / inline_amr_upgrade / avatar_amr_upgrade / avatar_amr_agent_card. - specs/: design doc for the Open Design account/avatar direction. * fix(daemon): surface live account for env-backed auth + isolate cache per credential Address @nettee review on #4684: - applyVelaLiveAccount() now populates plan/balance even when the login status has user:null (VELA_RUNTIME_KEY / VELA_LINK_URL env auth), so env-backed Open Design sessions no longer drop the fetched billing data. - Key the live-account cache by the full credential revision (auth source + profile + userId + config mtime), not just profile, and clear it on logout — a logout / account switch can no longer surface the previous account's plan or wallet balance before the background refresh runs. - Regression tests for both in tests/integrations/vela.test.ts. Sync tests to the AMR -> Open Design copy: - web: tests/providers/sse.test.ts error-string assertions. - e2e: onboarding / logout / run-failure / login-pill specs + visual fixture. * fix(i18n): drop duplicated "Open Design" in switchBody fallback copy The AMR -> Open Design rename sweep collided with the existing brand mention in chat.amrCard.switchBody, rendering "Open Design official Open Design model service" across 13 locales (zh-TW, ja, ko, ar, es-ES, hu, id, fa, pl, tr, ru, pt-BR, uk). Remove the redundant second mention so the retry/switch card reads cleanly. Flagged by @nettee on #4684. Also start aligning InlineModelSwitcher unit tests to the new copy (agent display name AMR -> Open Design, compact error string). * fix(amr): resolve live billing before first /status; hide Upgrade until plan known Address @nettee review on #4684 (two correctness issues in the new plan/balance path): - Cold cache: the signed-in /status handler now BLOCKS the first response on fetchVelaBillingSummary() when the cache is empty, instead of returning config-only and refreshing in the background. The new account surfaces read /status once and never re-poll, so the old flow left plan/balance hidden until the user refocused. Warm cache keeps the non-blocking fast path. fake-vela gains a `billing summary` handler and vela.routes.test.ts covers the cold-cache-first-open case. - Upgrade CTA: canUpgradeVelaPlan() now treats an unknown/empty plan as NOT upgradeable, so a signed-in session whose live summary hasn't resolved no longer flashes Upgrade at top-tier users. Unit-tested. * style(amr): bold the plan/balance text in the switcher + avatar account rows The settings card already renders both bold (the plan value reuses .agent-card-amr-balance-value); align the inline switcher account row and the runtime-switcher avatar row so plan + balance read with the same emphasis everywhere. * fix(amr): single-flight cold billing fetch + surface plan/balance on its own field Address @nettee follow-up review on #4684: - Race: the cold-cache decision keyed off the refresh throttle, so a second /status arriving during the first billing fetch saw refreshAccount=false, fell through, and returned config-only — which the read-once surfaces can't recover from. Now `peekVelaLiveAccount(key) === null` is the cold signal and the billing fetch is single-flighted per credential revision: concurrent cold callers await the same in-flight promise. setVelaLiveAccount stamps the fetch time so the warm path doesn't immediately re-refresh. - Identity: applyVelaLiveAccount no longer fabricates a `{ id: '', email: '' }` user for env-backed sessions. The live billing projection now rides on a new VelaLoginStatus.account field, so `user` stays null (preserving the analytics `user.id` null signal) and plan/balance are surfaced uniformly. SettingsDialog / InlineModelSwitcher / AvatarMenu read status.account.{plan,balanceUsd}. Regression coverage updated in vela.test.ts (applyVelaLiveAccount) and vela.routes.test.ts (cold-cache first open now asserts body.account). * style(amr): match the switcher plan text color to the balance The inline switcher account row showed the balance in --text-strong but the plan in --text-muted, so "$247.51 Plus" read with two different weights of grey. Use --text-strong for the plan too so balance + plan sit at the same emphasis. * fix(amr): treat tier-less billing summary as free; register new entry sources Address @nettee review on #4684: - Free vs unknown: fetchVelaBillingSummary() returned plan:undefined when `membershipTier` was absent, but that field is omitted for free accounts. The new surfaces read status.account.plan for both display and canUpgradeVelaPlan, so a successful free-account read rendered as "unknown" — plan hidden and the Upgrade CTA suppressed for the exact users who should see it. A successful summary without a tier now normalizes to the explicit 'free' sentinel; "unknown" stays signalled by a null account (failed fetch). Regression in vela.routes.test.ts (balance, no tier → account.plan === 'free'). - Analytics: the four entry sources this PR adds (settings_amr_upgrade, inline_amr_upgrade, avatar_amr_upgrade, avatar_amr_agent_card) were in the source→page map but not in the AMR_ENTRY_SOURCES validator set, so parseAmrEntryAnalyticsPayload rejected them and /analytics-entry 400ed for the new buttons. Added them to the set; parser regression covers all four. * fix(amr): invalidate the env-backed live-account cache on config/account change Address @nettee 11:19 review (the inline comment did not post, but the flagged env-backed live-account cache path has a real leak): readVelaCredentialRevision forced configMtimeMs to null for env-backed auth, so the live-account cache key became `env|profile|1||` with nothing to distinguish accounts. Because the live billing summary is fetched with the config profile's controlKey, a config rewrite (account switch) left an env-backed (VELA_RUNTIME_KEY) session serving the previous account's plan/balance. Keep configMtimeMs for env auth too so a config/account change yields a fresh key. Regression added in vela.test.ts. * fix(amr): fingerprint the configured AMR env in the live-account cache key Address @nettee 11:35 review on #4684: configMtimeMs alone wasn't enough. The AMR env credentials (VELA_RUNTIME_KEY / VELA_LINK_URL) can come from agentCliEnv.amr in app-config, and env-backed sessions report user:null — so switching the Settings-backed credential from account A to B on the same profile, without touching ~/.amr/config.json, left the revision at `env|profile|1||<same-mtime>` and let B reuse A's cached plan/balance. readVelaCredentialRevision now adds a non-secret credentialFingerprint (sha256 of the runtime key + link url, truncated) and velaLiveAccountCacheKey includes it. Regressions: a unit test (env key differs by fingerprint) and a route test that rewrites agentCliEnv.amr's VELA_RUNTIME_KEY without touching the config and asserts the second account does not inherit the first's cached billing. * feat(amr): label + stack balance/plan in the switcher account row; fix its tests UI: the inline switcher Open Design account row rendered "$247.51 Plus" with no labels, so users couldn't tell what the two values were. Balance and plan now stack on two labelled lines ("可用余额 / Available balance" and "当前套餐 / Current plan"), with the Upgrade CTA on the plan line. The settings.amrBalance / settings.amrPlan labels are made descriptive across all 19 locales (the settings card reuses them), so the meaning is explicit everywhere. Tests: rewrite InlineModelSwitcher.test.tsx for the account-row structure — the agent radio now carries status only in its aria-label (icon-only visible), and the sign-in / signing-in / cancel affordances live on the inline-model-switcher-account-action button. Redundant within(amrButton) getByText assertions are dropped (the radio-name query already asserts status); pending/error assertions target the account row. 22/22 green. * test(amr): align AmrLoginPill tests to the Open Design copy Account-status group is labelled 'Open Design account status', the console link is 'Console', and the compact sign-in error reads 'Sign-in failed.' — update the queries to match. 29/29 green. * feat(amr): redesign the account row — tier badge + balance subtitle; short labels Iterated on the Open Design account row in both the inline model switcher and the runtime-switcher (avatar) list so the plan/balance no longer collide: - Plan tier renders as a small neutral pill right after the "Open Design" name (identity + tier read as one group, à la Claude's "name · Max"). - Balance moves to a second line below the name, labelled "余额 $247.51" (small grey label + bold value) so users know what the number is. - Upgrade stays as the trailing action, vertically centred against the two lines. Account logo bumped to 24px to balance the taller row. - Revert the amrBalance / amrPlan labels back to the short "余额 / 套餐" ("Balance" / "Plan") across all 19 locales. * test(amr): align remaining web specs to the Open Design copy + account row Update the rest of the web component specs that still asserted the pre-rename wording / old account-row shape (the `Web workspace tests` red CI @nettee flagged): - EntryShell.onboarding: "Sign in to Open Design Cloud" -> "Sign in to Open Design"; "AMR sign-in failed." -> "Sign-in failed.". - SettingsDialog.execution: AMR card queried as /^Open Design\b/ (was /^Open Design AMR\b/). - HandoffButton: agent label "Open Design"; website link "打开 Open Design 官网". - AvatarMenu: drop the two obsolete avatar console-shortcut specs (the avatar console link was removed when the row was consolidated; the upgrade entry remains covered by the daemon parser + contract). Full web suite green: 3403 passed. * test(amr): cover the avatar-row upgrade CTA (plan/balance + stamped link) Replaces the two removed avatar console-shortcut specs with focused coverage for the new runtime-switcher upgrade path (@nettee): mocks a signed-in /api/integrations/vela/status, opens the menu on the non-prod `test` profile, asserts the rendered plan badge + balance, and verifies the clicked upgrade link carries view=plans and od_entry_source=avatar_amr_upgrade attribution. * test(e2e): update avatar visual spec to the redesigned account row The Playwright visual (settings-workspace) job was red on a functional assertion, not a pixel diff: the spec queried the removed `.avatar-amr-account-link` console entry and 'AMR account' / 'Balance & recharge' copy. Rewrite it to override the signed-out status with a signed-in plan/balance, then assert the Open Design row renders the Plus badge + $247.51 and the upgrade link points at ?view=plans. * chore: re-trigger CI on updated main — needs-validation gate moved to merge_group (#4714) * test: capture Open Design account balance visuals * chore: retrigger workspace validation --------- Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> Co-authored-by: a1chzt <chizblank@gmail.com> * [codex] keep AMR wallet balance stable during refresh (#4730) * fix: keep AMR wallet balance visible during refresh * fix: remove AMR wallet refresh hover tooltip * chore: retrigger validation * fix(ci): align AMR wallet branch validation * feat(web): Open Design 云账户入口 UI 优化(名牌/置顶/新 logo) (#4787) * feat(web): polish Open Design cloud account surfaces (plan badge, pin-to-top, new logo) - 模型切换器(InlineModelSwitcher): Open Design 账号卡片上位为可点选入口并置顶, 移除重复的代理行 - 头像菜单(AvatarMenu): Open Design 置顶代码代理列表; 执行模式选中态改为勾选标记(不再与代理选中态争色块) - 统一套餐名牌: 新增共享 PlanBadge 组件(全局 CSS, 跟随主题色), 替换设置页/切换器/头像菜单三处各自实现 - 设置页 CLI 卡片: 删除无用的钱包刷新按钮与"更新于…缓存"行; 余额统一为两位小数 - 全局替换 AMR logo 资产(amr.svg) * test(web): align AgentIcon AMR assertion with new logo fill --------- Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> * fix(web): preserve AMR wallet balance in switcher * fix(amr): avoid stale wallet status on refresh failures * fix(web): align avatar AMR balance row * fix(web): avoid stale AMR wallet snapshots * fix(web): keep avatar AMR console link * fix(amr): support wallet fallback for older vela cli --------- Co-authored-by: michaeltaxman911-hue <michaeltaxman911@gmail.com> Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
eef6efa5c7 |
[codex] show AMR wallet balance in Open Design (#4675)
* feat: show AMR wallet balance * fix: preserve AMR wallet reauth state * chore: retrigger PR validation * fix: bound AMR wallet balance fetch * chore: retrigger wallet validation * chore: bump bundled Vela CLI to 0.0.18 * chore: retrigger Vela CLI validation * chore(nix): refresh pnpm deps hash |
||
|
|
d19c53d274 |
Agent-driven brand extraction + two-loop memory overhaul (#4260)
* Implement brand management routes and CLI support - Added `brand-routes.ts` to handle HTTP endpoints for brand operations: listing, extracting, retrieving details, deleting, and serving logos. - Introduced `brands-cli-help.ts` for CLI commands related to brand management, including usage instructions for listing, creating, retrieving, and deleting brands. - Updated `cli.ts` to integrate brand commands into the existing CLI structure, allowing headless management of brands via the command line. - Created supporting files for brand metadata handling, including `design-md.ts` for rendering brand information in markdown format and `index.ts` for the brand engine API. - Implemented `prefetch.ts` to fetch and process brand material from specified URLs, ensuring a streamlined extraction process. - Enhanced server setup in `server.ts` to register brand routes and manage brand-related data effectively. This commit establishes a comprehensive framework for managing brands within the application, facilitating both HTTP and CLI interactions. * Enhance memory management and onboarding experience - Introduced canonical profile labels to ensure consistent handling of user input in profile forms, preventing duplicate entries. - Updated the `parseProfileBody` and `captureProfileFromForm` functions to utilize the new canonical label matching. - Added a memory callout section in the onboarding view to highlight the benefits of memory usage, including personalized responses and reduced setup questions. - Implemented new UI elements in the onboarding view to improve user engagement with memory features. - Expanded i18n support for new onboarding messages related to memory benefits across multiple languages. * Refactor onboarding flow and enhance design system integration - Updated the onboarding process to include a new brand extraction step, replacing the previous newsletter step. - Adjusted the tracking logic to reflect the new onboarding steps, ensuring accurate analytics for user progress. - Improved the UI for the onboarding view, including new input fields for email collection during the brand extraction phase. - Refined the EntryShell component to remove outdated comments and clarify the onboarding renderer's purpose. - Enhanced CSS styles for the onboarding steps to improve layout and user experience. - Updated internationalization strings across multiple languages to reflect changes in the onboarding flow and brand extraction messaging. * Add brand management features and enhance font handling - Introduced new modules for managing brand assets, including `chrome.ts` for headless Chrome operations and `fonts.ts` for self-hosting web fonts. - Implemented `prefetch.ts` to streamline the brand material extraction process, allowing for efficient harvesting of colors, fonts, and logos. - Enhanced the brand system with new schema definitions in `schema.ts` to support brand color and font management. - Developed the `engine` module to integrate brand building and rendering processes, including token derivation and artifact generation. - Improved the overall structure and organization of brand-related files for better maintainability and scalability. * Enhance brand extraction and project management features - Updated `brand-routes.ts` to include new dependencies for project management, allowing for the registration of brand-related projects. - Modified the `extractBrand` function to support project ID and system files, improving the brand extraction process. - Enhanced the CLI commands in `cli.ts` to handle project IDs during brand creation, enabling better tracking of brand projects. - Updated the server setup in `server.ts` to register new project-related routes. - Improved the UI components to display project information associated with brands, including buttons for opening projects in the `BrandDetailView` and `BrandsTab`. - Added new metadata fields in the contracts to support project tracking and management for brands. This commit establishes a more robust framework for managing brand projects, enhancing both backend and frontend functionalities. * Enhance onboarding profile management and memory persistence - Added new canonical profile labels for 'Organization size', 'Use cases', and 'Discovery source' to improve user input consistency. - Introduced `OnboardingProfileState` type to manage onboarding profile data more effectively. - Implemented functions to build and persist the onboarding profile body to memory, ensuring user selections are saved accurately. - Updated the `OnboardingView` component to handle profile persistence during navigation and submission steps. - Enhanced tests to verify that user selections are correctly persisted to the memory profile. This commit improves the onboarding experience by ensuring that user inputs are consistently captured and stored, enhancing overall user engagement with the application. * Reflow brand extraction into an agent-driven, live flow Replace the deterministic SSE prefetch/preview/system pipeline with an agent-driven extraction: POST /api/brands now reserves the brand and stands up a backing project with the target site open in an in-app browser tab plus a seeded prompt, so the agent measures, synthesizes brand.json incrementally, and the user can clear anti-bot walls by hand. New /preview and /finalize routes let the agent render the kit page live and register the resulting user:<id> design system, so extracted brand facts persist as a structured, reusable brand kit instead of a one-shot deterministic guess. Adds the brand-extract skill (SKILL.md + brand-kit.html template), kit-render engine, brand-extraction-engine tests, brand project covers in the Designs tab, onboarding extract handoff, and the matching od brand extract/preview/ finalize CLI subcommands and contract updates. Co-authored-by: Cursor <cursoragent@cursor.com> * Sediment finalized brands into structured memory Reflow a finalized brand into the memory store (brandToMemoryEntries + reflowBrandToMemory) so future chats can ground vague requests in the brand's palette, type, voice and rules. finalizeBrand now wires through the runtime dataDir and best-effort persists the brand, MemoryChangeEvent gains a 'brand' source, and the brand kit render hardens its inline JSON escaping. Adds brand.previewEmpty / brand.viewDetails across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Implement logo fallback and imagery support in brand extraction - Introduced a deterministic logo fallback mechanism to ensure that brand extraction processes can retrieve and save site logos, even when the agent fails to do so. - Enhanced the `startBrandExtraction` and `finalizeBrand` functions to utilize the new logo fallback, allowing for better handling of logo assets. - Added support for imagery samples in brand validation, enabling the inclusion of representative images in the brand kit. - Updated the brand kit rendering to include self-hosted fonts and imagery, improving the overall presentation of brand assets. This commit strengthens the brand extraction workflow by ensuring that logos and imagery are reliably captured and displayed, enhancing the user experience in brand management. * Enhance memory management with rule proposal and verification features - Introduced new functionality for distilling annotations into rule proposals, allowing users to suggest rules based on in-canvas annotations through the `od memory rule suggest` command. - Implemented a verification system that programmatically enforces compliance with active rules during artifact generation, ensuring that all active rules are covered in the self-verify scorecard. - Added endpoints for managing verification outcomes, including listing, removing, and clearing verification records, enhancing the transparency of the verification process. - Updated the memory management system to support the retrieval of active rule entries, ensuring that only linked rules are considered during verification. - Enhanced tests for both rule proposal generation and verification processes to ensure reliability and correctness. This commit strengthens the memory management capabilities by integrating rule proposals and verification, improving the overall user experience in managing design rules and ensuring compliance. * Distill review annotations into memory and enforce self-verify scorecard Add distillAnnotationsToMemory to mine inline preview comments/highlights/ marks into durable feedback + rule memory via a dedicated distiller prompt, threaded through the existing extract pipeline with an 'annotation' change source. Tighten the self-verify prompt (daemon + contracts) to state the daemon programmatically checks the scorecard, so a missing or uncovered scorecard on an artifact turn is an enforcement failure. Cover the rule suggest and verification-history routes with tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Apply brand design system through web config on "Use in new chat" Thread onApplyDesignSystem from the entry shell into BrandsTab so the brand's registered design system is applied via the web config channel instead of a bare daemon PATCH that left the Home composer stale. Add a transient home-intent latch + event so the Brands tab can request the Prototype chip on the already-mounted HomeView, which consumes it once the plugin catalog loads. Co-authored-by: Cursor <cursoragent@cursor.com> * Wire annotation distillation into background memory extraction Add a background distill pass that mines inline review annotations (comments / highlights / drawn marks) from a turn into durable memory alongside the general LLM extraction, surface an `annotation` memory toast source in the web UI, and cover the flow with a unit test. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix brand design system not applying to composer on "Use in new chat" Selecting a brand's "Use in new chat" applies the brand's design system as the default and fires the Prototype chip intent in the same synchronous click handler. HomeView consumed that intent inside the event listener, so `pickChip` ran before React committed the config change and seeded the composer's design-system field from the stale (empty) default — the composer showed "No design system" instead of the brand until a reload. Split the intent handling: the listener now only bumps a tick, and a separate effect consumes the chip after the re-render lands, so the seeded design system reflects the freshly-applied brand. Add the previously-untracked home-intent latch test coverage. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): rework Brands into Brand Kit and add Home create entry Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): harvest real cover/hero images for the Images module The brand kit's Images gallery only populated when the extraction agent remembered to save imagery — so a forgetful or bot-blocked agent (and the pre-imagery "Open Design" brand) left it empty. Add a deterministic, server-side imagery fallback (imagery-fallback.ts), mirroring the logo fallback: it parses og:image/twitter:image, large <img> (highest-res srcset/<picture>), <link rel=preload as=image>, and CSS background-image hero blocks, fetches candidates with browser-shaped headers, decodes PNG/GIF/JPEG/WebP dimensions to keep only big representative images (dropping icons/sprites/logos/tracking pixels), dedupes by content hash, and saves up to 8 of the largest into imagery/ with labeled samples. finalizeBrand runs it as a timeout-bounded, failure-tolerant safety net (injectable so tests stay offline) when the agent captured too few samples, first adopting any on-disk images. The extraction prompt and brand-extract SKILL now explicitly direct the agent to harvest the site's large/cover/hero images, filtered by rendered size. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(qa): implement deck layout validation and safety checks Add a new QA module for validating the layout of generated brand decks to ensure robustness against clipping and truncation issues. The `analyseDeckLayout` function checks for critical layout invariants, including the presence of `.slide` sections, correct container types, and necessary runtime layers. Introduce `assertDeckLayoutSafe` to enforce these checks during brand system rebuilds, preventing the deployment of decks that fail validation. Additionally, create comprehensive tests to verify the functionality of the new layout validation features. * fix(brands): apply deck shrink-to-fit synchronously so slides never clip The no-clip runtime scheduled its fit pass through requestAnimationFrame, whose callbacks are throttled while the deck is offscreen or occluded. A slide could therefore stay unscaled — and clip its content — until first paint. Fit synchronously on resize/load/fonts-ready with a trailing setTimeout settle pass for late reflow, removing the rAF dependency. Verified at the previously-broken 1024x620 viewport: container-type:size, zero truncations, runtime auto-applies scale (Problem 0.71, ASK 0.87, Product 0.97, Competition 0.97) and frame clip count drops to 0. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): let New Brand modal embed scrollable brand reference picker Add a fillHeight mode to BrandReferencePicker so the heading, quick-pick row and controls stay pinned while only the gallery scrolls inside a bounded-height parent. Wire it into NewBrandModal with a stable, spacious dialog and refresh the related newBrand/brandPicker copy across all 18 locales. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): enhance brand extraction with deterministic seed harvesting Introduce a new `seed-fallback` module to provide a server-side deterministic palette and typography seed during brand extraction. This ensures that the brand kit's initial display includes a harvested logo, an approximate color palette, and font families, improving the user experience by reducing the all-skeleton appearance during the first paint. Update the `startBrandExtraction` function to utilize this new module, allowing for a more seamless and visually appealing brand extraction process. Additionally, enhance the `BrandReferencePicker` component to reflect loading states and errors during brand extraction, ensuring users receive immediate feedback on their actions. Update related tests to verify the idempotency of the `finalizeBrand` function, ensuring that re-finalizing a brand correctly reuses the existing design system without duplication. * feat(brand-extract): enhance BrandReferencePicker and localization updates Updated the BrandReferencePicker component to reflect loading states and errors during brand extraction, improving user feedback. Added a new localization key for the brand extraction process and updated existing translations in English, Simplified Chinese, and Traditional Chinese to enhance clarity and user experience. Additionally, introduced new styles for better interaction with brand assets in the brand kit template. * feat(brands): wire in-page lightbox/masonry/asset preview + refine seed Brand-kit preview improvements for the live extraction kit: - brand-kit.html: add in-page overlay system (sandboxed iframe has no top-nav) — clickable image lightbox with prev/next, a "view all" masonry modal, and a full-page asset preview modal that loads system/artifacts/<kind>.html in an iframe. Defer auto-reload while an overlay is open so it never yanks the modal out mid-interaction. - seed-fallback.ts: prefer vivid mid-luminance hues for the seeded accent/accent-secondary, and drop icon/symbol faces (Remix Icon etc.) from the typography seed so specimens never render glyph soup. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): wire in-page lightbox/masonry/asset preview + refine seed Brand-kit preview improvements for the live extraction kit: - brand-kit.html: add in-page overlay system (sandboxed iframe has no top-nav) — clickable image lightbox with prev/next, a "view all" masonry modal, and a full-page asset preview modal that loads system/artifacts/<kind>.html in an iframe. Defer auto-reload while an overlay is open so it never yanks the modal out mid-interaction. - seed-fallback.ts: prefer vivid mid-luminance hues for the seeded accent/accent-secondary, and drop icon/symbol faces (Remix Icon etc.) from the typography seed so specimens never render glyph soup. Co-authored-by: Cursor <cursoragent@cursor.com> * i18n(web): add brandPicker.opening across remaining locales + picker test Completes the brand-reference picker i18n key that was committed only for en/zh-CN/zh-TW, so every locale satisfies the typed Dict, and lands the BrandReferencePicker extraction-feedback test left untracked by the concurrent worker. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(EntryShell): enhance AMR cloud card visibility post-detection Updated the EntryShell component to ensure the AMR cloud card remains visible after detection settles, even when the AMR runtime is unavailable. This change prevents the card from disappearing and allows it to degrade gracefully to fallback content and sign-in flow. Additionally, added tests to verify the new behavior, ensuring a better user experience during onboarding. * feat(brands): programmatic-first design system extraction + rename Make brand extraction two-phase so a usable design system is ready the moment the user enters a URL — the instant "aha" — instead of waiting on the AI agent: 1. PROGRAMMATIC-FIRST (synchronous): startBrandExtraction now harvests the site deterministically (logo, palette, typography, one-line description, cover imagery, source URL) via prefetchBrand, synthesizes a valid design system with brandFromMaterial (no LLM), and finalizes + registers it before returning. finalizeBrand is refactored into a reusable finalizeBrandCore shared by both the programmatic path and the agent path. 2. ASYNC AI ENRICHMENT: the seeded agent prompt is reframed to enrich the already-usable design system and re-finalize in place (same user:<id>), updating every artifact/template. Bounded + best-effort: a blocked/unreachable origin skips phase 1 and stays `extracting` for the agent to drive. Gated on userDesignSystemsRoot so the legacy agent-only path stays intact for tests. Also rename the user-facing "Brand Kit" surface to "Design System" across en + zh-CN strings, project names, and the enrichment prompt. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(brands): restore design-systems nav entry + reconcile BrandsTab on re-activation Address review feedback on PR #4260: 1. EntryNavRail dropped the only control that reached view==='design-systems' when Brands replaced it in the rail, leaving the still-rendered/routed design-system manager deep-link only (the entry-nav-design-systems e2e specs assert this). Restore a reachable rail entry (blocks icon, existing navDesignSystems key) alongside Brands. 2. BrandsTab only fetched once on mount, but EntryShell keeps sub-views mounted and toggles visibility, so a brand finishing extraction in its backing project never reconciled until a full reload. Refresh whenever the Brands view becomes active again, and poll while any brand is extracting (torn down once settled / when hidden). Red spec: tests/components/BrandsTab.refresh.test.tsx (fails pre-fix: fetchBrands called once, not twice). * fix(web): surface missing backing projects * fix(web): re-enable brand actions after use * fix(daemon): serve brand logos from data roots * fix(brands): reconcile failed extractions * fix(brands): non-blocking extraction start + lifecycle-accurate status (#4470) Acceptance fixes for the Brand Kit surface (PR #4260, even-ferry), addressing five reported issues: - Perf: `startBrandExtraction` no longer blocks the POST /api/brands response on the synchronous programmatic harvest+finalize. It now waits only a short budget (~1.2s) so fast origins still land on a ready, applyable design system (the instant "aha"), while slow/blocked origins return immediately on the "Extracting…" skeleton and finalize in the background. Measured: example.com POST dropped from a ~19s block to ~1.2s, then finalized to `ready` in the background. - Brand Kit stays stuck on "Extracting…" after a manual stop: the backing run's canceled/failed status now reconciles the brand to `failed` with a reason (this extends the existing reconcile; adds a regression spec). - Brand Kit ignores a backing run that is awaiting user input (anti-bot wall / question form): new reversible `needs_input` brand status, derived on read (never persisted) so it flips back once the user answers. Surfaced as a "Needs input" badge + hint in the card and a distinct status dot in the list; the list keeps polling while paused. - Home → Recent projects showed brand-extraction projects as "Not started": /api/projects now falls back to the brand lifecycle for brand projects whose run-derived status is `not_started` (extracting→running, needs_input→awaiting_input, ready→succeeded, failed→failed), so a live extraction is never shown as not started. - "Use in new chat" produced no visible result: it now carries a confirmation notice through the Home intent channel so Home shows a "Using <brand>" toast, making the navigate+apply verifiable (the composer already preselects the brand's design system). Tests: daemon brand-routes (canceled→failed, needs_input transient) and brand-extraction-engine (fast return + background finalize) specs; web BrandPreviewCard specs (needs_input badge/hint, Use-in-new-chat notice). #4 validated end-to-end against a real daemon. guard + typecheck green. * [codex] Fix brand extraction incomplete states (#4486) * fix brand extraction incomplete states * fix web memory tests for advanced modal Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix onboarding P0 brand-step navigation Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix daemon brand extraction test timeout Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix brand extraction fallback prompt state Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> * [codex] Fix brand extraction terminal status reconciliation (#4532) * Fix brand extraction terminal status reconciliation * Keep prefetched brand pages previewable * fix(brands): preserve ready extraction status Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(brands): persist terminal extraction signal Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(brands): clear stale terminal retry markers Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> * Fix brand kit modal intent handling (#4528) Co-authored-by: Siri-Ray <2667192167@qq.com> * fix(web): let design system contents scroll in narrow review layout (#4526) Co-authored-by: Siri-Ray <2667192167@qq.com> * [codex] Fix Brand Kit preview lifecycle guards (#4527) * Fix brand kit preview lifecycle guards * fix(brands): keep detail selected on delete failure Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(brands): handle delete HTTP failures Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> * ci: retrigger pull_request checks * test(e2e): stabilize entry settings shard --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: PerishCode <perishcode@gmail.com> Co-authored-by: lefarcen <935902669@qq.com> Co-authored-by: PerishFire <39043006+PerishCode@users.noreply.github.com> Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
939e3d7526 |
fix(daemon): reclassify named causes out of opaque execution_failed (#3408 P1) (#4608)
* fix(daemon): reclassify AMR/vela upstream failures out of opaque execution_failed #3408 P1: the largest engineering-view failure bucket is the opaque `execution_failed` (~5,400/7d). Sampling real Langfuse error texts showed many are not engine bugs at all but product-view failures whose (often Chinese) upstream error text the English-only detectors miss, so they fall through to the catch-all. Pull the clearest, highest-confidence AMR/vela ones into their true categories: - vela Chinese pre-charge wallet failure ("预扣费额度失败" / "余额不足" / "额度不足") -> insufficient_balance (recharge), via classifyAmrAccountFailure. - Chinese / generic rate-limit text ("速率限制" / "控制请求频率" / "rate limit" / "too many requests") -> retryable rate_limit / rate_limit_429. - vela "model ... not in allowed list" rejection -> model_unavailable / model_not_found (switch_model). Verification: - Unit: vela-errors.test.ts covers the Chinese balance detection (the authoritative change); the classifier test covers rate-limit + model wiring (its vela-errors mock is updated to mirror the new Chinese signal). - End-to-end: a new run-failure-telemetry-smoke case drives a REAL daemon run whose agent emits each production text, then classifies the REAL recorded events.jsonl — proving the text flows through the run lifecycle into the classifier and lands in the correct category (not execution_failed), rather than only passing a hand-built unit input. Refs #3408 (P1 / execution_failed deep dive), #4502. * fix(daemon): also reclassify binary-not-found failures out of execution_failed Second P1 reclassification batch (same theme as the AMR/vela one): two install/env failure shapes that the agent emits and that currently leak into the opaque execution_failed bucket are an install/PATH problem, not an engine failure. Route them to the existing cli_not_installed detail: - Windows shell "'node' is not recognized as an internal or external command". - "Error: spawn <path> ENOENT" — the executable file does not exist (distinct from spawn EPERM/EBADF/ENOEXEC where the file exists but cannot run); real codex shape where its vendored sub-binary is missing. Red specs use the real production texts; the end-to-end smoke test now also drives these two through a real daemon run and classifies the recorded events.jsonl. (Note: a missing CLAUDE_BIN is a different scenario — it goes through the daemon's preflight path, not an agent-emitted ENOENT — so the e2e case emits the real codex spawn-ENOENT text via the agent, matching the production origin.) Refs #3408 (P1 / execution_failed deep dive), #4502. * fix(daemon): reclassify prompt-size / ACP-protocol / auth texts out of execution_failed Batch A of the P1 execution_failed reclassification — three more named causes sampled from Langfuse that currently leak into the opaque bucket, routed to existing categories (no new contract types): - "Prefill context too large" (local MLX runtime) -> prompt_too_large. - ACP "thread/start failed" -> agent_protocol_error. - vela "login fail: carry the API secret key" -> auth (auth_required). Red specs use the real production texts; the end-to-end smoke test drives all three through a real daemon run and classifies the recorded events.jsonl. Refs #3408 (P1 / execution_failed deep dive), #4502. * feat(analytics): classify local-model-not-loaded (LM Studio) out of execution_failed The single largest remaining slice of the opaque execution_failed bucket (#3408 P1, ~1/5 by Langfuse sampling) is opencode pointed at a local LM Studio provider that has no model loaded: "No models loaded. Please load a model … or use the 'lms load' command." Verified this is user-action, not a model-selection bug on our side: OD passes `-m <model>` to opencode only when the user picks one, and OD has no LM Studio integration (the user configures it as an opencode provider). The error is "No models loaded" (zero loaded), not "model not found" — independent of the model name we send, the local app returns this until the user loads a model. We cannot load it for them; the proportionate fix is to stop hiding it in the opaque bucket and give an actionable category. - New `TrackingRunFailureDetail` value `local_model_not_loaded`. - modelUnavailableDetail() maps "no models loaded" / "lms load" → it, so the existing model_unavailable block routes it (category model_unavailable, user_action switch_model). - Red spec + end-to-end smoke case (real daemon run, recorded events.jsonl). Refs #3408 (P1 / execution_failed deep dive), #4502. * feat(analytics): classify stale Claude session resume as session_resume_expired When `--resume <id>` targets a session that no longer resolves, the daemon already clears the stale id and tells the user to resend (a fresh session is re-seeded with the full transcript). This is a recoverable session-lifecycle failure, but it currently sits in the opaque execution_failed bucket (#3408 P1). Root cause verified (code + claude-code-guide): the stored id pointed at a real session (the daemon only persists it on success or a committed resumable failure), so "no conversation found" comes from Claude's own 30-day cleanupPeriodDays prune, a CLAUDE_CONFIG_DIR change, a cwd/worktree change, or a prior run killed before the session was flushed (`-p` mode has no fsync guarantee). All recoverable by a fresh session — which the daemon already does. - New `TrackingRunFailureDetail` value `session_resume_expired`. - Detector matches both the daemon's surfaced message and the raw Claude CLI shapes ("no conversation found with session id" / "no session found" / "session <id> not found"), tightened so it cannot bridge an unrelated "session …" and a far-away "404 Not Found". - Classified as process_exit / session_resume_expired, retryable, user_action retry (NOT resumable — isResumableFailure stays false, so we never retry by resuming a session that is already gone). - Red specs + end-to-end smoke case. This is the analytics/labelling half. Auto-recovery (transparently re-running with a fresh session so the user never sees a failure) is a separate, larger change tracked with the run-resume work. Refs #3408 (P1 / execution_failed deep dive), #4502. * refactor(daemon): narrow session_resume_expired daemon-message match Address review: '\bcould not be resumed\b' was broader than the rest of the helper's Claude session-miss shapes and runs before other process-exit fallbacks, so an unrelated future 'could not be resumed' stderr would be mislabeled. Anchor it to 'session could not be resumed' (matches the daemon's actual surfaced message 'The previous Claude session could not be resumed …'). |
||
|
|
d5d9a761ca |
refactor(daemon): organize source modules (#4454)
* docs(daemon): add agent guidance * refactor(daemon): organize source modules * chore(guard): update daemon design system import * refactor(daemon): shorten domain module filenames * fix(daemon): correct pitch-deck manifest path after test move The pitch-deck manifest test moved one directory deeper into tests/design-systems/ but kept the old '../../..' repoRoot, resolving to apps/ instead of the repo root and failing to find plugins/_official/examples/html-ppt-pitch-deck/open-design.json. * fix(daemon): retarget media model verifier Generated-By: looper 0.9.10 (runner=fixer, agent=codex) * docs(daemon): refresh AGENTS legacy paths after module reorg * test(e2e): allow manual edit P0 CI scaling Generated-By: looper 0.9.10 (runner=fixer, agent=codex) * test(daemon): fix frontmatter import path Generated-By: looper 0.9.10 (runner=fixer, agent=opencode) * fix(web): avoid replaying legacy terminal runs Generated-By: looper 0.9.10 (runner=fixer, agent=opencode) * fix(web): preserve legacy terminal replay recovery Generated-By: looper 0.9.10 (runner=fixer, agent=opencode) * fix(daemon): correct live-artifacts test data root * fix(daemon): cover moved route registrars in context contract Generated-By: looper 0.9.10 (runner=fixer, agent=opencode) * fix(daemon): restore moved runtime artifact imports Generated-By: looper 0.9.10 (runner=fixer, agent=opencode) |
||
|
|
8ff19c6b41 |
[codex] Add main-risk smoke and E2E coverage (#4322)
* test(e2e): cover daemon reload artifact recovery * test(e2e): expand critical flow coverage * test(daemon): add startup and recovery smoke coverage * test(e2e): add visual and desktop chrome smoke coverage * ci: gate packaged onboarding smoke to nightly * fix(web): repair artifact replay type narrowing Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(ci): repair preflight smoke coverage checks Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): tighten artifact recovery polling Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): keep error recovery retries alive Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(e2e): relax brittle UI P0 assertions Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): recover pointer artifact outputs Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(e2e): stabilize manual edit reload smoke Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(e2e): avoid noisy win onboarding cleanup Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): stop failed artifact recovery polling Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(daemon): isolate startup smoke data root Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): recover standalone html artifacts Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(web): guard pointer artifact recovery by run time Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(ci): narrow strict visual gate to critical smoke Generated-By: looper 0.9.7 (runner=fixer, agent=codex) * fix(ci): limit PR visual capture to critical cases * fix(ci): keep PR visual capture complete * fix(web): keep reload artifact recovery retryable * fix(ci): clarify PR visual gate layering * fix(e2e): stabilize visual home P2 capture * fix(e2e): align visual gallery detail clicks * fix(e2e): preserve packaged install during onboarding cleanup * fix(ci): shard PR visual capture * fix(ci): remove duplicate nix validation from ci * fix(ci): keep visual capture screenshots flat * fix(ci): point nix hash autofix at ci-nix * fix(ci): stabilize PR P0 recovery gate * fix(web): ignore stale artifact pointers during recovery * fix(web): reset BYOK model after clearing key * ci: run critical UI P0 subset on PRs * ci: combine PR UI P0 critical shards * ci: combine PR visual capture shards * ci: preserve visual capture shard manifests * fix(web): clear ProjectView delayed timers on unmount * test(e2e): harden onboarding connect gate coverage * test(e2e): align AMR auth recovery and visual catalog timeout |
||
|
|
1e2f47fb8d |
fix(amr): surface vela login URL/code so sign-in survives a failed browser handoff (#4402)
* fix(amr): surface vela login URL/code so sign-in survives a failed browser handoff AMR (vela) sign-in is a device-authorization flow: `vela login` prints the activation URL + user code to stdout, then best-effort opens the browser and keeps polling until the server-issued expiry (>5min). The daemon spawned it headless, only read the first 250ms of output, and the UI showed nothing but a "Signing in…" spinner — so when the browser failed to open (stripped PATH in a packaged build, blocked network) the user had no URL to fall back to and stared at a dead spinner for the full AMR_LOGIN_TIMEOUT_MS (5 minutes). - daemon: parseVelaLoginActivation + lifetime stdout/stderr capture surface activationUrl / userCode / browserOpenFailed on VelaLoginStatus. - web: the AMR account pill's signing-in state now shows an activation block (open sign-in page + copy code) as soon as the URL is known, instead of only a spinner; it adapts to the active theme. - web: an AMR run that fails with AMR_AUTH_REQUIRED now offers inline sign-in in the chat error card (the login pill) and auto-retries once on success, rather than bouncing the user out to Settings. Tests: red->green parser spec (validated against real vela login output), AmrAccountControl activation-block specs, ChatPane inline-auth specs. * test(amr): real-vela E2E for the login activation capture seam Spawns an actual `vela login` child through the daemon's spawnVelaLogin and asserts readVelaLoginStatus surfaces the real activation URL + code while the login is in flight, then cancels (never approves, so no config is written and any existing session is untouched). Covers the daemon capture wiring (beginLoginActivationCapture -> readVelaLoginStatus) that the pure parser spec can't reach. Auto-skips where the vela CLI isn't installed, so CI is unaffected. * fix(amr): polish inline auth retry layout * fix(daemon): try AMR login direct first, fall back to IPv4 proxy loopback AMR API proxy (forced IPv4) to dodge broken `amr-api.open-design.ai` edge paths for some hosts (#3726). But behind a corporate transparent proxy that hijacks `amr-api.open-design.ai` onto an internal gateway (e.g. 飞连/CorpLink → 30.x), the extra proxy hop makes the upstream lose the client IP and reject device authorization with `502: Invalid IP address: undefined` — even though the direct path resolves fine. Result: AMR "Authorize" fails in every packaged build that ships this change (beta/main), while release builds without it still work. Only `vela login` was rerouted; the model catalog and chat inference already go direct (to amr-link / VELA_LINK_URL), so this restores login to the same direct-first default. The IPv4 proxy is kept strictly as a fallback: the route now starts login over a direct connection and only retries through the proxy when the direct attempt fails to start — never when a login is already in flight. Both cohorts win: transparent-proxy users succeed directly, broken-edge users (#3726) still get the proxy. Tests: fake-vela gains FAKE_VELA_LOGIN_FAIL_WITHOUT_API_URL to model a broken direct path; the login suite now asserts direct-first (no VELA_API_URL on a healthy path) and proxy-fallback (VELA_API_URL set only after a direct failure, incl. OD_PUBLIC_BASE_URL derivation). * fix(amr): clear stale login activation details * fix(amr): refresh login status immediately after auth starts * fix(amr): sync inline auth card with login status * fix(amr): tighten inline auth retry state * fix(amr): wait for direct login activation before fallback * fix(amr): show activation details only in chat auth card * fix(amr): guard clipboard copy explicitly in the activation block navigator.clipboard?.writeText(...).then(...) already short-circuits the whole chain when the clipboard API is absent, but make the guard explicit so the copy handler reads as a clean no-op rather than relying on optional-chain reach. * fix(amr): don't kill a slow-but-healthy direct login; only fall back on real failure The login activation watchdog turned a merely slow direct CreateDeviceAuthorization into a hard failure: after the grace it killed the direct child and let the route retry through the IPv4 proxy — the exact proxy hop that 502s on the transparent-proxy networks this fix targets. Now the grace only stops blocking the request; a still- running direct attempt is left alive (the UI polls /status), and the proxy fallback fires only on an explicit pre-activation exit/error. The grace is overridable via OD_AMR_LOGIN_ACTIVATION_GRACE_MS so the new route test can exercise the slow-direct path quickly (fake-vela gains FAKE_VELA_LOGIN_ACTIVATION_DELAY_MS). Addresses review feedback on #4402. * test(e2e): AMR auth failure now signs in inline, not via Settings The chat AMR_AUTH_REQUIRED card replaced its 'open Settings to authorize' bounce with an inline sign-in pill (POSTs vela login in place, auto-retries on success). Update the [P0] runtime-recovery spec to assert the inline flow: clicking Authorize & retry issues the login request and opens no Settings dialog. The non-AMR 'Switch to AMR' recommendation flow (AmrGuidance) is unchanged. * fix(amr): only auto-retry inline auth on a real sign-in transition The inline AMR auth card auto-retried the failed run whenever /status reported signed-in — via both the 500ms shared-status poll and the pill's onStatusChange. Because the per-assistant-id guard resets on each retry (new run = new id), a run that keeps failing AMR_AUTH_REQUIRED while /status reads signed-in retried forever (caught by the runtime-recovery UI P0 spec). Now we retry only on an explicit signed-out -> signed-in transition; a null/unknown status never fabricates one, and once signed-in we don't retry again until an explicit signed-out is seen. - Unit specs updated to drive the transition (and a loop-guard spec for the already-signed-in case). - e2e: the AMR auth spec asserts the inline sign-in flow (no Settings bounce); the switch-to-Codex spec mocks /status signed-out, the realistic state for an AMR_AUTH_REQUIRED failure, so the Authorize & retry action is offered. * fix(amr): fall back to the proxy on a post-grace direct login failure The direct-first login route only fell back to the IPv4 proxy when the direct child crashed inside the 250ms startup grace. A direct vela login that survives the grace and then errors out before printing its activation URL (the common broken-edge shape, #3726) returned 202 and stranded the user on a dead direct login. Re-introduce waitForActivation: the route now blocks until the direct attempt reaches device-auth steady state (prints its activation URL) or exits/errors before it; only a pre-activation exit/error triggers the proxy retry, and a slow-but-healthy direct login is left running (never killed). fake-vela now prints the activation block (so the daemon can detect steady state) and gains FAKE_VELA_LOGIN_FAIL_WITHOUT_API_URL_DELAY_MS; the route suite gains a regression where the direct attempt fails past LOGIN_STARTUP_GRACE_MS and must still reach the proxy. Addresses review feedback on #4402. |
||
|
|
98477c0924 |
fix(daemon): try AMR login direct first, fall back to IPv4 proxy (#4409)
#4210 routed daemon-started `vela login` device authorization through a loopback AMR API proxy (forced IPv4) to dodge broken `amr-api.open-design.ai` edge paths for some hosts (#3726). But behind a corporate transparent proxy that hijacks `amr-api.open-design.ai` onto an internal gateway (e.g. 飞连/CorpLink → 30.x), the extra proxy hop makes the upstream lose the client IP and reject device authorization with `502: Invalid IP address: undefined` — even though the direct path resolves fine. Result: AMR "Authorize" fails in every packaged build that ships this change (beta/main), while release builds without it still work. Only `vela login` was rerouted; the model catalog and chat inference already go direct (to amr-link / VELA_LINK_URL), so this restores login to the same direct-first default. The IPv4 proxy is kept strictly as a fallback: the route now starts login over a direct connection and only retries through the proxy when the direct attempt fails to start — never when a login is already in flight. Both cohorts win: transparent-proxy users succeed directly, broken-edge users (#3726) still get the proxy. Tests: fake-vela gains FAKE_VELA_LOGIN_FAIL_WITHOUT_API_URL to model a broken direct path; the login suite now asserts direct-first (no VELA_API_URL on a healthy path) and proxy-fallback (VELA_API_URL set only after a direct failure, incl. OD_PUBLIC_BASE_URL derivation). |
||
|
|
b12fbda1bb |
Track AMR handoff attribution from Open Design (#4396)
* Track AMR handoff attribution * fix: gate onboarding AMR profile sync * fix: reject non-onboarding AMR profile sources * fix: gate AMR attribution mirrors on consent * fix: normalize AMR profile mirror device id --------- Co-authored-by: Mason <jinmeihong0201@gmail.com> |
||
|
|
d20d78038c |
feat(analytics): forward onboarding profile to AMR for paid-conversion segmentation (#4280)
* feat(analytics): forward onboarding profile to AMR for paid-conversion segmentation
Open Design's onboarding survey captures a self-reported role and org size, but
they live only in onboarding component state and are discarded when the flow
ends. To let AMR segment paid conversion by who the visitor is (PM vs student,
enterprise vs solo), forward role/orgSize on the amr_entry mirror event,
anchored to the entry id — AMR's side (powerformer/vela) accepts them as
optional fields.
- Persist {role, orgSize} to localStorage on onboarding completion
(state/onboarding-profile.ts), so entries from ANY surface (chat error card,
settings, model switcher) — not just during onboarding — can forward them.
- Attach the persisted profile to the attribution and the mirror body in
amr-attribution.ts. Kept off the redirect URL; only the consent-gated mirror
channel carries it.
- Thread odRole/odOrgSize through the daemon's AmrEntryAnalyticsPayload type and
parseAmrEntryAnalyticsPayload, which rebuilds the payload field-by-field and
would otherwise strip unknown keys. Values are open strings (bounded to 64
chars), not a union, so a new onboarding option never forces a contract bump;
an over-long value drops the event rather than mirroring abuse.
Surfaces: UI (onboarding persistence) only — this is an analytics-forwarding
change with no new user-facing capability or CLI surface.
* feat(analytics): forward full onboarding survey (useCase, source) to AMR
Extend the forwarded profile from role/orgSize to the full About-you survey,
adding useCase and source so AMR can also segment paid conversion by use case
and acquisition channel. useCase is multi-select, so it travels as a bounded
string array (element length + count capped) through the persistence helper,
the attribution, the contract type, and the daemon parser, which validates and
passes the array through rather than stripping it. source is a bounded open
string like role/orgSize. Also switch the parser unit test to a static import
with a .js extension to satisfy node16 module resolution.
|
||
|
|
07693d0921 |
fix(daemon): proxy AMR login API over IPv4 (#4210)
* fix(daemon): proxy AMR login API over IPv4 Route daemon-started Vela login through a loopback AMR API proxy so the app can avoid broken amr-api.open-design.ai edge paths during device authorization. Preserve explicit VELA_API_URL overrides for operators and tests. Generated-By: looper 0.9.5 (runner=worker, agent=codex) * fix(daemon): respect public base URL for AMR proxy Use the daemon public-base-url resolver when deriving the default Vela API proxy URL and cover OD_PUBLIC_BASE_URL in the login route test. Generated-By: looper 0.9.5 (runner=fixer, agent=codex) * fix(daemon): forward raw AMR proxy bodies Generated-By: looper 0.9.5 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
d4c6bbd9e1 |
Add Spark self-hosted CI runner workflows (#3750)
* Probe Spark runner CI path * Add Spark runner setup validation * Harden Spark setup install probe * Add Spark runner policy checks * Add Spark runner unit checks * Add Spark runner typecheck path * Add Spark runner daemon checks * Isolate Codex runtime tests from host PATH * Isolate headless plugin prompt capture * Fix headless prompt capture script syntax * Add Spark runner web checks * Add Spark runner build checks * Add Spark runner browser checks * Compose Spark runner CI gate * Harden Spark runner gate diagnostics * Shard Spark runner daemon checks * Add daemon parallel CI experiment * Install dependencies for daemon parallel CI * Use Vitest parallelism for daemon runner checks * Implement manual hosted backup CI gate * Prepare pnpm for hosted CI backup * Use dynamic matrix for hosted CI modes * Emit searchable CI timing lines * Fix hosted CI Node version * Optimize CI runner scheduling * Stabilize runner CI all topology * Rebalance runner CI all lanes * Harden ci-gate run identity checks Generated-By: looper 0.9.5 (runner=fixer, agent=codex) * Fix CI runner cold-start pnpm bootstrap Generated-By: looper 0.9.5 (runner=fixer, agent=codex) * Require both ci-gate runs for both-success Generated-By: looper 0.9.5 (runner=fixer, agent=codex) * Fix CI summary pnpm metadata timing Generated-By: looper 0.9.5 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
90aab29695 |
feat(analytics): attribute AMR entry conversions (#3579)
* feat(analytics): attribute AMR entry conversions
* test(web): cover AMR attribution entry points
* fix(daemon): gate AMR entry mirror on analytics consent
The /api/integrations/vela/analytics-entry route forwarded every AMR
entry click to the external AMR ingest endpoint regardless of consent:
mirrorAmrEntryAnalytics fired whenever globalThis.fetch existed, so an
opted-out user's click was still shipped to a third-party service.
Gate the mirror the same way the analytics-capture path does:
- null readAnalyticsContext (no x-od-analytics-* headers => Privacy
metrics off) short-circuits to { mirrored: false }.
- defense-in-depth re-read of telemetry.metrics from app-config drops
the mirror even if a stale header leak slips through after opt-out.
Adds two daemon route red specs asserting that a no-headers request and
a metrics-off request never hit the capture server.
---------
Co-authored-by: lefarcen <935902669@qq.com>
|
||
|
|
fa59871428 |
Merge origin/main into release/v0.9.0 — back-merge to reconcile v0.9.0 with main
Syncs release/v0.9.0 with main so PR #3282 (release/v0.9.0 → main) merges
cleanly. 17 conflicts resolved by commit-time + per-area ownership:
- Comment/edit/preview: keep main (newer). FileViewer combines main's #3438
(pin manual-edit inspector, no full-height panel) + #3347 deck/queue work
WITH release's #3401 hover-card anti-flicker grafted on; #2879 Button kept.
- Toolbar: footer layout (reviewer-validated,
|
||
|
|
3083388a1a |
Add launch review regression coverage (#3300)
* Add main launch review E2E coverage * Add daemon launch review regression coverage * Tighten plugin authoring completion regressions * fix(web): preserve deck slide on preview switches Generated-By: looper 0.9.2 (runner=fixer, agent=codex) * Add project detail regression coverage |
||
|
|
601183efbb |
feat(daemon): load AMR model presets before remote catalog (#3425)
* spec * impl * fix * remove default model content * fix(amr): keep model catalogs fresh across env changes Generated-By: looper 0.9.2 (runner=fixer, agent=codex) * fix(amr): bust logout cache and stop preset polling Generated-By: looper 0.9.2 (runner=fixer, agent=codex) * test(amr): cover logout cache invalidation and bounded polling Generated-By: looper 0.9.2 (runner=fixer, agent=codex) * fix(amr): start preset polling before agent probe Generated-By: looper 0.9.2 (runner=fixer, agent=codex) * fix(amr): clear stale web cache on env rescan Generated-By: looper 0.9.2 (runner=fixer, agent=codex) * fix(amr): restart preset polling after sign-in Generated-By: looper 0.9.2 (runner=fixer, agent=codex) * fix(daemon): remove extra AMR spawn env arg Generated-By: looper 0.9.2 (runner=fixer, agent=codex) |
||
|
|
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
|