mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
codex/motion-hyperframes-retirement
597
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c68f999e80 | feat(motion): retire standalone HyperFrames creation | ||
|
|
411162cb1b |
[OPEND-4256] fix(sidecar): report a target's signal death as a real signal, not 128+N (#8995)
* fix(sidecar): re-raise a target's terminating signal instead of encoding it as 128+N A supervised target that calls process.exit(137) was reported as SIGKILL because the supervisor folded signals into the numeric exit-code space and the packaged observer reversed 128+N back into a signal. The supervisor now terminates itself with the target's unforwarded signal (POSIX), so owners see a real signal; numeric exits pass through untouched and are classified as crashes. * fix(sidecar): restore default disposition before re-raising the target's signal Node handles SIGUSR1 (inspector) and ignores SIGPIPE, so a plain self-signal did not terminate the supervisor and the owner saw exit code 1 instead of the target's signal. Reset the disposition first; cover SIGUSR1/SIGPIPE with the real-supervisor fixture. --------- Co-authored-by: heggria <bshengtao@gmail.com> |
||
|
|
417b81c752 |
[OPEND-4240] chore(tooling): migrate workspace to TypeScript 7 (#8957)
* chore(tooling): migrate workspace to TypeScript 7 * fix(docker): stage plugin manifests before native web type checking * perf(guard): batch native parsing with bounded AST retention * fix(tooling): port main's AST tests to native TypeScript |
||
|
|
5b6fbb55ea |
[OPEND-3712] feat(telemetry): stability metrics for launch, project entry, and memory (#8805)
* feat(analytics): define client metrics schema and unify telemetry metadata * feat(desktop): measure process startup phases through business readiness Add launch_attempt, business_ready, and launch_result timing with monotonic phase offsets, per-attempt correlation, shared app session identity, explicit process/activation measurement boundaries, and idempotent terminal results. Preserve packaged sidecar phases across the desktop handoff and report bounded startup failures without blocking successful startup on telemetry. Window mount/reveal alone does not declare business Ready. Preload/web owners must wire od:startup:context and od:startup:business-ready with matching launch_attempt_id/app_session_id plus required_data_ready/actions_ready=true. Align with the shared event registry/schema and envelope fields (device_id, event_id, app_session_id, client_type, capture_source, env, channel, app_version, os_version, arch, occurred_at, reported_at, capability), plus startup_type, launch_attempt_id, started_at, duration_ms, outcome, reason, failure_stage, stage_offsets_ms, stage_durations_ms, measurement_start, start_time_source, and os_launch_time_available. The existing daemon safety bridge overwrites some envelope fields; shared-envelope alignment stays with that owner. Coverage limits: no OS icon-click timestamp or separate launcher-process span; no new packaged launch report without real installation identity; failures before packaged runtime context is armed and forced process exits can be unobserved. Cold means a fresh desktop process; warm means hidden-window restore; reopen means desktop restored from a headless owner. Measurement acceptance and preload ownership need maintainer confirmation; native QA/publication remain. Validation (local Node 24 / pnpm 10.33.2): - Red specs captured before implementation. - desktop focused tests: 7 files, 47 passed. - packaged focused tests: 6 files, 87 passed. - pnpm --filter @open-design/desktop typecheck: passed. - pnpm --filter @open-design/packaged typecheck: passed. - pnpm guard: passed. - pnpm typecheck: passed after building missing local workspace artifacts. - git diff --cached --check: passed. Local commit only. No web/memory-sampling changes or external operations. * feat(web): measure project navigation intent to actionable ready * feat(observability): add bounded periodic process memory sampling * feat(telemetry): integrate launch readiness and bounded process memory metrics Unify metric envelopes, classify process launches with a persisted heuristic, wire preload and renderer readiness, cancel abandoned navigation, and bind consent-aware memory sampling to the Electron lifecycle. Document the metric definitions and stabilize validation fixtures that inherited host configuration or raced signal-handler readiness. * docs(telemetry): translate stability drafts to English Rewrite the historical project-entry draft in English and neutralize the stability-metrics intro so odcrew-facing docs stay English-only. * fix(telemetry): distinguish Linux Node installs from packaged runtimes * fix(telemetry): wait for committed conversation navigation before readiness * fix(telemetry): exclude deferred headless restores from launch timing * fix(telemetry): require current Home project and agent reads to succeed * fix(telemetry): fail closed on damaged memory sampling consent * docs(telemetry): note catalog read failures cannot block Home readiness * feat(telemetry): prepare update stage and desktop restart observations * feat(telemetry): prepare responsiveness collectors pending transport integration * fix(desktop): cancel consent lookup and anchor memory sampling to ready * fix(telemetry): classify and correlate crash and exit reports * feat(telemetry): measure consented visible preview attempts * feat(telemetry): wire page switch intent and readiness timing * fix(telemetry): validate responsiveness metrics and fence revoked buffers * fix(telemetry): require successful scoped Home resources before readiness * fix(telemetry): clear chat health collection on consent withdrawal * docs(telemetry): record integrated responsiveness validation and budgets * fix(telemetry): admit update lifecycle stage metrics * test(contracts): restore merged client metric schema cases * fix(telemetry): route native stability metrics through safety capture Remove metrics-consent checks from startup, memory, exit and update-stage reporting while preserving product analytics consent and privacy projection. * docs(telemetry): align stability metrics with safety-channel policy Document consent-independent stability reporting and unchanged product/content consent, privacy projection, budgets and remaining native QA. * fix(telemetry): observe runtime daemon exits and correlate memory samples Capture parent-observed daemon death through the safety channel, preserve stronger evidence across restart, and join exits to real packaged memory sampling with opaque process IDs and nullable V8 heap limits. * fix(telemetry): send web stability metrics on the safety channel Drop the metrics-consent gate from web responsiveness, preview and chat health collectors to match the native safety-channel routing, and remove test hooks for the deleted consent API. * test(web): record an open intent before asserting a trusted first paint * test(web): align component tests with commit-driven navigation and visible-preview gating ProjectView switches conversations in the router onCommit callback, preview diagnostics require a visible frame, and catalog/page-switch reads changed their call shape; update mocks and expectations accordingly. * fix(telemetry): require successful catalogs before reporting page navigation ready A failed design-system/skill/template read cleared loading without marking the catalog ready, so page switches still reported ready. Mirror the startup gate. * fix(desktop): prune expired stage-only update check flows Every scheduled check opens a metric flow directory. Remove stage-only flows past the replay TTL during the existing startup scan; installer flows stay. * fix(telemetry): isolate reporting from navigation and catalog fallback * fix(telemetry): preserve business behavior with passive observation * fix(telemetry): classify daemon channel by service packaged flag On Linux CI the Node binary lives under /opt/, so execPath heuristics marked unpackaged runs as packaged and reported channel=stable. Without an explicit or version-inferred channel, derive it from the service's packagedRuntime flag instead. Also restore the desktop SHOW-hook boundary assertion to match activeDesktop.show(). * fix(telemetry): keep killed daemons and fresh-install launch attempts observable OPEND-4221: the sidecar supervisor exited 0 whenever its target died from a signal, so an external SIGKILL of the daemon reached packaged as code=0/signal=null and was reported as clean-exit. The supervisor now exits 0 only for stops it forwarded itself and encodes any other terminating signal as 128 + N; observeDaemonExit decodes that back to reason=killed with the signal. OPEND-4220: packaged flushed client_launch_attempt directly at daemon-ready and dropped it when installation.json did not exist yet, which is always the case on a fresh install. Launch events now stay buffered until desktop main arms the daemon-backed reporter, and the daemon retains launch metrics (bounded, in order) until the installation identity exists, replaying them before later events. Explicit opt-out still delivers immediately on the safety channel. * fix(telemetry): exclude url_load previews from the preview denominator trackVisiblePreview probes the frame with od:preview-metric-probe and, after 15s without od:preview-metric-result, finishes the attempt as error / probe_unverified. URL-loaded artifacts never receive the srcdoc observability bridge, so they cannot answer; every visible, successful URL preview was therefore counted as an error. Frames that cannot answer the probe are now excluded from the denominator, matching how the PR scopes Live Artifact HTML. * fix(telemetry): move renderer safety identity to the installation id on fresh install OPEND-4220 retest: on a fresh install the renderer safety channel bootstraps before the installation id is minted, falls back to the anonymous id, and kept it for the whole session, so renderer stability events carried a different device_id than native launch/memory events. setIdentity now also patches the safety context, and a late bootstrap keeps the installation id. --------- Co-authored-by: heggria <bshengtao@gmail.com> |
||
|
|
75693e95f4 |
[OPEND-3114] feat(od-next): open registered agents and CLI BYOK (#8702)
* feat(od-next): open registered runtimes and CLI BYOK Use the effective runtime registry for OD Next admission and remove retired capability qualification. Keep native observation and existing resume/cold recovery, with the evidence graph owned by observability contracts. Add run-scoped BYOK connection flags with caller-selected environment key input and reuse the existing provider bridge. Accept equivalent frozen Skill JSON and Base64 representations while preserving content and ownership boundaries. Validated with focused contracts, runtime, public Run and CLI regressions; eight real native agents and a real BYOK request/production pair; guard and workspace typecheck. Refs #8701. * fix(web): describe BYOK project file tools Explain that API-provider project runs use OpenCode file tools and require OpenCode to be available. Update the existing notice in all 19 locales and its component/browser regressions. Validated with red/green notice checks, real settings and preview follow-ups, guard and workspace typecheck. Refs #8701. * fix(od-next): preserve hidden prototype form feedback Keep HTML hidden semantics within the shared layout layer without overriding authored product CSS or hidden-until-found behavior. This fixes name errors remaining visible after a successful registration. Validated with a red/green Chromium regression, three resource-boundary checks, a real Claude follow-up with desktop/mobile form interaction, guard and workspace typecheck. Refs #8701. * test(od-next): make Run fixtures select their execution path Explicitly select ordinary Runs for retry persistence, plain stdout replay and the compatibility single-run telemetry case. Keep task hierarchy and terminal fallback telemetry cases active, and assert the selected path. Restore owner write permission to the leftover-agent fixture's frozen directories only during teardown so recursive deletion works on POSIX. Skip symlinks and preserve cleanup errors and every original behavior check. Reproduced all four CI failures before the fix. Focused daemon and E2E checks passed (12 tests), along with guard, workspace typecheck and focused LSP diagnostics. Refs #8701. * fix(daemon): avoid passing Pi resource directories as prompt files Pi reads existing --append-system-prompt paths as files. Stop passing skills, design systems and task input directories there; their resource paths already travel in the composed RPC prompt. Validated with 104 focused runtime/RPC tests, a real Pi 1.1.0 offline startup red/green probe, workspace typecheck and guard. * fix(web): coalesce streamed calls in generation progress Fold in-flight snapshots and terminal inputs through the existing tool-event reducer before rendering the Design Files progress feed. Keep the latest input, stable call identity and the limit of twelve distinct calls. Reproduced the duplicate React key warning before the fix. Runtime and progress-feed regressions now pass alongside existing in-flight tool-row tests. * fix(web): preserve forms and browser storage in prototype previews Permit client-side form submission in opaque interactive previews. Route Web Storage artifacts through the existing isolated powered preview, including daemon hints for large files, and retain guards when isolation is unavailable. Verified URL and srcDoc form interaction, persisted values after reload, and host isolation with focused Chromium regressions. Focused viewer/runtime and daemon tests, workspace typecheck and guard passed. * test(web): keep srcDoc fixtures on the opaque preview path Use an existing non-storage sandbox-shim signal in transport lifecycle fixtures now that Web Storage artifacts select the isolated preview origin. Preserve every reload, activation, recovery and manual-save assertion. Validated all 338 cases in the three affected FileViewer test files, workspace guard and typecheck, and focused diagnostics. * fix(byok): accept keyless IPv6 loopback providers Recognize the bracketed IPv6 hostname returned by WHATWG URL parsing in both settings validation and the OpenCode BYOK runtime. Keep remote hosts key-required. Validated with focused daemon and Web tests, settings browser checks, workspace typecheck and guard. * fix(daemon): restore Pi sessions and preserve completed RPC turns Load prior Pi history with switch_session and capture the authoritative sessionFile through nonblocking get_state. Treat a host-requested SIGTERM after a clean completed turn as shutdown, including Pi builds that report exit 143, while preserving real errors and cancellation. Validated with focused protocol and public Run lifecycle tests, a real Pi generation and native resumed edit, workspace typecheck and guard. * fix(preview): isolate project storage and preserve hidden controls Give powered previews stable per-project loopback origins and reserve those hosts for matching powered GETs before API or static exceptions. Advertise the capability so newer Web clients retain the opaque fallback with older daemons. Reuse the active powered transport in Present. Share a low-specificity hidden-control guard across URL and srcDoc previews without overriding explicit author rules or until-found; preserve doctype mode when synthesizing a missing head. Validated with actual two-project storage, Workers, reload, Present and API isolation browser witnesses, form/hidden regressions, focused Web and daemon checks, workspace typecheck and guard. * test(e2e): align public canaries with best-effort OD Next Read the remaining recipe identity marker without the retired serialized package hash. Make the fake OpenCode help probe finite. Exercise the current public question-form action, completed question turns/new answer tasks, and no-artifact normal endings while retaining cancellation, artifact and deck assertions. Validated with six fake-runtime cases and focused active/off public browser canaries with explicit rollout flags; no production completion policy or timeout was relaxed. * fix(od-next): guide focus restoration after list updates Prefer native modal lifecycle and restore focus to a current control after list rerenders, falling back to the primary action when a filtered item disappears. Real OpenCode and Pi prototypes exposed detached-trigger focus loss. Keep this as best-effort build guidance, without output contracts or automatic repair rounds. Validated the actual artifacts through normal public edit tasks and browser interaction checks; profile resource tests passed. * fix(od-next): retain focus through inline task updates Extend the existing live-control guidance to inline list actions. Real Copilot and Qoder boards also lost keyboard focus when status updates rebuilt their cards; hidden filtered items need a visible fallback. * fix: align Discovery integration tests and presentation storage * test(daemon): tolerate transient memory origin cleanup races --------- Co-authored-by: zhouzheng <zhouzheng@refly.ai> |
||
|
|
341641d71a |
[OPEND-3962] fix(motion): 修复预览声音与模板配乐约束 (#8777)
* fix(motion): avoid audio seek thrashing during preview stalls * fix(motion): keep in-progress video previews silent * fix(motion): let user briefs override template demo soundtracks * fix(motion): keep project grid covers muted All Projects grid and recent-project hover covers loaded motion sources without odMotionThumbnail, so the embedded player autoplayed the soundtrack. Covers now always request thumbnail mode: animation keeps playing, audio stays muted until the project preview is opened. --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> |
||
|
|
938a09b12c |
[OPEND-3334] feat(web): one project view from the Home send — no hand-off, one Working state (#8267)
* fix(web): keep the project split still across the Home hand-off
The optimistic creation frame left `.split` on its stylesheet default
(460px) while ProjectView resolved the saved width, or the equal split of
the container, only after it mounted — and that first write ran through
the 200ms width transition, so the chat column moved once the real view
arrived (OPEND-3207).
Move the split geometry into `project-split-layout.ts`, have both surfaces
resolve their first-paint width through `resolveProjectSplitLayout`, and
settle a surface's first measured write without the transition.
Red spec: e2e/ui/home-send-split-width.test.ts (red on main, green here).
* test(web): read the 4px handle constant from project-split-layout
The material spec pinned `SPLIT_RESIZE_HANDLE_WIDTH = 4` in ProjectView's
source; the constant now lives in project-split-layout.ts and ProjectView
imports it, so assert on the module and on the import.
* test(cms): wait for the Test modal impression before switching language
The locale case only passed while the impression frame never fired: on a
fast machine the next language switch unmounted the modal before its
requestAnimationFrame recorded the impression. CI is slow enough for the
frame to land, and the switch that follows then closes the modal for good.
Waiting for the recorded impression makes that order deterministic.
* fix(cms): keep the open Test modal across a same-deployment reload
A language switch (or lease renewal) reloads the selected Test deployment.
While the new decisions are in flight the runtime publishes a session
without decisions, and the modal treated that gap as the presentation
ending: it dropped its "open" marker, then the returning decision looked
like a new offer of an activity this account had already seen and stayed
closed.
Key the open presentation by the selected deployment and snapshot. A
reload of the same selection continues it; a redeploy, a different
account, a dismissal, or the session ending still releases it.
* test(e2e): red spec for the Home → project hand-off parity (OPEND-3334)
Two pins for the hand-off between the optimistic project frame and
ProjectView: pixel parity of the chat and workspace columns across the
hand-off (animations frozen), and a single assistant status label + class
set from the click to the first content. Expected red on main: the frame is
a hand-drawn copy of ProjectView and drifts from it (old "Preparing…" footer
vs the execution record's "Working" head, simplified composer, disabled
empty-state pills, tabs dock, switcher name).
* test(e2e): enroll the Home hand-off specs in the ui_p0 entry-settings group
home-amr-pending, home-send-single-loading and home-send-split-width were
merged (or opened) without being enrolled in uiP0Groups, so no merge lane
had ever executed them; only the prerelease full pool would have. Enroll
them with the other Home-entry files.
* test(e2e): enroll the hand-off parity spec in ui_p0 (OPEND-3334)
* feat(web): draw the Home → project hand-off frame with the real view's parts (OPEND-3334)
The optimistic creation frame was a hand-drawn copy of ProjectView and had
drifted from it, so the hand-off moved: the old assistant footer's
"Preparing…" flipped to the execution record's "Working" head, the composer
gained its accessories, the empty-state pills lit up, the tabs dock gained
the history control.
The frame now draws the same parts: the role row named through the shared
`assistantRoleNameForAgent`, the real `ExecutionShell` on an empty running
shell ("Working", its orb, its geometry), the real composer accessories
(design-system picker + agent/model menu) inert, the history control's
footprint in the dock, and live-looking, inert empty-state pills. Behind an
execution shell the old footer no longer paints its running label, so a
running turn states its status once.
App and ProjectView pass the agent name, config, agents, daemon liveness and
design systems the frame needs; the pending record carries the send's
design-system id.
* test(e2e): compare the status row's element set per hand-off phase
The record head is more than one element (its shimmer span and the summary
wrapper both carry the text), so counting distinct class sets across the
whole log flagged an identical head as a change. Compare the set the frame
drew with the set the view drew instead.
* feat(web): let the real ChatPane draw the Home hand-off's first turn (OPEND-3334)
The pending frame and ProjectView's hand-off card were hand-drawn copies of
the chat column, so every ChatPane internal they missed (the fixed composer
layer, the chat seam, the log's top inset) was a jump at the hand-off. Both
now render the real ChatPane on an optimistic first turn built from the
creation record: a user message and a running, empty assistant placeholder.
- runtime/chat/creation-handoff: pure builder for that turn; staged images
preview through object URLs until the files are in the project.
- ChatPane: `detached` mode (inert pane + composer layer) for the frame;
the composer portal target resolves at mount so the composer is portaled
before the first paint instead of one frame after it.
- ProjectView: mounts ChatPane during the hand-off and feeds it the
optimistic turn until a real message exists; the overlay card, the hidden
pane CSS and `composerLayerHidden` are gone.
- agentModelSelection: one `selectedAssistantIdentity` for the real and the
optimistic turn.
* fix(web): give the pending frame's workspace column the add-tab and project-menu controls (OPEND-3334)
The parity pin now passes on the chat column and fails on the workspace
column: the frame lacked FileWorkspace's add-tab button and the project
menu trigger, so the top bar was 4px shorter and the empty state below it
moved at the hand-off.
* test(web): decouple pending handoff assertions from CSS classes
Generated-By: looper 0.13.0 (runner=fixer, agent=codex)
* fix(web): the Home hand-off's first turn fades in once, no remount, no workspace reload (OPEND-3334)
A rAF probe of the Home send found the first turn mounted four times, each
playing the 200ms `msg-enter` fade (QA's recording shows the flashes):
the pending frame (the one intended entrance); ProjectView taking over;
the ChatPane key moving from `conversation-unavailable` to the resolved
conversation id; and the real rows replacing the optimistic ones under new
ids. The workspace went pending-empty -> "Loading..." -> empty.
- ChatPane `quietEntrance`: rows first rendered while it is on get
`msg--quiet-enter` (no fade) for their lifetime; dropping the flag never
restarts an animation. ProjectView sets it while the hand-off runs.
- ProjectView keeps the pane's key when the hand-off pane's first
conversation id resolves; a later switch still re-keys.
- A project born from the Home send (not an imported folder) shows the
empty workspace until the first listing, as the pending frame did.
* test(e2e): pin one entrance, one view pane and no workspace reload across the Home hand-off (OPEND-3334)
* fixer: address PR #8267 follow-up items
Generated-By: looper 0.13.0 (runner=fixer, agent=codex)
* fix(web): keep the file-list authority gate across the Home hand-off (OPEND-3334)
|
||
|
|
8d820a6989 |
[OPEND-3799] feat(od-next): use configurable company stock search and HTML safeguards (#8691)
* feat(od-next): 按图片类型选择搜图或生图,搜图限定 Pexels / Pixabay - 核心提示词:「真实图片优先」改为「按图片类型取图」:真实事物用真实图片, 示意/虚构/氛围图默认生图,图表、图标、大段文字用代码实现 - prototype / marketing 任务说明与编排文件同步;方案中为每张图写明取图方式 - 素材 skill:新增按类型选路与搜图规则(Pexels 优先、Pixabay 其次,禁用 Wikipedia/Wikimedia),一次批量下载、单请求超时与总时长上限、按显示尺寸下载、 记录来源与许可;用来源文字描述判断是否贴合,不再把图片读回对话 - 策略包版本 2.0.6 → 2.0.7,并同步相关资产版本与测试断言 * feat(od-next): 新增 od media stock-search,一次调用完成图库搜图与下载 - 有 PEXELS_API_KEY / PIXABAY_API_KEY 时,按槽位并发搜索 Pexels(优先)与 Pixabay(兜底),单请求超时 12 s、总预算 180 s,按显示宽度下载到 assets/stock/,并把来源、作者、许可写入 credits.json - 每个槽位取 20 张候选,按 alt 文字对检索主体重排:主体出现在描述开头加分, 未要求人物时降权以人物开头的照片(例:搜眼镜框不再返回戴眼镜的老人) - 无 key 时退出码 5,agent 回到无 key 路径;参数错误退出码 2 - 素材 skill:有 key 时改用该命令,查询词用英文、主体在前 * refactor(od-next): stock-search 重排改为通用规则,去掉人物词表 - 删除人物词表与中文专用打分分支 - 按 alt 命中的查询词数量排序,数量相同时主体出现越靠前越优先 - 查询里没有英文词时不做比对,保持图库原始顺序 * feat(od-next): 核心提示词写明搜图来源与取图命令,并厘清菜名等品类的取图边界 - 核心提示词的「按图片类型取图」新增一条:搜图只用 Pexels、其次 Pixabay, 禁用 Wikipedia / Wikimedia Commons 和随机图服务;有图库 key 时用 od media stock-search 一次取齐。agent 不加载素材 skill 也能照做 (ODEval 批次 89565639 中 4/17 次搜图跳过 skill,全部退回 Wikipedia) - 菜名、菜系、商品品类再出名也算泛指,走生图;只有具体实例(某家真实餐馆 自己的菜、某品牌的商品)才算真实指代物。核心提示词、prototype 任务说明、 素材 skill 同步 - 「示例内容默认用真实指代物」限定为决定出现哪些具名实体,不改变泛指品类的 取图方式 - 策略包 2.0.7 → 2.0.8,core 2.3.2 → 2.3.3,prototype 2.2.3 → 2.2.4, 测试断言同步 * refactor(od-next): 取图规则只留核心提示词一份,素材 skill 只保留操作步骤 - 选路规则、搜图图源、禁用来源、官网兜底只写在核心提示词;素材 skill 与 prototype 任务说明改为引用,不再各写一份 - 「不要把下载或生成的图读回对话」移入核心提示词:它是影响耗时最大的一条, 不应依赖 skill 是否加载 - 编排文件的加载条件从「只在需要素材时」改为「第一次搜图、下载、生图等 素材动作之前」 - 策略包 2.0.8 → 2.0.9,core 2.3.3 → 2.3.4,orchestration 2.3.2 → 2.3.3, prototype 2.2.4 → 2.2.5 * fix(od-next): 默认搜图,仅在用户明确请求后考虑生图 * fix(od-next): guard phone dimensions and hidden visibility * fix(od-next): guide dynamic DOM structure and data separation * fix(od-next): prefer official assets for named real subjects * fix(od-next): align combined strategy assets with main contracts * fix(media): validate stock search inputs and preserve slot identity * fix(media): retain stock-search quota and fallback diagnostics (cherry picked from commit |
||
|
|
3dfd41c6b3 |
[OPEND-2800][OPEND-4117] feat(od-next): integrate Discovery delivery and validated prototype fixes (#8804)
* fix(od-next): continue plans with keyed markers and honor explicit deliverables * fix(od-next): retire output contracts and hand off continued tasks * fix(od-next): record round settlement and recover production cold * fix(od-next): resume marker sessions with context deltas * fix(od-next): separate completion facts and honor plan-only intent * fix(od-next): preserve physical failures and legacy chat compatibility * test(web): align run recovery fixtures and await campaign mount * fix(od-next): tolerate terminal production marker formatting * fix(od-next): unblock follow-ups after a stop or unreadable history (#8423) Treat a previous task this build cannot read as absent so the follow-up starts a new task, and stop counting a Run with a pending cancel as holding its conversation. Add end-to-end coverage for the marker continuation flow and restore the remote Workspace directory gate cases. * feat(od-next): add deliverable discovery and ten-type routing * fix(od-next): refine discovery and live delivery evidence * feat(observability): expose actual task delivery receipts for evaluation * fix(observability): settle successful headless task telemetry * docs: clarify focused local validation Keep local daemon and web validation scoped to affected files and integration boundaries. Reserve full suites for PR CI unless the user explicitly requests them. * feat: add Motion Design skill and OD Next task route * fix(web): rename motion design entry and place it second * fix(web): reconcile motion design catalog consumers * feat(web): add motion design templates to Home and Community Five motion design example templates (3D procedural island, explainer, product launch, showreel, data story), chosen by community reach, under the Home motion-design chip and a new Community "Motion design" type. Each template follows the bundled example layout: an OD-generated example.html with its runtime libraries under assets/, an HTML preview baked by the existing preview pipeline, and a SKILL.md that carries example.html as the reference when the card is picked on the OD Next motion-design route. Home cards ship curated copy for every UI locale. Motion templates stay off the Video and HyperFrames chips. * fix(skills): keep motion-design procedural layers visible in video export The MP4 exporter seeks timelines with callbacks suppressed (renderSeek(t, { suppressEvents: true })), so content drawn only from a GSAP onUpdate callback is missing from exported video. Recommend a per-frame draw loop that reads the timeline clock, and state that visible content must not depend on timeline callbacks. The OD Next task asset is re-synced with scripts/sync-motion-design-skill.ts. * fix(motion): preserve reference assets and exported animation frames * feat(skills): integrate curated motion helpers with complete runtime files * feat(skills): add Huashu, Oil Motion and Motion.Lab adapters * test: classify animate design guidance in narration guard * feat(motion): refine five community templates and portable previews * fix(motion): refine launch transitions and center gallery previews * fix: allow free-form briefs on automatic motion task route * feat(motion): add template soundtracks and audio export * fix(motion): require rendered MP4 alongside editable video source * test(motion): align strategy activation expectation with package version * fix: scope motion delivery and make editable sources playable * fix(motion): bundle source dependencies and valid closing protocol * feat(motion): rebuild the five templates on the most-viewed community posts Re-select topics by single-post views in the latest awesome-opus-5.5-video snapshot and anchor each template to the top eligible post of its genre: camera-focus explainer, showreel, product launch, procedural island and a new wordless animated short that replaces the data story. All five example.html files are hand-authored canvas/Three.js films drawn by one render(t) from the GSAP master timeline, so preview, scrubbing and MP4 export produce identical frames. Each ships an original procedural soundtrack, updated SKILL.md and 19-locale copy; Home order follows reach. * fix(plugins): load bundled fonts in plugin previews Plugin previews blocked every bundled font: the preview CSP had no font-src, and relative url() references inside inline <style> blocks kept pointing at the preview URL instead of the plugin folder. Allow same-origin fonts, map inline-style url() references through the existing asset route (escapes, absolute and data: URLs stay untouched), and serve font files with font MIME types. * feat(community): make motion template Use match the Home example pick Community 「做同款」 on a motion design template now binds it as that type's own example card, carrying the type's project metadata, so the run takes the same OD Next motion-design route as a pick from Home's motion design list and delivers the MP4 together with the editable source. Other task types keep the existing plugin-pinning hand-off. Community use clicks now record use_source (card | detail_modal) and use_action (use | use_with_query); the details modal Use is tracked for the first time. The Home hand-off no longer records a second community_gallery click for uses Community already tracked. * fix(plugins): count the five motion templates in the official registry metadata * fix(media): keep HyperFrames audio filters compatible with FFmpeg 4.1 CI's bundled Linux FFmpeg 4.1 rejects adelay all=1 and amix normalize=0. Make each track stereo and delay both channels explicitly, skip amix for a single track, and undo amix averaging with a gain for several tracks. * fix(motion): keep template previews bakeable on slow CI renderers CI's software-GL baker skipped the 3D island (preview readiness timeout): under slow frames GSAP lag smoothing slowed the timeline, so the 8 s poster frame never arrived within the baker's 15 s wait. Play every template on the wall clock and move the island poster to 6 s. With simulated 300 ms frames all five now report ready within 10 s. * feat(motion): add the kinetic typography template Template selection now follows the motion design spec's four content types (brand and product films, kinetic typography, explainer animation, procedural art). Kinetic typography takes the fifth slot, replacing the animated short. The new template is a 15-second, 120 BPM type film with an original soundtrack and 19-locale copy. * fix(motion): let the project motion player fit template stages Inside a project the motion source player wraps the composition, fits it and adds its own controls. The templates also scaled their stage, so editable sources from Remix and Community use rendered at a fraction of the preview. Templates now keep their own fit and controls only outside that player. * feat(motion): restage the 3D island as a midday lagoon The island moves from golden hour to a bright tropical midday closer to its anchor post: a shader lagoon coloured by a baked depth map with ripples, shallow-water shimmer and shore foam, a wide white-sand beach, palms and instanced bushes, a village with a pier, soft sun shadows and a lower camera path. The CPU wave mesh is gone, so frames cost no more than before. Copy is updated in all 19 locales. * fix(motion): keep each soundtrack inside the composition root A Community use regenerated the island source tail and dropped the root <audio> element, so its MP4 had no sound. Templates now place the element inside the composition root next to the stage. * docs(motion): state the template selection rule as complete works * fix(motion): enforce video clocks and bound content verification * feat(motion): replace the motion templates with remakes of viral posts The five Motion design templates are now frame-faithful remakes of viral motion posts, so each preview shows the look users came for: the 15-second motion designer showreel, a high-energy motion reel (copy set in English), a one-shape UI motion piece, a pixel-art wizard and a 20-second electric-blue tech reel. Each ships open-licensed fonts, an original soundtrack, the creator's published prompt where one exists, and credits its source post. The explainer, product launch, 3D island and kinetic type templates are removed. Tests follow the new set; a backward-seek check now covers hidden WebGL layers. * feat(motion): feature The Future Is Already Here first and start preview sound with the film * chore: ensure PR CI * fix(motion): match The Future Is Already Here controls and reorder the motion templates * fix(motion): make the one-shape UI preview controls legible * fix(motion): hide The Future Is Already Here controls inside the project player * fix(od-next): maintain discovery prompt assets in English * fix(motion): plan suitable sound and synchronize source audio playback * feat(motion): keep remake soundtracks by default and fade exported audio where it stops * test(motion): cover audible source controls in Chromium * fix(test): use explicit ESM imports in audio player regression * fix(od-next): preserve planning intent and make agent CLI probes inert * fix(motion): align preview track discovery with video export * feat(motion): autoplay looping HTML without a player bar * fix(test): consume the built motion playback contract * docs(motion): state explicit soundtrack rules for make-same and the music line in replies * chore(motion): version the audible looping delivery profile * docs(motion): let template sound rules lead the general policy and freeze the audio contract * fix(motion): scope autoplay to declared video motion sources * fix(motion): reject audible MP4s under an explicit silent contract * docs(motion): drop the routine music note from make-same replies * test(motion): align delivery regressions with looping source playback * fix(od-next): route refreshable sources and preserve live templates * fix(od-next): distinguish source refresh from preview reload * fix(motion): isolate preview and audio export from legacy HyperFrames * fix(od-next): retain live list containers for empty source data * test(motion): retain the existing desktop legacy audio limitation * fix(motion): suspend audio when previews leave the active workspace * feat(motion): replace two motion templates with a product promo and a visual lesson; sign the showreel as Open Design * fix(discovery): distinguish reference recreation from new prototypes * fix(discovery): require skill reads before copying reference artifacts * feat(motion): give the showreel the Open Design palette and a full-size stacked wordmark * feat(motion): give the product promo a livelier mascot girl * refactor(motion): rebuild The Future Is Already Here as readable, editable scene code * fix(motion): pick gallery cover frames that survive the baker's capture delay * feat(motion): sharpen the showreel opener, depth, truchet and end card * fix(discovery): avoid implementation-only skills and false video delivery * feat(motion): make the product promo mascot talk, add a speaking score and the flow pop * fix(discovery): distinguish video rendering from editable animation delivery * feat(motion): give the visual lesson the original's paper-collage texture * fix(motion): bundle gallery covers for fresh branch checkouts * feat(motion): feature and refine Bring Ideas to Life Adapt the paper story around OpenDesign capabilities, with short rounded hands, clearer timing, continuous paper actions, and a larger audience invitation. Feature it first in Motion and synchronize localized names, metadata, cover, guidance, and regressions. Keep product promo audio music-only with sparse percussion. * fix(motion): stabilize artifact cards and capture declared covers * fix(discovery): retain WebGL intent and isolate reference-only skills * fix(discovery): complete authorized plans and verify audible delivery * fix(discovery): gate skill reads on current production authorization * fix(web): share creation type order with community * fix(discovery): stop required-model attempts on authentication failure * fix(discovery): retain required model provider and billing channel * fix(discovery): stop opaque video failures without diagnostic generations * fix(discovery): distinguish local state and tighten output adherence * fix(preview): preserve local forms and hash routes in strict sandbox * fix(prototype): keep validation feedback selectors consistent * fix(prototype): preserve native hidden feedback states * fix(discovery): align local routes and live filter states * fix(prototype): preserve dynamic controls and dialog visibility * fix(prototype): keep review and confirmation state coherent * fix(web): stabilize preview activation and retain scoped local progress * test(web): verify persistent previews through activation messages * feat(web): refine task type selection and keyboard navigation * fix(discovery): distinguish execution from requested delivery and expose recovery * fix(web): use final run delivery state in folded task history * revert(web): restore todo-based continuation controls * revert(od-next): remove experimental continuation recovery changes * fix(telemetry): bind Task traces to authenticated Vela identity * test(telemetry): establish accepted Task roots in bridge fixtures * test(discovery): align required CI fixtures with integrated behavior * fix(web): preserve succeeded messages when run probe is unavailable * test(discovery): verify selection clearing and production turn recovery --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> Co-authored-by: Cheems <hihicheems@gmail.com> Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> Co-authored-by: zhouzheng <zhouzheng@refly.ai> Co-authored-by: William <zhouhaoran@refly.ai> Co-authored-by: open-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com> Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> |
||
|
|
dd43a3cef1 |
[OPEND-4136] feat(web): use Feishu community links for Chinese locales (#8884)
* feat(web): route Chinese community entries to Feishu * fix(web): match Feishu shortcut icon to surrounding monochrome icons --------- Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com> |
||
|
|
2b41bc5c3d |
feat(share): publish files as public links with stop/resume and synced comments (#8408)
* wip: wire deleted-file stop retry and cover owner-scoped HTTP behavior
* wip: persist published comment backfill state
* wip: connect backfill delivery receipts and preserve completed batches
* wip: expose exact-file backfill state and retry through HTTP and CLI
* wip: wire scoped comment sync pause into preview panel
* test: cover backfill terminal rejection and stale publication receipts
* wip: separate published content from unavailable viewer origin
* feat: distinguish publication success from unavailable viewer links
* fix(web): gate export share guide on authoritative project history
* test: restore publish backfill HTTP proof with current publisher pipeline
* fix(web): show authoritative stopped-share guidance
* fix(web): restore publish help before creating a share
* fix(web): show unread comments on collapsed rail
* fix: share annotated selectors across URL and srcdoc bridges
* test: verify saved comment selector reanchors in Chromium
* fix(web): warn about active shares before project deletion
* fix: authorize and bootstrap public share project bindings
* wip: prepare dispatched live share-chain probe (not run)
* fix(web): deliver per-file share residuals after deletion
* fix(web): prevent stopping shares in read-only viewers
* fix(e2e): retain complete share probe HTTP evidence
* fix(web): name shared projects in delete confirmation titles
* docs(e2e): clarify live share acceptance and human login boundaries
* fix(e2e): preserve share query context in diagnostic evidence
* fix(web): restore share-panel copy and comment-pin visuals per board8
chain1-publish G4: delete the stale "仅支持分享单个文件,内嵌图片等本地
资源暂不支持" sentence from fileViewer.publishSingleFileDescription (19
locales) — it contradicts the current multi-resource share and G4 required
its removal.
chain1-publish S1: add the missing "链接访问" title row + its static
description ("接收者可查看预览和已有评论,不包含对话和代码。") to
ShareTab's public-share section, as new fileViewer.linkAccessTitle /
linkAccessDescription keys (19 locales). No switch added — that control
overlaps the existing "停止分享" button and is left as a 需确认 item.
chain4-owner 4.2b: canvas comment pins go from 20x20/weight 600/2px white
ring/fixed #282828 to 28x28/weight 500/no ring, and now derive their
background/foreground from the same per-author color used by that author's
sidebar avatar (commentAuthorAvatarColor), both pure local CSS/TSX with no
packages/ or protocol change.
chain4-owner O7: formatCommentTime now buckets by calendar day (adds
"昨天", drops the "N 天前/N 周前" buckets, switches the hour bucket to
same-day instead of <24h elapsed, and formats older dates via
toLocaleDateString with year only when it differs from the current year),
and comment-side-time gets a title attribute with the full absolute
timestamp on hover.
Verification: pnpm --filter @open-design/web typecheck passes. Not run
through a real renderer — no pixel-level claim.
* fix(web): share entry icon, delete-confirm title, OP2 read-only hint
chain1-publish E0: swap the share toolbar entry's icon from RemixIcon
share-forward-line to the board's own upload-arrow glyph (13px path
"M12 4v11M7 9l5-5 5 5M5 14v5h14v-5") — same size/shape as the board's
.shareBtn icon, glyph only, no behavior change.
chain6-dialogs D-04: drop the extra "确定" from designs.deleteConfirm's
zh-CN/zh-TW title ("确定删除「{name}」?" → "删除「{name}」?") to match
the board and every other locale, which already read "Delete "{name}"?"
without an "are you sure" prefix.
chain4-owner OP2: add the missing "分享页评论不可编辑" explanation line
under a comment's textarea specifically when the viewer can't edit it
AND the comment is a share-page (external) one — team-member-reads-
external-comment (OP3) is unaffected, matching the board's OP2-only
scope. New comment.sharePageCommentReadOnly key, 19 locales.
Verification: pnpm --filter @open-design/web typecheck passes. Not run
through a real renderer — no pixel-level claim.
* test(web): update FileViewer specs for the G4/O7/4.2b restorations
Three specs encoded the exact pre-fix behavior the previous two commits
intentionally changed, so they went red:
- "reads the publish help without publishing" located the help bubble by
the now-deleted stale sentence ("Only a single file can be shared for
now..."); relocates it by the sentence that remains.
- "keeps relative comment-time boundaries stable across clock boundaries"
asserted the old <24h-elapsed hour bucket; the O7 rewrite buckets by
calendar day, so the fixture's 23h-ago comment (which crosses this
fake clock's midnight) now correctly reads "昨天" instead of "N 小时前".
- "keeps D1-D4 marker and deck-page presentation on the rendered
selectors" asserted the old 20px/weight 600/white-ring pin CSS; updated
to the board's 28px/weight 500/no-ring geometry.
Verification: pnpm --filter @open-design/web typecheck passes;
tests/components/FileViewer.test.tsx: 334/334 passed (via
node_modules/vitest/vitest.mjs --pool=forks, run directly against the
node 24.18.0 binary — this shell's `npx`/`node` wrap nvm in a way that
blows bash's FUNCNEST under vitest's fork pool).
* feat(web): draw the export guide's countdown, title and description
The 10s countdown already existed as state - remainingMs, checkedAt and
paused were computed and consumed - but nothing rendered it, so the guide
showed no sign that it was about to dismiss itself.
The bar is one CSS width transition per run rather than a per-frame timer:
the remaining time is already known when the run starts, so animating to
zero over exactly that duration needs no re-render, and a pause just freezes
the transition.
Board copy added across all 19 locales.
* test(web): update AfterExportShareGuide specs for the P1 card restoration
Three assertions encoded the pre-restoration visual/DOM shape that
|
||
|
|
78d5637cb4 |
fix(daemon,web): forward pure plugin applies and honor persisted capability grants (#7597)
* fix(daemon,web): forward pure plugin applies and honor persisted capability grants Pure apply returns snapshotId "" for existing projects, so the web run path now forwards pluginId and pluginInputs when no snapshot id exists. The daemon trust vocabulary now accepts pipeline:* and applyPlugin honors stored capabilitiesGranted with prompt:inject floor. Fixes #6186 * test(web): cover pure-apply plugin identity fallback in run requests |
||
|
|
94f7a48633 |
fix(web): kick the compositor on a chat scroll freeze, then take the wheel and keyboard over by default (#8251)
* fix(web): kick the compositor on a chat scroll freeze, then take the wheel and keyboard over by default When the freeze probe calls the chat log frozen, apply one frame of `will-change: transform` so Blink rebuilds the scroller's compositing state from the current layout, let the next downward wheel through natively and measure it against what it asked for (the detector's own normalisation and 8px yardstick), and only if it stuck, fell short or snapped back answer the wheel and the scrolling keys from JavaScript. The takeover is on by default now; `open-design:chat-scroll-takeover = '0'` is the escape hatch. `ChatPane` releases the takeover — and asks the probe to start a fresh surface — when `activeConversationId` changes, because the log node is reused across conversations. A healed surface is released the same way so a recurrence can be judged again. One new event, `client_chat_scroll_heal`, carries the outcome, the kick geometry and the notch arithmetic, joined to the freeze by `probe_id`; `client_chat_scroll_frozen` is untouched. * fix(web): keep Home/End finite and let the takeover go when the chat log is removed Home and End were ±Infinity folded into the same pixel accumulator as the arrows, so Home-then-End in one frame (or key repeat) summed to NaN and the browser read the NaN scrollTop as 0 — End could not reach the bottom. Edge jumps now live in their own `pendingEdge` slot: the last edge pressed wins, pressing one discards the steps queued before it, and steps pressed after it apply on top; applyPending refuses to write a non-finite value. A tab or route unmount removes the chat log without a conversation change, and the probe only notices on a scroll the missing node never sends, so the takeover stayed engaged with its document-level keydown listener cancelling arrows and paging with nothing to scroll. Every input path (wheel, keydown, frame) now checks `isConnected` and disengages, and ChatPane's release effect also releases in its cleanup. |
||
|
|
41afc74c88 |
chore(release): bump main to 0.25.1 ahead of stable 0.25.0 (#8787)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com> |
||
|
|
6410d3622a |
fix(od-next): restore production follow-up host instructions (#8770)
* fix(od-next): restore production follow-up host instructions Compose production host instructions with the same fresh key persisted on the run, including native resume, cold-start input, and UI locale. Keep the instructions independent of retired model state declarations and exclude the nonce-bearing text from internal continuation retry identity. Verify optional suggestions, message persistence, and repeated preparation through the real server and run registry. Validated with main red specs, focused regression tests, guard, and the workspace typecheck. * test(od-next): preserve production test entry points Keep the existing production test header and fixture setup while sharing host-protocol assertions with a separate suggestions case. This avoids adding a conflict with the pending agent rollout tests. Validated with the focused real-server regressions, guard, and the full workspace typecheck. |
||
|
|
ae321ab2e6 |
fix(cms): fence offline replay across revocation and process death (#8676)
* fix(cms): fence offline replay across revocation and process death * feat(cms): define paginated Test catalog and immutable runtime context * fix(cms): synchronize Test and Production host presentation lifecycles * test(cms): retain cross-host lifecycle and browser acceptance regressions * fix(cms): retain request tickets through body classification --------- Co-authored-by: heggria <bshengtao@gmail.com> Co-authored-by: ivy-tinggg <986810707@qq.com> |
||
|
|
535fa8eab7 |
fix(amr): restore Home funding checks and decouple run feedback (#8688)
* fix(web): surface live AMR funding failures without delaying sends * fix(amr): check funding independently of agent startup * chore(amr): pin Vela funding feedback test package * fix(amr): route early funding hints through isolated CLI command * fix(amr): restore scoped Home funding cache gate * fix(amr): restore Home wallet and plan memory gate * test(amr): align send audits with independent funding checks * chore(amr): bundle Vela CLI 0.1.5 for funding checks --------- Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
14fee234e6 |
feat: add motion design workflow and curated motion skills (#8619)
* feat: add Motion Design skill and OD Next task route
* fix(web): rename motion design entry and place it second
* fix(web): reconcile motion design catalog consumers
* feat(web): add motion design templates to Home and Community
Five motion design example templates (3D procedural island, explainer,
product launch, showreel, data story), chosen by community reach, under the
Home motion-design chip and a new Community "Motion design" type.
Each template follows the bundled example layout: an OD-generated
example.html with its runtime libraries under assets/, an HTML preview baked
by the existing preview pipeline, and a SKILL.md that carries example.html
as the reference when the card is picked on the OD Next motion-design route.
Home cards ship curated copy for every UI locale. Motion templates stay off
the Video and HyperFrames chips.
* fix(skills): keep motion-design procedural layers visible in video export
The MP4 exporter seeks timelines with callbacks suppressed
(renderSeek(t, { suppressEvents: true })), so content drawn only from a GSAP
onUpdate callback is missing from exported video. Recommend a per-frame draw
loop that reads the timeline clock, and state that visible content must not
depend on timeline callbacks. The OD Next task asset is re-synced with
scripts/sync-motion-design-skill.ts.
* fix(motion): preserve reference assets and exported animation frames
* feat(skills): integrate curated motion helpers with complete runtime files
* feat(skills): add Huashu, Oil Motion and Motion.Lab adapters
* test: classify animate design guidance in narration guard
* feat(motion): refine five community templates and portable previews
* fix(motion): refine launch transitions and center gallery previews
* fix: allow free-form briefs on automatic motion task route
* feat(motion): add template soundtracks and audio export
* fix(motion): require rendered MP4 alongside editable video source
* test(motion): align strategy activation expectation with package version
* fix: scope motion delivery and make editable sources playable
* fix(motion): bundle source dependencies and valid closing protocol
* feat(motion): rebuild the five templates on the most-viewed community posts
Re-select topics by single-post views in the latest awesome-opus-5.5-video
snapshot and anchor each template to the top eligible post of its genre:
camera-focus explainer, showreel, product launch, procedural island and a
new wordless animated short that replaces the data story.
All five example.html files are hand-authored canvas/Three.js films drawn by
one render(t) from the GSAP master timeline, so preview, scrubbing and MP4
export produce identical frames. Each ships an original procedural
soundtrack, updated SKILL.md and 19-locale copy; Home order follows reach.
* fix(plugins): load bundled fonts in plugin previews
Plugin previews blocked every bundled font: the preview CSP had no font-src,
and relative url() references inside inline <style> blocks kept pointing at
the preview URL instead of the plugin folder. Allow same-origin fonts, map
inline-style url() references through the existing asset route (escapes,
absolute and data: URLs stay untouched), and serve font files with font
MIME types.
* feat(community): make motion template Use match the Home example pick
Community 「做同款」 on a motion design template now binds it as that type's
own example card, carrying the type's project metadata, so the run takes the
same OD Next motion-design route as a pick from Home's motion design list and
delivers the MP4 together with the editable source. Other task types keep
the existing plugin-pinning hand-off.
Community use clicks now record use_source (card | detail_modal) and
use_action (use | use_with_query); the details modal Use is tracked for the
first time. The Home hand-off no longer records a second community_gallery
click for uses Community already tracked.
* fix(plugins): count the five motion templates in the official registry metadata
* fix(media): keep HyperFrames audio filters compatible with FFmpeg 4.1
CI's bundled Linux FFmpeg 4.1 rejects adelay all=1 and amix normalize=0.
Make each track stereo and delay both channels explicitly, skip amix for a
single track, and undo amix averaging with a gain for several tracks.
* fix(motion): keep template previews bakeable on slow CI renderers
CI's software-GL baker skipped the 3D island (preview readiness timeout):
under slow frames GSAP lag smoothing slowed the timeline, so the 8 s
poster frame never arrived within the baker's 15 s wait. Play every
template on the wall clock and move the island poster to 6 s. With
simulated 300 ms frames all five now report ready within 10 s.
* feat(motion): add the kinetic typography template
Template selection now follows the motion design spec's four content types
(brand and product films, kinetic typography, explainer animation, procedural
art). Kinetic typography takes the fifth slot, replacing the animated short.
The new template is a 15-second, 120 BPM type film with an original
soundtrack and 19-locale copy.
* fix(motion): let the project motion player fit template stages
Inside a project the motion source player wraps the composition, fits it and
adds its own controls. The templates also scaled their stage, so editable
sources from Remix and Community use rendered at a fraction of the preview.
Templates now keep their own fit and controls only outside that player.
* feat(motion): restage the 3D island as a midday lagoon
The island moves from golden hour to a bright tropical midday closer to its
anchor post: a shader lagoon coloured by a baked depth map with ripples,
shallow-water shimmer and shore foam, a wide white-sand beach, palms and
instanced bushes, a village with a pier, soft sun shadows and a lower
camera path. The CPU wave mesh is gone, so frames cost no more than before.
Copy is updated in all 19 locales.
* fix(motion): keep each soundtrack inside the composition root
A Community use regenerated the island source tail and dropped the root
<audio> element, so its MP4 had no sound. Templates now place the element
inside the composition root next to the stage.
* docs(motion): state the template selection rule as complete works
* fix(motion): enforce video clocks and bound content verification
* feat(motion): replace the motion templates with remakes of viral posts
The five Motion design templates are now frame-faithful remakes of viral
motion posts, so each preview shows the look users came for: the 15-second
motion designer showreel, a high-energy motion reel (copy set in English), a
one-shape UI motion piece, a pixel-art wizard and a 20-second electric-blue
tech reel. Each ships open-licensed fonts, an original soundtrack, the
creator's published prompt where one exists, and credits its source post.
The explainer, product launch, 3D island and kinetic type templates are
removed. Tests follow the new set; a backward-seek check now covers hidden
WebGL layers.
* feat(motion): feature The Future Is Already Here first and start preview sound with the film
* chore: ensure PR CI
* fix(motion): match The Future Is Already Here controls and reorder the motion templates
* fix(motion): make the one-shape UI preview controls legible
* fix(motion): hide The Future Is Already Here controls inside the project player
* fix(motion): plan suitable sound and synchronize source audio playback
* feat(motion): keep remake soundtracks by default and fade exported audio where it stops
* test(motion): cover audible source controls in Chromium
* fix(test): use explicit ESM imports in audio player regression
* fix(motion): align preview track discovery with video export
* feat(motion): autoplay looping HTML without a player bar
* fix(test): consume the built motion playback contract
* docs(motion): state explicit soundtrack rules for make-same and the music line in replies
* chore(motion): version the audible looping delivery profile
* docs(motion): let template sound rules lead the general policy and freeze the audio contract
* fix(motion): scope autoplay to declared video motion sources
* fix(motion): reject audible MP4s under an explicit silent contract
* docs(motion): drop the routine music note from make-same replies
* test(motion): align delivery regressions with looping source playback
* fix(motion): isolate preview and audio export from legacy HyperFrames
* test(motion): retain the existing desktop legacy audio limitation
* fix(motion): suspend audio when previews leave the active workspace
* feat(motion): replace two motion templates with a product promo and a visual lesson; sign the showreel as Open Design
* feat(motion): give the showreel the Open Design palette and a full-size stacked wordmark
* feat(motion): give the product promo a livelier mascot girl
* refactor(motion): rebuild The Future Is Already Here as readable, editable scene code
* fix(motion): pick gallery cover frames that survive the baker's capture delay
* feat(motion): sharpen the showreel opener, depth, truchet and end card
* feat(motion): make the product promo mascot talk, add a speaking score and the flow pop
* feat(motion): give the visual lesson the original's paper-collage texture
* fix(motion): bundle gallery covers for fresh branch checkouts
* feat(motion): feature and refine Bring Ideas to Life
Adapt the paper story around OpenDesign capabilities, with short rounded hands, clearer timing, continuous paper actions, and a larger audience invitation. Feature it first in Motion and synchronize localized names, metadata, cover, guidance, and regressions.
Keep product promo audio music-only with sparse percussion.
* fix(motion): stabilize artifact cards and capture declared covers
* fix(web): share creation type order with community
* test(motion): exercise audio controls through project UI lifecycle
* fix(motion): scope live cover behavior to actual video sources
* test(motion): use real HEAD responses in cover regressions
---------
Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com>
Co-authored-by: William <zhouhaoran@refly.ai>
Co-authored-by: open-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com>
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: Amy <1184569493@qq.com>
|
||
|
|
014bffbf77 |
fix(collab): synchronize owner devices with overwrite consent (#8683)
* fix(collab): synchronize owner devices with overwrite consent * docs(collab): add owner device synchronization QA handoff * chore(collab): move delivery reports and screenshots to Plane * fix(collab): preserve owner pull fallback and ghost heal Generated-By: looper 0.16.1 (runner=fixer, agent=codex) * fix(collab): probe null owner heads for retractions Generated-By: looper 0.16.1 (runner=fixer, agent=codex) * fix(collab): preserve manual checks across coalescing Generated-By: looper 0.16.1 (runner=fixer, agent=codex) * feat(collab): hide update check behind a dismissed-version badge Show the header entry only when the owner skipped a cloud version, as a "Cloud update available" badge that re-opens the overwrite dialog. Rewrite the dialog description in plain product language. * fix(collab): clarify device sync copy and place entry before present * fix(collab): silence update reminders without automatic overwrite --------- Co-authored-by: Looper <looper@noreply.github.com> Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com> |
||
|
|
424f5c68cc |
fix(od-next): remove output-contract gates and unblock continuation (#8356)
* fix(od-next): continue plans with keyed markers and honor explicit deliverables * fix(od-next): retire output contracts and hand off continued tasks * fix(od-next): record round settlement and recover production cold * fix(od-next): resume marker sessions with context deltas * fix(od-next): separate completion facts and honor plan-only intent * fix(od-next): preserve physical failures and legacy chat compatibility * test(web): align run recovery fixtures and await campaign mount * fix(od-next): tolerate terminal production marker formatting * fix(od-next): unblock follow-ups after a stop or unreadable history (#8423) Treat a previous task this build cannot read as absent so the follow-up starts a new task, and stop counting a Run with a pending cancel as holding its conversation. Add end-to-end coverage for the marker continuation flow and restore the remote Workspace directory gate cases. * docs: clarify focused local validation Keep local daemon and web validation scoped to affected files and integration boundaries. Reserve full suites for PR CI unless the user explicitly requests them. * fix(web): scope folded task completion to its final run Keep the final physical Run key and conclusion together when OD Next history is folded or accumulated live. Reuse the existing predecessor offsets while retaining the full display history and remaining Todo snapshot. Add regression coverage for completed and unfinished production, live continuation, multiple Runs, legacy history, wrong and quoted markers, question handoff, and failed or canceled Runs. Validated the parent regression before the fix, 165 focused Web tests, workspace typecheck, and guard. * fix(od-next): compact historical context without restricting clarification Reuse confirmed artifact summaries for old assistant history, preserve cross-agent plans and original requests, and represent earlier questionnaires as neutral historical information without inferring answers. Keep user replies, skips, changed answers, unpersisted bodies, examples, and unfamiliar content intact. Do not add question repetition checks, output gates, repair rounds, or limits on clarification. Validated red regressions, shared context and Web provider tests, focused handoff/resume/cold-production coverage, real server and fake CLI input, workspace typecheck, and guard. The repeated-question server fixture remains successful. --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> Co-authored-by: Cheems <hihicheems@gmail.com> Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> |
||
|
|
9041ca60bc | chore(release): bump main to 0.25.0 ahead of stable 0.24.1 (#8700) | ||
|
|
0b0dad9191 |
fix(sidecar): bound macOS private IPC socket paths (#8002)
Long macOS temporary-directory paths can push private sidecar sockets beyond the 103-byte pathname limit and crash startup. Fall back to /tmp only for oversized macOS paths, preserving the full digest and UID isolation. Add byte-boundary, identity, platform-compatibility, and native macOS IPC regression coverage. |
||
|
|
0bd8251c9b |
fix(daemon): name AMR runs failing on Windows older than 1809 as os_unsupported (#8629)
The bundled OpenCode is a Bun binary and Bun requires Windows 10 1809 (build 17763). On older builds it exits with STATUS_ENTRYPOINT_NOT_FOUND (0xc0000139) before readiness, which was classified as a retryable fatal_rpc_error and shown as a generic failure. Classify it as a non-retryable os_unsupported when the host build is below 17763 and show a card telling the user to update Windows. |
||
|
|
8145e9120b |
fix(daemon): keep ACP turns succeeded when the daemon force-closes the agent (#8514)
* fix(daemon): keep ACP turns succeeded when the daemon force-closes the agent After a clean ACP end_turn the daemon waits 500ms and then terminates the agent. On Windows a child killed by pid reports exit code 1 with no signal, so the close classifier (which only forgave SIGTERM or code 130) marked the completed turn failed with exit_nonzero and suppressed retry. In the last 7 days this hit 143 AMR runs from 65 Windows users on 0.24.1; every sampled bundle (76/76) had already returned stopReason end_turn. Treat any exit as succeeded once the daemon itself began shutting down a cleanly completed ACP attempt. * fix(daemon): only forgive a completed ACP exit the daemon itself caused Review follow-up. terminateProcessTree waits gracefulWaitMs before it signals anything, so a vela that exits 1 on its own during that wait used to count as a daemon shutdown and was reclassified as succeeded. terminateProcessTree now takes onChildSignal, called just before the first signal to a direct child that is still alive (POSIX group, pid-less and Windows pid-tree paths). server.ts sets acpCompletionShutdownRequested from it for the completed-turn termination only, so a non-zero exit during the grace wait stays failed. * fix(daemon): report a daemon close only after the signal was delivered Review follow-up. `onChildSignal` ran before the stop helpers dispatched anything. If that dispatch then failed (a non-ESRCH error from the Windows stop path) or found the child already gone (ESRCH), the flag was still set, and a child that later exited 1 on its own was reported as succeeded. - platform: `stopProcesses` takes `onSignal`, called for each PID a signal was actually delivered to. ESRCH and thrown dispatches are not reported. - runs: the completion-shutdown callback fires once, after the first delivered signal to the still-owned direct child, on the Windows pid-tree, POSIX group and pid-less paths alike. |
||
|
|
e0946f9423 |
fix(od-next): recover planning replies that repeat the runtime state block (#8520)
* fix(od-next): recover planning replies that repeat the runtime state block
OD Next treats a reply with more than one open-design-runtime-state block
as non-repairable, so the task ends before anything is produced. On AMR
0.24.1 that surfaced as OD_NEXT_CONTINUATION_FAILED ("The parsed response
was not eligible for contract repair.") on 16 design-mode prototype runs
from 15 users in the last 7 days, 15 of them with no artifact. Most of the
daemon logs we could read show `Expected at most one
open-design-runtime-state block, received 2.`
- Collapse runtime state blocks that carry the same JSON value (key order
aside) into one declaration and record a normalization.
- Let disagreeing runtime state blocks take the existing one-time
serialization repair. Nothing has been written at this point. When every
block agrees on executionIntent, that intent counts as the reply's
explicit intent for the repair gate; otherwise the gate stays closed.
- Plan Contract duplicates still fail closed.
* fix(od-next): send agreed-produce duplicate runtime states straight to repair
Review follow-up. A duplicate Runtime State reply has no strict runtimeState,
so requiresStrategyIntentResolution treated a new task's intent as missing
and spent an intent-resolution turn before the serialization repair: two
provider turns where one was promised.
When every duplicate block declares `produce`, the intent is already
explicit, so the automatic continuation now claims the contract repair Run
directly. An agreed `plan_only` still takes the intent-resolution turn,
because only that turn can complete a planning-only task; the repair gate
would block it.
* test(od-next): cover duplicate runtime states that go through intent resolution
On AMR 0.24.1, 19 of 20 duplicate runtime-state failures asked the intent
supplement first. The model answered produce with one clean block, then the
original reply, still carrying the duplicate, was refused with "The parsed
response was not eligible for contract repair." This path already repairs
on this branch; the test fails with that exact error on the parent commit.
* fix(od-next): let the supplement complete a duplicate-state planning-only task
A planning reply whose two Runtime State blocks both declare plan_only goes
through intent resolution, but the source keeps no state of its own, so the
validated plan_only reply could not be copied anywhere and the task was blocked
on the duplicate. When the duplicate is the source's only defect, the
supplement's schema-validated plan_only state now completes the task and the
source Plan Contract is kept. Any other source defect stays fail-closed.
|
||
|
|
17e2559597 |
fix(od-next): isolate SVG icon geometry on latest main (#8298)
* fix(od-next): bound prototype SVG icon geometry * fix(od-next): classify prototype SVGs by role, not canvas size The icon contract listed example glyphs on each side but gave no rule for anything outside those lists, so a logo inside a button or a deliberately large UI glyph had to be guessed. State the basis: classify by the role the SVG plays, not by canvas size or the presence of a viewBox. --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> |
||
|
|
7eb1e217be |
fix(web): align client error copy with the product error-copy doc (OPEND-2849) (#8620)
* fix(web): align export and update-check error copy with product (OPEND-2849)
- S26a: a generic export failure now shows the product title + body
("导出失败" / "本次导出未完成,请重新尝试。") through the same Toast
message + details shape as S26c. fileViewer.exportFailed is split into
fileViewer.exportFailedTitle / fileViewer.exportFailedDescription in all
19 locales; the old key is removed.
- S31b: drop the sentence-final period from the title line of
updater.dialogCheckFailed (zh-CN and 17 other locales; th had none).
* fix(web): never surface raw exporter errors on export failure (OPEND-2849)
Product copy S26: an export failure shows the S26a title + body by default;
only an already-localized, user-facing reason may replace it. Raw exporter
strings (HTTP status lines, renderer/daemon errors, internal exceptions)
no longer reach the toast.
- New export-failure-toast module: LocalizedExportError marks errors whose
message comes from an i18n key; exportFailureToast passes only those
through and falls back to S26a for everything else.
- Localized reasons now thrown as LocalizedExportError: PPTX daemon
unreachable / PPTX not available, version image capture failed.
- Image export catch-all uses the same rule instead of echoing err.message.
- The original error object is unchanged, so console.warn and
exportErrorCode analytics still see the raw message.
* fix(web): align disk-write failure and browser-open hint copy with product (OPEND-2849)
- chat.runError.localStorageFailureMessage now matches the product supplement
row 13 verbatim in zh-CN ("…请确认有足够的剩余空间后,再重新尝试。"); the
old "permission to save to the current folder" clause is dropped and all
19 locales are retranslated to the new meaning.
- settings.amrActivationBrowserFailed (S32 browser not opened): half-width
comma replaced by a full-width one in zh-CN and zh-TW; wording unchanged.
* fix(web): align S03/S04/S06/S09a/S15/S24/S28a/S31c/S32a error copy with product (OPEND-2849)
zh-CN copied verbatim from the product error-copy doc; all 19 locales translated.
- S04 pre-send signed out (balance gate dialog): "Open Design 尚未登录" + body.
- S06 pre-send insufficient allowance: owner dialog "可用额度不足" + body (no
balance figure); member dialog "团队额度不足" + body. Benefits list and
buttons unchanged.
- S15 mid-run allowance exhausted: the turn-anchored upgrade card's $0 state
shows title + body, personal vs. member chosen by the billing audience
ProjectView already resolves. Low-balance tier, amount and Upgrade button
unchanged; the pre-send tail card keeps "现在无法开始新任务".
- S24a unfinished todos: "任务尚未完成" + "本次任务仍有 {n} 项内容未完成。" with the
real unfinished count; S24c user stop: "任务已停止" + body. Buttons kept.
- S03 / S28a Home send failures: the optimistic create's rollback toast (the
surface users actually see) and HomeView's inline block now map
AMR_AUTH_REQUIRED to S03 and transport failures to S28a, as title + body.
- S31c update download failed (updater error code download-failed): update
dialog shows "更新下载未完成" + body; Settings status shows the title.
- S32a browser not opened during sign-in: title + body via one shared
component for the three sign-in surfaces; the sign-in page link is kept.
- S09a rate-limit auto retry: run_retry_attempted now carries
failure_category in the SSE contract; a rate_limit retry row reads
"模型服务请求繁忙" + body, other retries keep "正在重试".
|
||
|
|
cc73b0e93f |
fix(daemon): retry transient OAuth refresh contention (#8160)
When two local processes race to refresh the same stored OAuth credential, the agent aborts its refresh and reports the condition as transient: Failed to refresh OAuth token: another Claude Code process is refreshing it or exited mid-refresh. This is usually transient; retry in a minute... The classifier had no branch for this text, so it fell to the generic process-exit fallback as `exit_nonzero`, which `transientSuppressedReason` refuses for `process_exit` with `non_retryable_category`. The safe-retry path was suppressed and the run died on a dead-end failure card, for a condition that clears as soon as the contending holder releases the lock. Name it `credential_refresh_contention` and wire it the way `session_resume_expired` already is: classified at `session_init` as retryable with `user_action: 'retry'`, and added to the `process_exit` transient allowlist. The branch is claimed ahead of the auth verdicts on purpose. The message ends "...or sign in again", and `auth` is fixed at `retryable: false` / `user_action: 'login'` — reaching it would prescribe re-authenticating a credential that never expired. The detector is anchored on the contention itself rather than on the word "refresh", so a genuinely spent or replayed refresh token keeps its non-retryable auth verdict; a test pins that direction. Retry is safe without further gating: the failure is raised before a request is sent, so nothing streamed, no tool ran and no artifact was written, leaving the side-effect gates in `decideSafeRunRetry` satisfied. |
||
|
|
df2ebd6b57 |
fix(web): keep late memory cards in their source conversation (#8065)
* fix(web): keep late memory cards in their source conversation * fix(web): preserve memory polling window ownership across retries * fix(web): preserve late memory attribution across consecutive turns --------- Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
64710082d0 |
fix(diagnostics): collect the AMR run's runtime log records (OPEND-3498) (#8488)
* fix(diagnostics): collect the AMR run's runtime log records (OPEND-3498) AMR incidents never carried AMR-side evidence. The collector looked for OpenCode logs under <dataDir>/amr/opencode-home, but Vela drops the OPENCODE_TEST_HOME it receives and runs OpenCode under per-conversation homes in $AMR_HOME/opencode-sessions, so nothing was listed and no note explained the gap. $AMR_HOME/logs/agent-runtime.jsonl was never listed either. - Take the incident run's records from agent-runtime.jsonl, plus those of the OpenCode sessions it owns until another run takes a session over, so other runs' records never enter the bundle. - Report a missing runtime log, a missing run id and the not-yet-located session logs as explicit collection notes, and keep those reasons instead of a consent verdict. - Give sources that appear after opting in a boundary at their current size on start, so a newly listed shared log is not omitted forever. Locating the per-conversation OpenCode session logs is left for a follow-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(diagnostics): keep a rotated log's baseline when extending the fence On Windows the packaged launcher rotates latest.log to previous.log at every start. extend() saw previous.log as an unbaselined path and recorded a boundary at its current size, so the rotated log's bytes written after the real boundary were dropped as consent_boundary. Found in a packaged Windows run: the daemon log written after opting in was missing from the bundle. Skip files that share a baselined creation time; the fence decides their boundary from that baseline. Skipping only defers to apply() and never admits bytes by itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e5d75aa8a1 |
fix(diagnostics): stop repeating API failures from flooding automatic uploads (#8461)
* fix(diagnostics): stop repeating API failures from flooding automatic uploads Stable 0.24.1 devices uploaded thousands of diagnostic incidents a day from one deterministic failure: GET /api/runs answering 400 PROJECT_SCOPE_REQUIRED. - Key API-failure incidents by failure signature per hour instead of the per-request id, so a polling caller yields one incident per hour. - Drop earlier "[diagnostics] incident delivered" receipt lines from automatic bundles; on busy devices they filled the daemon log tail. - Desktop pet task center: stop interval polling once the daemon requires a project scope, and stop restarting the poller on every 1.5s config reload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(diagnostics): filter receipts before allocating the log tail budget Scan automatic text logs backward with bounded buffers, preserving complete lines and consent offsets before applying the retained byte limit. Cover receipt suffixes larger than 4 MiB and filtered-tail boundary cases. Generated-By: looper 0.13.0 (runner=fixer, agent=codex) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
eb27acd322 |
feat(billing): support Coding Plan usage and wallet fallback (#8339)
* feat(billing): support Coding Plan usage and wallet fallback * fix(web): keep AMR send off billing preflight Workspace send no longer waits on advisory vela billing preflight; recovery still requests it. Realign zero-wallet P0 oracles and the credits-card wallet label. Generated-By: looper 0.15.0 (runner=fixer, agent=omp) * fix(billing): serialize recovery preflight and bound hung vela Skip overlapping AmrBalanceDialog recovery ticks and stop the watch after ten minutes even if a preflight is still pending. Give vela billing preflight a 60s runVelaCommand timeout so a hung CLI cannot pin recovery. Generated-By: looper 0.15.0 (runner=fixer, agent=omp) * fix(web): restore coding plan text contrast in billing card * feat(web): render Coding Plan windows and Go tier in the credits panel - CodingPlanUsage renders one row per preflight window (name, used %, remaining USD, reset countdown + absolute time) with loading/unavailable states; refreshes once on mount and once at resetsAt (no 30s polling, setTimeout delay clamped to the 32-bit limit). - Pure model in coding-plan-usage-model.ts; creditsPerUsd comes from `vela billing summary` through WorkspaceBillingSummary and falls back to 10,000 only when the backend omits it. - PlanWordmark/EntryNavRail accept the `go` tier (placeholder wordmark, design asset still pending). - i18n: 7 new billing keys + entry.billingTierGo across all 19 locales; real translations for the #8339 keys; drop unused codingPlanNone / codingPlanRemaining. - CLI wording: unstarted window reads "not started yet". * build(pack): pin vela-cli 0.1.4-test.0 and gate on billing preflight The packaged client needs `vela billing preflight` for the Coding Plan window rows. Bump the bundled CLI to the npm `test` prerelease that ships it and add a third capability gate in tools-pack: a CLI without the subcommand prints the parent `billing` help and exits 0, so the gate checks the help markers (preflight, --workspace-id, --model, --format) instead of the exit code. TEMPORARY: replace 0.1.4-test.0 with the stable 0.1.4 once it is released; do not merge with a `test` prerelease pinned. * fix(web): make Coding Plan rows legible on the inverted credits card The credits card is a hard-coded #202020/#fafafa inverted surface, but CodingPlanUsage.module.css read page-level --text-* / --bg-fill-* tokens, so on the card the window names rendered #202020 on #202020 (1.00:1) and the stat/reset lines sat below WCAG AA. Derive every colour from currentColor instead (name 100%, stat 72%, reset/note 60%, separator ~50%, track 36%, exhausted = --red mixed toward currentColor); measured in Chrome: name 15.6:1, stat 8.7:1, reset 6.4:1, track 3.2:1, exhausted 6.5:1. Keep the stat on one line (nowrap) and let the name ellipsize instead of wrapping "60% / used" onto two lines; drop the block's 12px side padding so it aligns with the card's own rows. Also add a minutes tier to the reset countdown: the last hour before a window resets used to show no countdown at all. New key billing.codingPlanResetsInMinutes in all 19 locales. * feat(web): rebuild the plan panel 1:1 from design PR #8364 Replace the improvised Coding Plan block with the design's panel, measured against docs/ui-previews/plan-panels/index.html in Chrome (computed styles and a 4x pixel diff of the Plus specimen): - Header: tier name + 16px wordmark, 升级 pill (管理 on the top tier). - One row 「7天额度免费用 · 已用 N% ›」 (7-day window only; rounded percent; 100% when spent, no red state) over a 5px #b1f38b track, then the separator and the 「钱包余额 · US$x.xx ›」 row. No headings, window names, remaining USD, reset time, note or unavailable line. - Team workspaces keep the pre-existing card unchanged (all team_* tiers); the quota row renders only for personal workspaces. - Free personal workspace: no quota row, but the header sells 升级 like the design (a personal workspace needs no billing permission; an account B reports without a plan upgrades as free). - Top-right pill shows the wordmark only, no balance. - Wallet always reads US$ regardless of locale (product ruling 2026-09-23). - zh-CN tier names follow the design: 免费版 / 标准版 / 专业版 / 旗舰版. - Drop the dead billing.codingPlan* keys and model helpers; add billing.codingPlanWeeklyAllowance and entry.creditsManage in 19 locales. * feat(web): show 管理 on personal Max per design PR #8364 The design's Max card sells 管理 (console auto-recharge settings) where every other tier sells 升级; product ruled on 2026-09-23 to follow the design for personal Max too, superseding the earlier "personal tiers are always upgradeable" ruling. The gate now asks isMaxPlanTier and 管理 wins over 升级 when both would show. * feat(web): route the plan card's three entries per product ruling 2026-09-23 ruling on the card's landings: 升级 opens the console's pricing dialog (billing=plan, unchanged), the allowance row opens the console dashboard (unchanged), the wallet row asks for the manual top-up dialog (new billing=recharge intent; B does not honor it yet and degrades to the dashboard), and 管理 on Max opens the plain dashboard instead of the auto-recharge dialog. * fix(web): align personal plan cards with v2 design and scoped loading * fix(web): center plan wordmarks inside the top-right pill * fix(web): reuse shared billing cache for plan hover cards * test(web): match personal billing preflight in avatar fixture * fix(billing): require usable quota evidence for funding recovery * test(billing): cover scoped preflight and quota event refresh end to end * feat(daemon): relay scoped coding plan usage events and health * fix(billing): refresh quota caches from scoped realtime events * test(daemon): accept capability query on mock event stream --------- Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
f166c02636 |
fix(packaged): let a macOS headless runtime become the desktop in place (#8398)
* fix(packaged): let a macOS headless runtime become the desktop in place
When Codex starts Open Design headlessly through MCP, macOS treats that
windowless process as the running app. Opening the app from the Dock,
Finder, Launchpad or `open -a` then only reactivates it, so no launcher
runs and no window ever appears (OPEND-3426).
On macOS the headless runtime now runs the desktop path with its window
deferred, and restores the window in the same process when the user
opens the app: through `activate`/`open-url` paired with the app becoming
active, or through SHOW from a newer launcher that sees a `restorable`
owner. The daemon, its port and connected MCP clients stay. A new outer
hands headless launches to the active payload and marks the launch as
managed; payloads started by older outers keep today's behavior.
Managed MCP registrations carry absolute sidecar endpoints for every mode
of the namespace, so the MCP process finds the daemon whichever mode owns
it, waits for a running owner instead of reopening it, and cold-starts
only when nothing runs. The daemon refreshes an existing Codex
registration on start and never installs one. A write interrupted by a
service restart now says it may or may not have been applied.
* fix(daemon): scope Codex refresh to its managed install
Require the existing registration's managed discovery marker to match before refreshing it, so local, stable, and prerelease installs cannot take over the global name from one another. Cover the unrelated-owner path without invoking a config mutation.\n\nGenerated-By: looper 0.11.2 (runner=fixer, agent=codex)
* fix(daemon): prove Codex registration ownership by data root and app
Tighten the ownership check from
|
||
|
|
7bad1a45f2 |
feat(telemetry): classify publish and deploy failures additively (#8402)
Publish-public and deploy failures were only visible as one opaque bucket
each (`publish_failed`, generic HTTP_400 / Error), so neither PostHog nor the
automatic diagnostics bundles could tell why they failed.
Purely additive — no existing status, error code, event value, trigger or
copy changes:
- daemon: failed publish/unpublish/deploy responses gain an optional
closed-token `failure` { stage, reason, upstreamStatus?, upstreamCode? }.
Vela CLI failures are classified from the CLI's stable stderr contract
(vela-cli 0.0.33–0.1.3); deploy failures separate provider rejection,
unreachable provider, rejected Vercel token, local file and internal
errors. Each failure also writes one structured, text-free log line.
- web: artifact_publish_result / artifact_deploy_result gain optional
failed_stage, failure_reason, upstream_status, upstream_error_code, and
(publish) daemon_error_code. `error_code` keeps its values.
- correlation: each attempt's analytics request id is sent as
x-od-request-id, logged by the daemon, and reported as request_id.
|
||
|
|
282416ccd8 |
feat(diagnostics): automatically collect consented failure evidence (OPEND-3397) (#8357)
* feat(diagnostics): persist and upload consented Agent fault evidence * feat(diagnostics): capture cancellation and delivery experience failures * feat(diagnostics): collect visible frontend failures through consented relay * fix(web): reset cached analytics before diagnostic test mocks Generated-By: looper 0.13.0 (runner=fixer, agent=codex) * fix(web): reset template picker before it becomes interactive * test(web): await campaign presentation before retry polling |
||
|
|
06e0fd7259 |
fix(cms): keep a campaign on screen through network, sleep and clock noise (#8292)
* fix(web): page recovery no longer withdraws a valid touchpoint lease (OPEND-3363)
`online`, `pageshow` and `visibilitychange` were all bound to the destructive
`wake()`, which calls `revoke()` synchronously — before the revalidation it
starts has produced any evidence. Switching Wi-Fi, waking from sleep or tabbing
back therefore unmounted a campaign the server had authorized, and the user saw
it vanish and (sometimes) reappear.
The loss could also be permanent. An emptied lease makes `abandonAttempt`'s
`!lease.current` branch true, so the first failed revalidation dropped the saved
`revalidationLease` too; the retry that succeeded came back `{kind:"retain"}`
with nothing left to restore. The impression was already recorded, so the
activity never showed again for that session.
The three recovery events now take the non-destructive shape `focus` already
had, named `resume`: a lease still inside the window the server granted keeps
its mount and revalidates in the background, a lapsed lease falls through to
`wake`, and a hidden page cancels only the request in flight. `armExpiry` still
retires every lease on the server's own deadline, and a 410 withdrawal still
closes at once.
Deliberate behavior-contract change. Three existing cases pinned the old
destructive semantics and are rewritten here with the opposite outcome:
- `withdraws old authority synchronously on wake and rejects a timed-out
revalidation` → `keeps an unexpired visible decision mounted while an online
revalidation is pending`
- `still withdraws display and authority on actual page hiding` → `fences a
hidden page without withdrawing the lease it was granted`
- `fences the old modal action until recovery mounts a fresh decision` → `keeps
the mounted modal action authorized across focus and online recovery`
The badge case additionally drops from three refreshes to two: `online` now
joins the revalidation `focus` already has in flight instead of tearing it down
and re-requesting.
Every new case asserts inside the pending revalidation window, not on the
settled state — the blind spot the four network edge cases this week shared.
* feat(web): let a production touchpoint hold the authority the server grants (OPEND-3366)
`resolveAuthorizationDeadline` takes the minimum of the server's
authorization, the activity's `endsAt` and a local cap. With A3 removing the
sixty-second clamp on `authorizationExpiresAt`, that local cap became the
binding term: a five-minute `MAX_LEASE_MS` truncated every long authorization
back to five minutes, so a client that lost the network went blank in the
middle of an activity that was still running.
The cap now lives once, as `PRODUCTION_MAX_LEASE_MS`, at the longest interval a
timer can name — a backstop against a server clock that grants past the
activity, not a policy. `endsAt` governs, which is what the server means.
`armExpiry` already segments the wait with `Math.min(remaining, MAX_TIMER_MS)`
and is unchanged.
All three production placements (Modal, Badge, Hover) had their own copy of the
five-minute constant, which is exactly how two of them could keep a short lease
after the third was fixed. They now share the one exported value, and a test
reads all three sources to keep it that way.
Depends on OPEND-3363: before it, page recovery emptied the lease before any
failure was evaluated, so a long authorization had nothing to protect.
* feat(daemon): assemble touchpoint content locally instead of re-downloading it (OPEND-3371)
A production decision refresh re-downloaded the whole content package every 30
seconds, although the bytes almost never change. B4 lets a caller say which
content it already holds and get a `contentOmitted` reply, but taking that
benefit in the browser would need a new desktop package — every already-shipped
prerelease would keep paying full price. Putting it in the daemon means the
client does not change at all and existing installs benefit immediately.
The daemon now carries `heldContentId` / `heldContentLocale` on its own behalf,
and puts the omitted `content` back from a local store before answering the
browser. Two layers: blobs addressed by their own digest, so the four
placements of one activity share `shared.js` / `theme.css` once; and one
assembly record per (placementKey, locale) naming the manifest, the entry and
the path → digest map. The root is derived from `RUNTIME_DATA_DIR`, so two
namespaces get two stores and neither can see the other's.
The proxy could stay a pure `source.pipe(dest)` only as long as nobody had to
read the body; this route now buffers and rebuilds the JSON. Everything else —
the Test runtime, the events endpoint, any production request the daemon holds
no content for — keeps the verbatim streaming path.
Two properties carry the design, and each has a named case:
- A daemon holding nothing sends today's request and returns today's response
byte for byte, with no `contentOmitted` anywhere. Published clients guard on
`if (!next.content?.id) return clear`, so breaking this makes campaigns
disappear.
- Nothing behind the daemon may cost the campaign. A miss, a corrupted blob, a
digest that stopped describing its bytes, an unwritable data directory, a
Vela that ignores the parameters — each falls back to exactly today's full
request. A trimmed reply that cannot be rebuilt is re-asked once, without
held content, and that retry can never trim again.
The cache holds no opinion about WHICH activity to show: the server re-decides
that every request, so a stale cache cannot pin a withdrawn activity, and a new
one is still visible within one poll.
Measured on a realistic package (real minified JS/CSS as content bytes):
638.8 KB raw / 190.1 KB gzip for the full response against 0.7 KB raw /
0.4 KB gzip for the trimmed one — the steady-state WAN body is under a
kilobyte per refresh regardless of how large the activity is.
* test(web): pin what a rotating touchpoint decision id costs the client (OPEND-3369)
The server-side measurement can only show that `touchpointDecisionId` differs
per request (120 distinct ids in an hour of polling). Whether that costs
anything is decided on the client, in the lease key: `refresh` bumps
`generation` whenever the key changes, and the mount effects are keyed on
`generation`, so a rotating id is a full teardown and re-mount per poll.
Every existing lifecycle case uses `key: "same"`, so this path had never been
observed. Two pairs of cases observe it now, at both seams:
- `useTouchpointLifecycle`: a rotating key over 120 polls produces 120
generation bumps and a new decision object each time; the same hour with a
stable key produces zero.
- `ProductionCampaignModal`: the same hour produces 121 distinct
`opend-touchpoint` elements and 121 `mount` calls with a rotating id, against
one element and one mount with a stable one.
Tests only; no behavior change. This confirms the client-side benefit claimed
for B3 — 120 remounts an hour become none once the id is stable.
* fix(web): take the one-shot credential out of the touchpoint lease key (OPEND-3374)
All three production placements put `touchpointDecisionId` in their lease key. A
changed key is `++generation`, which is a full teardown: shadow DOM rebuilt,
Blob URLs re-created, entry animation replayed, scroll lock released and
re-taken. But that id is not content identity — it is a credential the server
re-issues whenever its own sixty-second row lapses.
OPEND-3369 stabilised the id and removed the every-thirty-seconds version of
this churn. It did not remove the cause. The credential lives 60s and the client
polls every 30s, so missing two polls — a Wi-Fi switch, a tunnel, a closed lid —
expires the row; on recovery the server INSERTs a new one and the campaign
flickers. That moved the remount from "on a timer, for everyone" to "whenever
the network wobbles", which is the same population the recovery-lifecycle P1 was
about.
The key now answers only the question it exists to answer — same content, same
person, same deployment — through one shared `touchpointContentIdentity`, so the
three placements cannot drift apart.
Hover needed arguing rather than deleting, and the argument came out differently
than expected. Its key carried the ENTRY's content id plus BOTH credentials; the
layer's content id was never in it, so the layer's identity rode on its
credential. Whether a deployment always gives both placements the same
`content.id` is a server-side property this side cannot verify, and this repo's
own hover fixtures assume it does not (each placement gets its own version). So
the layer's content version is now named explicitly. The red test for it —
`still remounts the pair when only the layer's content version changes` — fails
on the previous key by showing the layer still rendering the replaced content.
Retaining the previous decision object means the client presents a credential
the server has expired. Both uses of it are asserted here rather than assumed:
`still clears on a 410 whose receipt echoes the stale decision id the client
kept` covers OPEND-3372, and `reports the stale decision id on a click after the
credential rotated` covers OPEND-3364. Without those two, this change would
become a revocation failure or lost attribution.
Deliberate contract change. The three OPEND-3369 observation cases measured the
cost of a rotating id and are rewritten with the opposite outcome:
- `rebuilds the mounted host on every poll when only the decision id changes` →
`keeps one mounted host for a whole hour of rotating decision ids` (121 hosts
and 121 mounts become 1 and 1)
- `keeps one mounted host for a whole hour when the decision id is stable` keeps
its result and loses its role as a contrast — the two now agree, which is the
point
- the hook-level `remounts once per poll when the decision id rotates ...` is
re-framed as `... when the lease key rotates ...`: the hook never knew about
decision ids, and which inputs change a key is now decided in the placements
OPEND-3369 is not undone — its write-side saving stands on its own and this
returns it to being purely that.
* fix(web): judge a set-aside lease by its own window, not by an empty screen (OPEND-3376)
**The ticket asked for `revalidationLease` to be deleted as unreachable. It is
not unreachable, so this takes the alternative the ticket names instead: fix
`abandonAttempt`'s criterion. See below — this needs a decision.**
The reachability argument was: `wake()` has one call site, `resume`'s `else`
branch, which runs only when the lease is missing or lapsed; so `wake()` can
only ever set aside a lease that is already expired; and the restore branch
requires the set-aside lease to still be valid; so the two are mutually
exclusive.
Every step is true except the last. It needs "expired" to be a property that,
once true, stays true. It is not. `elapsed()` is `max(monotonic, wall)`, so a
wall clock that steps FORWARD makes a live lease read as expired — `resume`
then takes the `wake` branch — and a correction BACK makes it read as live
again. NTP steps, VM resume and dual-boot clocks all do this, and this module
already takes wall-clock skew seriously enough to have a case for it.
Verified with a throwaway probe against this branch's HEAD: step the clock an
hour forward, dispatch `online`, step it back, resolve `{kind:"retain"}` — the
restore branch fires and the campaign comes back. Deleting the mechanism would
therefore have been a silent behavior change, not a no-op.
So the P1 hazard is repaired where it actually lives. `abandonAttempt` decided
whether a failure may end display by asking whether there is an ACTIVE lease,
treating "none" as "expired". `wake` empties `lease.current` before it
revalidates, so the next failure met that branch, spent the set-aside lease too,
and the retry that finally succeeded came back `{kind:"retain"}` with nothing
left to restore — gone for the session. The criterion now judges whichever lease
is still recoverable by its own window.
This is stronger than the deletion would have been. The deletion's safety rested
on `wake()` keeping exactly one call site, which nothing enforced; adding one
back would have reopened the loss. After this, a new `wake()` call site cannot
cause permanent loss at all, because the short-circuit that caused it is gone.
Red first: `no single failure can discard display authority that is still
recoverable` drives the clock excursion, fails one revalidation inside the
set-aside lease's own window, and asserts the retry restores display. It fails
on HEAD and passes here. All 42 pre-existing lifecycle assertions are untouched
and still pass, so nothing was bent to fit.
What is NOT delivered: `revalidationLease` still exists, and so does the
three-state lease machine the ticket wanted reduced to two. Whether to keep the
restore branch (it un-does a spurious withdrawal caused by a bad clock) or drop
it and accept one remount plus up to a 30s gap in that case is a product call,
not one to make inside a "delete dead code" ticket.
* refactor(web): a lease carries content identity, not authorization timing (OPEND-3377)
Since OPEND-3374 a matching key retains the previous decision object, which is
the point — consumers depend on `decision`, so a new reference would re-mount.
But the retained object is the whole response DTO, and three of its fields
describe something that does not survive being retained: `serverTime`, `endsAt`
and `authorizationExpiresAt` are whatever some earlier response happened to
carry. How long display may last comes from the lease's own `validForMs`, taken
fresh every time.
Nothing reads the stale copies today. That is a fact about who has written the
consumers so far, not about the code: the day someone adds `decision.endsAt` to
a countdown they read an end time the operator has already moved, and nothing
fails to tell them. Same species as OPEND-3376 — correct, but resting on an
invariant nothing enforces.
Option (a) from the ticket. `touchpointLeaseValue` drops the three fields and
`TouchpointLeaseValue<T>` says so in the type, and the value really does not
carry them, so a consumer that casts past the type still finds nothing. One
named helper, three placements, matching `touchpointContentIdentity` and
`PRODUCTION_MAX_LEASE_MS`. (b) was rejected because `never`-typed fields still
read as present and still invite `as`.
Chosen for the reason the ticket gives: it states the boundary rather than
patching a symptom. A lease carries a STABLE content identity, which decides
whether to re-mount, and a FRESH authorization window, which decides how long
display may last; these are different things and no longer share a DTO.
The type system then found the rest by itself. `dispatchProductionCampaignAction`
declared it took a whole `Decision` while reading only static actions and
identity — it already receives the window separately as `expiresAt` — and
Hover's `matches` did the same. Both are narrowed; no call site changed.
Evidence for the acceptance bar: adding
`console.log("activity ends at", decision.endsAt)` to the Modal's mount effect
produces
src/components/ProductionCampaignModal.tsx(375,46): error TS2339:
Property 'endsAt' does not exist on type 'AuthorizedDecision'.
Three type-level assertions in the suite keep that true without needing a human
to re-run the experiment: each `@ts-expect-error` is itself checked, so if a
field came back TypeScript would report the directive as unused and `pnpm
typecheck` would fail.
Pure narrowing. Every pre-existing assertion in the lifecycle suite and in all
three placement suites is untouched and still passes, including OPEND-3374's
retained-credential cases, and the web suite is green.
* test(web): pin that a receiptless 410 withdraws display (OPEND-3375)
Vela now answers a request carrying an `activeDecisionId` for a deployment that
has been taken down with `410 production_runtime_withdrawn` and no receipt,
shaped byte for byte like a rollout withdrawal that cannot name one. The client
half of that was explicitly outside its authorization.
The client already handles it correctly, and this locks that in rather than
changing it: the receipt parse is what decides, so any 410 that does not yield
all four fields becomes `http_410`, and `http_410` is the one detail that sets
`touchpointWithdrawal` — the single flag `withdrawsDisplay` reads, and the only
failure permitted to end a lease the server already granted. Every other failure
is transport noise the lease rides out, which since OPEND-3363 it really does
ride out, so a 410 quietly reclassified as noise would now mean a withdrawn
campaign stays on screen for the rest of its lease.
Two cases, and neither goes red first — they are regression locks, not fixes:
- the loader, over the withdrawal body, the rollout body it mimics, a null
receipt and an empty body, asserting `http_410` + `touchpointWithdrawal`
- the Modal end to end through the real loader and lifecycle: a withdrawn
deployment closes the dialog on the next poll, and a later 200 does not bring
it back
* fix(web): a clock step must not re-mount a campaign that never left the screen (OPEND-3378)
Audit of every consumer of `elapsed()` under the non-monotonicity found in
OPEND-3376: `max(monotonic, wall)` rises and falls again when a wall clock steps
FORWARD and is corrected BACK. Ten call sites; nine acceptable, one not. The
verdicts are in the report; the reasoning that survives in code is on `elapsed`
itself, whose old one-line comment ("a forward jump can only shorten it") is
what made the shape sound monotonic in the first place.
The one that had to change is `same`, which decides renewal against re-mount.
It asked whether the previous lease was still inside its window, and a forward
step makes a perfectly live lease answer no. The response arriving during the
step is then a new presentation: `++generation`, rebuilt host, rebuilt Blob
URLs, replayed entry animation — the flicker OPEND-3374 removed, re-entering
through the clock. A3 made leases run for the whole activity, so the window a
step can land in went from a minute to days.
It is also not defensible on its own terms. Nothing evaluates `elapsed` until
something asks, so a step alone tears nothing down (verified by probe): the
campaign is still mounted and still authorized when the poll lands, and there is
no withdrawal for that re-mount to correspond to.
So the question is asked only where it means something: a lease being RESUMED
from `revalidationLease` must still prove it is inside its window, because its
display really was withdrawn. A lease that is still `lease.current` renews. A
mounted lease that has genuinely lapsed is not reachable here — `armExpiry`
retires it, which empties `lease.current`.
Red first: `a clock step forward does not re-mount a campaign that is still on
screen`. It fails on HEAD with `expected 3 to be 2` — one generation too many.
Deliberately NOT changed: `elapsed` itself. The monotonic term stops a clock set
back from granting time; the wall term stops a sleeping device from freezing the
lease, because `performance.now()` pauses across sleep on some platforms. Each
closes a cheat the other does not, so making it purely monotonic or purely
wall-clock would be a regression, and the fix belongs at the call sites that
care about the direction of the error.
Every pre-existing assertion is untouched, including OPEND-3376's
`no single failure can discard display authority that is still recoverable`.
* fix(web): drop the visibility fence whose premise OPEND-3363 removed
#8269 and OPEND-3363 are each correct and together reproduce the P1 they were
both written to remove: a campaign that vanishes on a tab switch.
#8269 added a `visibilitychange` fence releasing the open presentation whenever
the page goes hidden. Its own comment states the premise — "A hidden page ...
withdraws the lease and takes this modal down with it" — which was true when it
was written. OPEND-3363 removed exactly that: hiding now cancels only the
request in flight and leaves the lease, and the modal, alone.
So the fence released a presentation that had not ended. The campaign stayed on
screen with nothing recorded as presenting it, and the poll arriving on return
took `!continuesOpenPresentation && wasDisplayed(...)` with
`openPresentation.current` already null — the `{kind: "clear"}` branch — and
closed the host.
Reproduced before touching anything, on the rebased branch:
keeps a displayed campaign through a tab switch now that hiding no longer
withdraws it
→ TestingLibraryElementError: Unable to find an accessible element with the
role "dialog"
and the failure is on RETURN, not on hide: the modal survives being hidden, so
this is the interaction and not a regression in either change alone.
The fence is removed. What it protected is still protected, by the presentation's
own deadline: it is anchored to the authorization that opened it, so a sleep long
enough to lapse the lease also lapses the presentation, and the offer arriving on
wake is correctly read as a new one. That is asserted rather than argued.
Everything else from #8269 is kept, including the parts this branch was behind
on: campaign hosts confined to the home view, `onError` releasing the
presentation only for a real withdrawal, the `revokesActive` refactor, and
`touchpointWithdrawsDisplay`.
Deliberate contract change. #8269's `does not re-present a displayed campaign
after the page is hidden and shown again` asserted that hiding takes the modal
down, which OPEND-3363 made false. It is rewritten as `does not re-present a
displayed campaign after a sleep outlasts its authorization` — the property it
was actually protecting — with a sleep long enough to be a real one.
* fix(daemon): repair a damaged content blob instead of keeping it forever
`remember` skipped any blob file that already existed. The name is the
digest, so a present file "should" hold exactly those bytes -- but a file
damaged after it was written keeps its name, and `remember` is the only
moment the daemon ever holds the correct bytes for it again. Every other
round it is answering from a trimmed reply that carries no content at all.
The result was a closed loop with no exit and no signal: `held` sees the
files exist and offers the version, Vela trims its reply to ~1KB,
reassembly fails on the digest, the fallback refetches the full ~270KB,
and `remember` declines to overwrite. That placement then pays a trimmed
ask plus a full ask on every single poll -- strictly worse than running
with no cache at all -- and nothing in this module logs or counts it.
Writing unconditionally costs nothing in steady state, because `remember`
only runs on a response that was not trimmed. The rewrite is atomic and
idempotent, so the loop now closes after exactly one round.
The existing HTTP case pinned the defect as expected behaviour: it ran a
single round and asserted two upstream calls. It now runs one round
further and asserts the second round is back to one conditional ask.
* fix(daemon): make a blob's digest a statement about the file itself
`readVerifiedBlob` hashed a decode of the stored file and then returned
the file. Those are not the same bytes. Node's base64 decoder drops every
character outside the alphabet -- anywhere in the input, not only after
the padding -- so anything appended to or injected into a stored blob was
invisible to the digest while still being handed back and forwarded to
the browser.
The handover notes called this harmless on the grounds that the browser
decodes the same way. It does not. `atob` is WHATWG forgiving-base64: it
strips ASCII whitespace and throws `InvalidCharacterError` on anything
else. So the daemon reports a verified package, skips the fallback it
keeps for exactly this case, and the browser refuses to mount it. With
the blob-repair fix this placement would still self-heal in one round;
before that fix the pair was permanent, silent, and invisible to every
integrity check on both sides.
Blobs now store the bytes their digest names and are re-encoded to base64
on the way out, so `sha256(file) === digest` is an exact claim about
everything the cache returns. Modules already worked this way; the two
layers now share one invariant.
Two consequences, both deliberate:
- blobs written by an earlier build no longer match their digest. They
are repaired on the next full response, which is why the blob-repair
fix has to land first -- without it every one of them would be
stranded and cost double forever.
- a rebuilt response now carries Node's canonical base64. It stays
byte-identical to the original only while upstream sends canonical
base64 too, which Vela does today. The warm/cold equality case is the
assertion that will fail if that ever stops being true.
The cache fixture's shared resource was exactly 24 bytes, so its base64
form had no padding and appended garbage folded into the decode and broke
the digest by accident -- the new cases would have passed against the old
code for the wrong reason. It is 25 bytes now.
* fix(daemon): bind reassembly to the version it was negotiated for
`reassemble` took only the cache key, so it reread the record from disk
and rebuilt from whatever that record said at that moment. The record is
mutable: a concurrent full response for the same placement replaces it.
When it did, the daemon spliced the new campaign's content into a reply
the server had framed for the old one -- and nothing downstream could
tell. The browser verifies that the content is internally consistent, and
it is; both halves are. So it renders campaign Y's creative and reports
its impressions against campaign X's decision id. Billing and attribution
cross silently, on a path where every integrity check passes.
The pair this attempt offered upstream is now an argument, and
`recordStillHolds` is the invariant: a reply trimmed against one version
may only ever be rebuilt against that version. A record that has moved on
returns null, which the existing fallback already handles by re-asking in
full.
The locale it compares is the one the daemon OFFERED, not the one it
asked for. Vela resolves a placement locale through [requested, base
language, en-US], so a zh-CN request is legitimately answered and cached
as en-US, and `held` reports that faithfully. Comparing the requested
locale would strand every placement the server answers through a
fallback, which is why a case now pins that too.
Dropping the upstream request on abort ships with it, because the two are
one defect in practice. This proxy had no cancellation at all, while the
AMR proxy a few hundred lines up has had it all along. The browser's
per-attempt budget is 15s and this proxy's is 30s, so every attempt the
browser gives up on stayed alive here for up to fifteen more seconds:
still downloading, still buffering, and still writing the assembly record
that a later, live request was about to rebuild from. `res.headersSent`
does not catch it, because an attempt aborted before a byte went out has
sent no headers. That is what turns the race above from a one-round-trip
window into a fifteen-second one, which is why fixing only the first half
would leave reviewers with the wrong idea of how wide it is.
The fake Vela can now be taken over by a case that needs to control when
a reply lands, not only what it says. It still may not bypass the server's
own rule -- content is omitted only for a caller that already holds
exactly the version being served -- so the crossing above is produced the
way production would produce it: by promoting a campaign mid-flight.
* fix(daemon): put a ceiling back on buffering and decompression
Before content assembly this route forwarded production decisions as a
stream, so its memory was bounded by the socket. Reading the body to
assemble it gave that up and replaced it with nothing: `size` was tracked
only to size the final `Buffer.concat`, and the three `zlib.*Sync` calls
ran without `maxOutputLength`, which leaves the limit at
`buffer.kMaxLength` -- effectively none. Measured on this Node: a 199KB
gzip response allocates 200MB in 51ms, a ratio of about 1000:1. The 2MiB
check people point at runs four allocations later, inside the cache, long
after the damage is done.
Two things make this worse than it looks for a daemon. The decompressors
are synchronous, so an oversized body does not merely consume memory, it
holds the event loop of a privileged local process while it expands. And
this is not an edge route: it is the steady-state poll every placement
makes twice a minute.
Both limits now derive from one number. `MAX_CONTENT_BYTES` -- the budget
the web host already enforces on a content package -- is exported, and
the proxy allows four times it, which covers base64's 4/3 inflation plus
the manifest and metadata wrapped around the resources. A body past that
cannot be assembled into anything the browser would accept, so buffering
it buys nothing.
Past the ceiling the route degrades to streaming rather than refusing:
the caller still gets its answer, in one WAN fetch, framed exactly as
upstream framed it. That is the behaviour this route had before, and it
is what the pre-existing `passThrough` path does for every other request.
* chore(daemon): escape the key separator so the file is not binary to git
`keyName` separated the placement key from the locale with a literal NUL
byte written straight into the template string. Git classifies a file
containing NUL as binary, so this one showed up in the pull request as
`+0/-0` and `Binary files differ`. It is the 11KB module the whole
content-assembly feature lives in, and no reviewer on GitHub could open
it -- which is exactly where the defects fixed earlier in this branch
were sitting.
`\u0000` is the same character. Nothing about the hash, the file names on
disk or any stored record changes; only git's guess about the file type
does.
* fix(web): stop the timer limit binding the production lease again
`PRODUCTION_MAX_LEASE_MS` exists to catch a server clock that grants past
the activity it is granting for. Giving it `MAX_TIMER_MS` answered a
different question -- what one `setTimeout` can name -- and so made it the
binding term in `resolveAuthorizationDeadline` for every schedule longer
than ~24.9 days.
That is the same defect OPEND-3366 was written to remove, one tier up. The
five-minute value truncated every longer authorization to five minutes;
`MAX_TIMER_MS` truncates every schedule longer than ~24.9 days. `armExpiry`
has segmented long waits since this landed, so the timer's reach was never
the lease's problem to borrow.
It is reachable. The server validates only `endsAt > startsAt` and a future
`startsAt` -- there is no maximum schedule length anywhere in the write
path -- and its own production-runtime fixtures assert multi-year windows
are handed back unshortened. A device that misses every poll for 24.9 days
(a lid closed over a long trip, then opened offline) wakes to a truncated
lease, `wake` treats the offer as a new presentation, and the recorded
device impression retires it. That campaign never shows on that device
again.
The docblock made the same mistake in prose, and said so confidently: "At
the longest interval a timer can name it stops binding, and `endsAt`
governs". True below 24.9 days, false above it, and it is the sentence that
would have let the next reader repeat this. Rewritten to keep the two
bounds apart -- one timer segment versus one authorization -- and to record
both times the constant was given the wrong one.
Ten years, not `Infinity` and not a dropped term: skew is exactly what
`endsAt` cannot catch, because `validForMs` is `deadline - serverTime` and
`endsAt` is what the lag is measured against. Not one year either, for the
same reason five minutes failed -- the team's own fixtures schedule past it.
* test(web): give the sleep case a control that can tell absence apart
Both assertions in "does not re-present a displayed campaign after a sleep
outlasts its authorization" are absences. An absence is also what a
lifecycle that never came back looks like, so the case could not
distinguish the device impression closing a live offer from no offer
arriving at all -- and a `wake` that stopped waking would have left it
fully green.
Verified by construction: with `resume`'s lapsed-lease branch stubbed to
return instead of calling `wake`, both `toBeNull()` assertions still pass
and only the new control fails.
The control drops the impression and wakes once more through the same
event and the same advance, so the impression is the only difference
between presenting and not.
* test(web): cover the two background-recovery cases A5 had no test for
A5 (screen sleep / backgrounding) was carried as unverified because
`document.hidden` cannot be forced inside a real Electron renderer. That is
true of Electron and not of this suite, which has been forging it all along
with `vi.spyOn(document, "hidden", "get")`. Two of its four cases had no
coverage at all.
Short side, at the modal. The existing tab-switch case toggles visibility
on the real clock with nothing in between, so it cannot tell "the lease was
left alone" from "nothing had time to lapse". The new case is hidden for
forty-five seconds of a sixty-second authorization: a poll tick falls
inside the spell, and the lease is still the server's to renew on return.
It asserts the same dialog node, the same `opend-touchpoint` host, and zero
requests while hidden.
Long side, at the hook. Nothing covered a lease lapsing WHILE the page is
hidden. `armExpiry` does not consult `document.hidden`, so the grant
retires on the server's deadline whether anyone is watching or not, and the
return has nothing left to revalidate: it must go through `wake`, exactly
once. `generation` is asserted as changed rather than as a number, because
this path steps it three times and pinning the count would pin the route
instead of the outcome.
Both were checked by breaking what they claim to hold:
- visibilitychange bound back to `wake` (pre-OPEND-3363) -> the modal case
fails on the first assertion after hiding.
- `refresh` no longer fencing a hidden page -> the modal case fails on the
zero-requests assertion.
- `armExpiry` consulting `document.hidden` -> the hook case fails on
`current` being null.
- `resume` no longer waking a lapsed lease -> the hook case fails on the
single `load`.
Recorded for the next reader: reverting OPEND-3378 does NOT red the modal
case. The lease is still inside its window at the moment of return, so
`same` holds either way; 3378 needs a clock step forward, which is already
covered by "a clock step forward does not re-mount a campaign that is still
on screen".
What is left for a human is one platform fact, not product logic: whether
an Electron renderer flips `document.hidden` on sleep, occlusion and
minimise at all. Recovery does not depend on that event firing -- the
expiry timer ignores it, the thirty-second poll runs regardless, and focus,
online and pageshow share the same handler -- so the worst case is one poll
interval of delay rather than a wrong branch.
* docs(web): scope the OPEND-3377 stripping claim to production
"They do not survive into the lease" reads as a property of
`useTouchpointLifecycle`. It is a property of `touchpointLeaseValue`, and
the hook has a fourth consumer that does not apply it: `TestCampaignModal`
retains whole decisions with `serverTime`, `endsAt` and
`authorizationExpiresAt` intact, and reads across polls from the object it
retained.
That is the right call there -- the Test channel exists to show an operator
what the schedule is doing, its authorization is capped at sixty seconds
and every round recomputes from a fresh `serverTime`, so nothing it holds
can be stale by more than one poll -- and this change does not touch it.
What it does is stop the note reading as a repository-wide invariant when
the source scan behind it only visits the three production files. Left
unqualified, the next reader adding a consumer would believe a guarantee
nothing is enforcing.
* test(web): stop waiting for a real animation frame on a fake clock
The device impression is recorded inside a `requestAnimationFrame`, and
`requestAnimationFrame` was not in either case's `toFake` list. So the frame
stayed on the REAL clock while the case advanced only the fake one, and
`expect(localStorage.getItem(marker())).toBe("1")` after a fixed
`advanceTimersByTimeAsync(16)` was really asking whether enough WALL time
had happened to pass inside the awaits. On an idle machine it had; under
load it had not.
Measured, 12 busy cores against one file, six runs per arm:
- "keeps the same host through a background spell" (mine) 3/8 red
- "does not re-present ... after a sleep outlasts" 3/8 red
with every one of my commits reverted -- this one arrived with
the #8269 rewrite and was already failing before I touched the file
- after this change 0/29 red
Both now go through one helper that advances in small steps until the
marker lands. Each step yields to the real microtask queue, which is what
lets `verifyWebTouchpoint`'s real crypto finish; the frame it then requests
fires on fake time. Ablation, same load: the stepping alone is what carries
it (6/6 green); faking frames alone is strictly worse than before (6/6 red),
because a single fixed advance can run out before the frame is even asked
for. Frames are faked anyway, so the mechanism the case waits on belongs to
the clock the case controls, instead of to the wall.
"keeps the displayed campaign on screen when a poll fails and its retry
recovers" -- the case CI reported, and the A1 watchman from #8269 -- is not
touched. It sets the marker by hand precisely because of this, and it was
green in all 14 runs with my commits reverted. It has a weaker form of the
same fragility, noted for a follow-up: it reads
`document.querySelector("opend-touchpoint")`, which the host satisfies
synchronously, so nothing in it waits for the mount that sets
`openPresentation`. Starve that crypto long enough and the recovering poll
finds no open presentation, reads the impression it set by hand, and clears
the host. That is how it can fail, and it is not this PR's to change.
* fix(web): stop bounding production display authority by a lease duration
`resolveAuthorizationDeadline` took `serverTime + PRODUCTION_MAX_LEASE_MS` in
its minimum, so the ten-year value truncated every longer grant. The server's
own production-runtime fixtures run 2020-01-01 to 2100-01-01, so the premise
that no operator's schedule could reach it was false when it was written:
after ten years offline the client would withdraw a campaign the server still
authorizes, which is OPEND-3366 again at a larger threshold.
The term is removed rather than widened. Three times it was filled with a
number that answered a different question -- five minutes (the poll interval),
`MAX_TIMER_MS` (one timer's reach), ten years (a guess at "far enough") -- and
each became the binding answer to this one. Every fill was defended as a skew
backstop, but a duration cap only ever sees the SUM of the skew and the
schedule, so no value can bound one without binding the other: a value small
enough to catch a month of skew truncates every multi-year campaign. There is
no fourth value to try.
Skew is carried instead by the terms that survive it. `authorizationExpiresAt`
stays in the minimum, so a clock-derived credential window cancels the offset
exactly; `endsAt <= serverTime` still refuses an activity already over on the
server's clock; and a successful poll replaces `validForMs` outright, so an
inflated window is only ever spent by a client that cannot reach the server
for the whole of it. The residue -- a server clock wrong by a month
over-displays by a month, an error the size of the skew and not of the lease
-- is recorded as accepted, together with why comparing `serverTime` against
the device's `Date.now()` is not the fix.
The one maximum left is the Test runtime's sixty-second contract, now named
`TEST_MAX_AUTHORIZATION_MS` and a rejection threshold only; it cannot shorten
anything. The regression matrix gains the server fixture's own 2100-01-01
window, and the three-placement guard now asserts production passes no
maximum at all.
* test(web): wait for the mount behind the impression, not for a hand-written marker
`keeps the displayed campaign on screen when a poll fails and its retry
recovers` wrote the device-impression marker itself, to get past a frame its
`toFake` list had left on the real clock. That manufactures a state the
product cannot produce: `openPresentation` is assigned when `mountTouchpoint`
resolves and the marker only in the frame after it, so "impression recorded,
nothing open" exists in the case and nowhere else -- and it is exactly the
`{kind:"clear"}` branch of the load callback. Whenever the real SHA-256 behind
the mount had not finished inside the case's fixed ten-millisecond advance,
the recovering poll therefore tore down the very host the case then asserted
on, which is the `expected null not to be null` this went red with.
This is an existing weakness in the case, not one this branch introduced.
Starving the digest reproduces it identically on
|
||
|
|
9aa3114f3e |
fix(daemon): make heap pressure, SQLite growth and OOM exits observable (#8354)
A daemon that dies of a V8 OOM cannot report itself, and today nothing records heap usage, SQLite growth or why the previous daemon ended. Add a persist-then-report health checkpoint: - sample heap spaces, RSS and event-loop delay (60s and after major GC), flag pressure levels and jumps, and keep a synchronous in-flight marker for large reads so an OOM inside one stays attributable - opt the daemon into Node's fatal-error report once listening; the next boot reduces it to controlled fields, deletes the raw report, and reports the previous session as daemon_health_summary or daemon_unclean_exit (carried forward across crash loops) - add storage_* fields to the emitted run_finished (and its startup replay) measured with SQLite octet_length, plus a constant-cost page snapshot (daemon_storage_snapshot) - bundle the checkpoint in the diagnostics export Observation is additive: existing return values, rows, HTTP responses, event properties, recovery snapshots, logs and exit statuses are unchanged, and every hook isolates its own failures. Refs OPEND-3402 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a8d94dfa6e | fix(updater): make payload runtime handoff atomic (#8348) | ||
|
|
894d55466b |
chore(release): bump main to 0.23.1 ahead of stable 0.23.0 (#8300)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com> |
||
|
|
0ffafb3d76 |
fix(chat): keep live strategy continuations in their original task (#8195)
* fix(chat): keep live strategy continuations in their original task * fix(chat): re-derive the task position when a reattach follows a successor Run `reattachDaemonRun`'s `onRunCreated` re-points the row at the daemon-created successor Run but spread the predecessor message, so a page reloaded mid-task kept the predecessor's `strategyTaskRunIndex` on a row that is now streaming a different Run. That position is what `foldStrategyTaskTurns`, the fork boundary and the successor-absorption rule read. Resolve it through the same `strategyTaskRunIndex(...)` helper the live send path uses, so a projection that does not name this Run exactly once yields `undefined` instead of the predecessor's index. --------- Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
792480fe21 |
fix(daemon): bound stored run events and keep crashed runs from acting on truncated prompts (#8170)
* test(daemon): red specs for unbounded persisted run events
Incident 2026-09-14: a cursor-agent conversation (~169 turns) stored every
unrecognised stdout line verbatim as a `raw` event. One `editToolCall`
completion repeats the edited file twice (~1.2 MB) and the `user` line echoes
the whole composed prompt, so each turn stored ~1.4 MB that nothing renders.
The renderer and daemon ran out of memory, and `GET .../messages` failed with
`RangeError: Invalid string length`.
These specs pin the invariants the fix must establish, against 0.22.x-shaped
rows (seeded straight into the daemon's tables) and the real parser +
persistence path:
- I1: every persisted run event fits a 64 KiB budget; meaningful events
(status, done_key, text, usage) are stored unchanged; nothing is dropped;
live `raw` lines are bounded too.
- I2: GET messages returns a bounded transcript; the PUT sibling-Run guard and
the project run-status projection keep their verdicts without any database
read handing a run's event log to JS.
- I3: a background, idempotent, one-row-per-transaction heal bounds existing
rows, skips active runs, and is safe to interrupt.
* fix(daemon): bound persisted run events and heal oversized rows
A persisted run event never carries an unbounded payload (I1):
`runtimes/run-event-payload-budget.ts` bounds every stored event to 64 KiB of
JSON. An oversized event keeps its kind, identity fields and count; its
payload is shortened deterministically to a 16 KiB head+tail with an inline
`[open-design: ...]` marker (bytes cut, original size, digest) and a
`truncated: { originalBytes }` field. cursor-agent `tool_call` lines drop their
full-file copies (`beforeFullFileContent`, `afterFullFileContent`, read
`content`) first so the retained JSON stays valid and meaningful; Write/Edit
rows keep their `+N -M` via `od_diff_stat`; text/thinking are never cut.
Applied at the json-event-stream source (live SSE + run buffer), at the
SSE->persisted conversion, and at every events_json/batch write.
Loading a conversation never materializes unbounded data (I2): GET messages
streams rows and bounds legacy rows on read; the PUT sibling-Run guard reads
each sibling's first done_key in SQLite (`listSiblingRunDoneKeys`); the
project run-status projection picks each project's latest run in SQLite and
reads only the events `eventsEndedWithUnfinishedWork` can see.
Existing oversized rows are healed once (I3):
`storage/message-event-payload-heal.ts` runs after the daemon is listening,
one row per transaction, streaming each row's events via json_each, skipping
queued/running runs, and records completion in `daemon_maintenance_passes`.
Contracts gain the optional `AgentEventPayloadTruncation` field on raw,
tool_use and tool_result events (and the SSE raw payload).
* fix(daemon): keep listMessages working on all()-only DB adapters
Streaming rows with `iterate()` is what keeps a pre-heal conversation read
from holding every row's legacy event log at once, but some integration
boundaries hand listMessages a lightweight adapter that models only
`prepare().all()` (as `hasMessageEventBatchStorage` already tolerates). Fall
back to `all()` there. Also prepare the run-status completeness statement once
per listing instead of once per project.
* test(daemon): red spec for unbounded live raw lines from every parser
claude-stream, copilot-stream and qoder-stream hand unrecognised stdout lines
to the SSE fan-out verbatim; only json-event-stream bounded them at the
source. Pin that every parser's live raw event fits the 64 KiB per-event
budget on both the newline and flush() paths, and that a line that fits is
passed through unchanged.
* fix(daemon): bound raw lines at the source in every stream parser
Apply boundedRawAgentEvent to the raw emitters of claude-stream,
copilot-stream and qoder-stream, as json-event-stream already does, so the
live SSE stream and the in-memory run buffer carry the same bounded line the
transcript stores. Live tool_use/tool_result stay unbounded on purpose:
Langfuse tool spans and artifact detection read them from the live run.
* fix(daemon): deliver agent prompts intact and reap leftover agents after a daemon crash
Plain-text-stdin runtimes (cursor-agent, codex, opencode, copilot, qwen,
amp, ...) received the composed prompt through an async pipe write. When
the daemon died right after spawn, only the kernel pipe buffer (64 KiB)
reached an agent that reads stdin late, and the detached agent (running
with --force) kept going on the truncated prompt, whose last visible
request was the conversation's oldest one.
- Deliver the prompt as a file-backed stdin: a 0600 file under the run
directory, passed as the child's stdin fd at spawn and deleted right
after. If it cannot be written, the run fails before any agent exists.
Claude's stream-json pipe is unchanged.
- Record each spawned agent (pid, process group, spawn window, daemon
identity) in its run directory. On the next daemon start, terminate
leftovers whose recording daemon is gone and whose identity still
matches, and sweep stale prompt files. A failed record write fails the
spawn instead of leaving an agent nothing can reap.
- Add process-identity helpers to @open-design/platform.
By design a leftover agent keeps running until the next daemon start;
the interrupted run reads back failed / DAEMON_RESTARTED.
Red first: with main's behaviour a late-reading agent received 65,536
bytes and its process group survived a restart. The agent-process specs
and e2e/tests/dialog/leftover-agent-reap.test.ts go red there and are
green on this change.
|
||
|
|
46be6eacc7 |
fix(chat): preserve planning-only intent after clarification (#8172)
* fix(daemon): preserve plan-only intent across task clarification * test(daemon): preserve production intent in strategy fixtures * test(e2e): bound shared logo lookup during workspace restoration --------- Co-authored-by: rebel-nexu-zero[bot] <rebel-nexu-zero[bot]@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
8fb94d28eb |
feat(cms): 统一客户端活动点位接入与验收修复 (#7986)
* feat(cms): add verified desktop touchpoint hosts and Test acceptance * fix(cms): keep Test modal scrim above app chrome * fix(cms): load host styles and support hover packages * fix(cms): report mounted host compatibility for restoration * fix(cms): parse Vela actions in shared manifests without executing them * fix(cms): preserve authorized actions and correct modal and hover behavior (#8003) * fix(cms): harden touchpoint host interactions * fix(cms): preserve modal actions and verify effective close controls * test(cms): cover hover exit and above-anchor placement * fix(cms): hide client test controls by default * fix(cms): persist automatic campaign impressions per account and device * fix(web): remove host campaign close button * fix(cms): suppress campaign modals during onboarding * fix(cms): unify test and production campaign lifecycle (#8118) * fix(cms): enable safe campaign navigation in test and production * fix(cms): enforce server-authorized touchpoint lifetimes * fix(cms): refresh production touchpoints when client language changes * fix(cms): stabilize hover refresh and synchronize Test locale * fix(cms): preserve campaigns on focus and remember Test dismissal * fix(cms): discover Test deployments without reloading * fix(cms): keep granted touchpoint display authority through transport failures A refresh that times out or fails in transport is not a withdrawal, but the shared lifecycle treated every failure the same way: `status = "error"` then `revoke()`, which tore down a lease the server had already granted and was still inside its own deadline. One slow poll therefore blanked a running campaign until the next 30s tick, and a second failure could not restore it because the value had been dropped. `abandonAttempt` now names the invariant: cancel the attempt, keep the lease, and let `armExpiry` retire it at the deadline the server actually granted. The lease length is the bound, so no retry budget is needed — Test's sixty-second contract survives exactly one missed poll, and Production survives as many as its authorization covers. Three cases still clear, and one of them is new: - the failure carries the server's own withdrawal. A 410 is authoritative even when its receipt body is unreadable, so `ProductionTouchpointLoadError` now marks that one case and the lifecycle honours it. Without this, a malformed 410 would have been ridden out as if it were transport noise. - nothing is mounted, or the lease was already fenced by `wake`. Page recovery has no evidence the activity is still live, so it stays withdrawn. - the lease has run out on its own. This does not fix OPEND-3237, which is a new activity failing its FIRST load: there is no prior lease to keep and the display stays empty. It fixes the adjacent class where a mounted activity is erased by a transient refresh. * fix(cms): keep a failed refresh recoverable inside its own poll cycle Three changes to how one refresh cycle behaves when it does not go cleanly. None of them fixes OPEND-3237, whose first-load failure is still unexplained; they remove the ways a working campaign is lost to an ordinary hiccup. Bounded retries. A failed attempt used to get its next chance from the fixed 30s tick. For the sixty-second lease the Test runtime is granted, that chance lands exactly when the lease expires, so a single failure put display on the edge of going blank with nothing able to recover it. Retries at 1s and 3s give a fast failure — transport error, 5xx, DNS — several chances while the lease is still comfortably alive. A retry must finish inside the cycle that spawned it. `refresh` declines to start while a request is in flight, so a chain that ran past the next tick would not merely be late: it would swallow that tick entirely. Worst case was 15+1+15+3+15 = 49s against a 30s interval. Scheduling now requires room for the retry AND its full budget, which is also what makes "retries cover fast failures" true in the code rather than only in a comment — an attempt that burned the whole budget leaves no room by construction and is never retried. The retry budget resets per cycle, not per success: otherwise the first cycle to exhaust its retries would leave every later cycle with none. Request budget 10s -> 15s. One refresh fetches a context and every enabled placement, so the budget covers a round, not a request. A real round of 11.5s whose placements all succeeded was being abandoned by the ten-second budget. It stays well under the 30s interval on purpose. Forward `accept-encoding` through the touchpoint runtime proxy. Decisions carry base64 content and run to megabytes, and the proxy built its upstream headers from scratch, dropping the caller's preference — every refresh pulled the payload uncompressed, measured at 383KB against 214KB for the same decision. Because the body is piped through verbatim, upstream's `content-encoding` has to be handed back with it, or the caller decodes gzip as JSON; both halves land together. Verified end to end through daemon and web: 2,149,685 -> 1,606,678 bytes on the wire, byte-identical after decode. An authoritative withdrawal is never retried: a 410 that took a campaign off the screen must not put it back. * fix(cms): present each Test campaign modal once per account and device Test modal presentations now share the production touchpoint-displayed:v1 impression: recorded once visible, read before any new automatic opening, and persisted across reload, remount, and client restart. * fix(cms): give the Test control readout an explicit ladder weight |
||
|
|
b6b0022fbf | fix(amr): continue committed compaction work without replay (#7958) | ||
|
+25 |
7245f543a7 |
feat(web): land the Home entry refresh (feat/home-entry-refresh → main) (#8208)
* feat(web): refresh the Home hero and composer (#7731) * feat(web): add the SiriOrb run indicator Ports the Siri Orb (SmoothUI, MIT) from #7635 as a standalone component: a rotating neon sphere with idle / thinking / streaming speeds, drawn entirely in its CSS Module. Unused by this PR; the rail run-status icon (PR-A of OPEND-2553) consumes it. * feat(web): vendor the composer running orb Adds the thinking-orbs 0.3.1 'solving' state at its 20px preset (MIT, attributed in the file) as a local component instead of a new runtime dependency: the composer needs one of the package's nine animations, pinned to the dark palette, and the transcription keeps the geometry, preset scaling, painter and frame loop of the published build so the canvas output is identical. Refs OPEND-2553 / #7635. * feat(web): add the rotating Home headline i18n keys homeHero.title now carries the {word} slot in every locale together with homeHero.titleWords (the comma-separated nouns the headline cycles), homeHero.subtitle and the attachment band's paging labels. Every locale owns its own sentence and noun list so the placeholder-parity check stays green (#7635 only rewrote en / zh-CN / zh-TW). Refs OPEND-2553. * test(web): re-pin the Home hero and composer specs to the refreshed surfaces Follows #7635's hero/composer contract: type row instead of the template dropdown, display+clear type pill, no Home mode picker, no resource submenus in the '+' menu, references in the working-directory row, lead-chip truncation, the 116px hero offset, the 220px execution-chip cap, the flat app wash and the stable send arrow. Carries over the Demo's new specs (dock collapse, Backspace-at-start, modelVersionLabel, icon-only design-system pill) and drops the ones for removed surfaces. Refs OPEND-2553. * test(e2e): follow the Home type row and composer menu Ports #7635's Playwright helper (homeTypeRow / clearHomeTemplate / pickHomeTemplate) and the home-hero-rail, entry-chrome-flows, project-management-flows, visual-navigation and entry-topbar changes that reach the composer through the type row, the working-directory group and the trimmed '+' menu. Refs OPEND-2553. * feat(web): refresh the Home hero and composer Replicates the Home hero + composer subset of #7635 on current main (OPEND-2553, PR-B): plain headline with the rotating noun and subtitle, a typeless cold Home whose type row (3 + 2 behind More) retires into the composer's display+clear pill, upload disc + design-system palette + type pill in the foot row, the supplied send mark and border beam, two-line staged-file chips in a paging band, the lead template chip with Backspace-at-start, references and linked checkouts in the working-directory row (one slot), the trimmed '+' menu without resource submenus or the mode picker, poster rows for examples, the model chip's brand mark and version-only label, the running orb + stop square in the project composer, and the flat app wash. HomeView gains the page/dock variant plumbing and drops the default type seed and the AppWashKineticGrid mount; the recent-projects grid stays for PR-C. Adds task_chip_clear to the composer click contract. * test(e2e): follow the Home composer and type-row refresh Migrates the Playwright specs that named surfaces #7635 removed or moved: the composer session-mode picker (every turn now carries the conversation's stored design mode, and design turns carry no message chip), the Home examples loading shell (a cold Home is typeless and shows only the type row while the catalogue loads), the fixed type row (three inline pills plus two behind More; every other create type reaches the hero through the cross-surface hand-off, which the shared pickHomeTemplate helper now drives), the working-directory row's single slot for references and linked checkouts, the '+' menu's working-dir group, the lead chip's eight-character title, the 32px send disc, and the flat app wash. The Home recent-projects specs keep main's shape (PR-C owns that move). Refs OPEND-2553. * test(e2e): repair the visual captures the composer refresh broke The projects captures clicked the rail's drafts item, which only renders with a workspace context the visual fixture never has; Home already shows the recent-projects strip, so the click is dropped. The integrations capture reached its page by direct load, whose default tab is MCP and whose cold mount outlasts a bare expect; it now clicks the connectors tab like the MCP capture does. The BYOK switcher capture expected the link glyph, but the chip leads with the selected model's brand mark and only falls back to the glyph for a vendor without one. OPEND-2553 (PR-B). * feat(web): add recent projects and run status to the entry rail (#7730) * feat(contracts,daemon): report awaiting-input projects on the runs list `ChatRunStatus` cannot express "waiting on the user": the run that asked a `<question-form>` reports `succeeded` and exits while the project stays blocked, so a client folding `GET /api/runs` into a per-project status shows such a project as finished. Ship `awaitingInputProjectIds` beside the runs, composed from the same `listProjectsAwaitingInput` read `GET /api/projects` uses and intersected with the projects the returned runs already reveal, so the field can never widen what a caller may see. Ported 1:1 from upstream #7635 for OPEND-2553 (PR-A). * feat(web): derive per-project run status from the runs feed `Project.status` only arrives on the unscoped `GET /api/projects`, so a workspace-scoped session cannot read it. Add the pure fold (`foldRunsToProjectStatuses`) that mirrors the daemon's composition, the per-project `listRunsForProject` read plus `useProjectRunStatuses` hook that keeps it live off `RUNS_CHANGED_EVENT`, the one glyph per status (`ProjectRunStatusIcon` over the SiriOrb), and the two conversation reducers (`latestUserPromptText`, `runProgressSteps`) the Design Files empty state will read. No visual change on its own. Ported 1:1 from upstream #7635 for OPEND-2553 (PR-A). * feat(web): list recent projects in the entry nav rail Adds the 最近浏览过 disclosure under 插件 in the signed-in rail: the head of the same recent catalog 全部项目 shows, newest first, capped at eight rows. Each row leads with the project's live run status (running / needs input / completed / failed) from the runs feed, opens through the shell's pull-first opener, floats a cover preview beside the rail on hover, and carries a ⋮ menu with rename, whole-project export and a two-step delete. An acknowledged ✓ is spent by opening the project and re-raised by the next run. The Home page's recent-projects grid stays; this only adds the rail entry. Row geometry, easing and colours are pinned to the Demo (upstream #7635) by `tests/styles/entry-rail-recent.test.ts`. OPEND-2553 (PR-A). * fix(web): acknowledge the rail ✓ per finished run The 最近浏览过 rows spent a project's ✓ by remembering the PROJECT id and only forgot it after observing a live queued/running status. The section polls only while it is open, so a run that started and finished while it was collapsed was never observed: on re-expanding, the stale acknowledgement still matched and the new completion's ✓ never showed. (The Demo, #7635, carries the same defect; this deliberately diverges from it.) Acknowledge by run identity instead. `foldRunsToProjectRunSummaries` reports the newest terminal run's id beside each project's status (the status-only fold is derived from it and unchanged for its callers), `useProjectRunSummaries` exposes it live, and the rail stores `{ projectId: acknowledgedRunId }`: a ✓ is acknowledged for one specific finished run, and a newer finished run is a new notice. The reset-on-running effect is gone. A store in the old array shape reads as nothing acknowledged, so at worst one already-seen ✓ shows once more. Red spec: tests/components/EntryNavRail.recent-section.test.tsx "re-raises the ✓ for a newer finished run even when its running phase was never seen". OPEND-2553 (PR-A). * fix(web): close the Home hero/composer acceptance issues against #7731 (OPEND-2553 PR-B2) (#7769) * fix(web): keep the staged attachment band in sight as the prompt scrolls The scroller sat on .home-hero__prompt-flow, which is also the attachment band's parent, so scrolling a long prompt carried the band away with the text: an attachment the user had just staged left the screen while they were still writing about it. Move the scroll down to a new .home-hero__prompt-surface holding the prompt and its lead chip and nothing else. That element keeps the position: relative the out-of-flow chip needs as a containing block, so the chip still scrolls with the text it leads rather than pinning itself to the top of the box. Ported from nexu-io/open-design#7635 ( |
||
|
|
eca7c7ab98 |
fix(web): align ChatPanel failures with approved product copy (#8140)
* fix(web): align runtime error messages with approved product copy * test(chat): give retry deduplication scenario a Cloud owner * test(chat): use the supported Cloud model preference shape * test(chat): align CLI recovery scenarios with fixed Cloud actions * test(e2e): align Cloud error layout with fixed recovery actions * test(e2e): use ordinary Cloud failure for error-card layout * test(web): preserve ChatPane helpers and align recovery fixtures * test(e2e): resize error-card layout through the native separator * test(e2e): reject missing error-card action geometry --------- Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
55b3762afe |
feat(daemon): capture per-request request_id + token usage for claude_code runs (#4620)
* feat(daemon): capture per-request request_id + token usage for claude_code runs The claude-stream-json parser read every assistant message's `message.id` only to dedup streamed text/thinking and then dropped it, and never read `message.usage` at all — so a run with N model requests collapsed into a single run-level `result.usage` record with no request id. That left request-level cost/percentile analysis stuck at run-level (~0.9% coverage). Surface the per-request granularity the wire already carries: - claude-stream.ts emits a new `request_usage` event per assistant message, keyed by `message.id` (the provider `msg_…` request id), carrying the per-message input/output/cache tokens via a `perRequestUsageFrom` helper. - packages/contracts adds `request_usage` to both the live SSE `DaemonAgentPayload` and the `PersistedAgentEvent` union; server.ts maps the live event to its persisted form. - mocks/lib/format-claude.mjs now emits per-message `usage` summing to the run-level `result.usage`, so the capture path is verifiable by local replay without burning provider budget. The per-request token sum reconciles against the run-level `result.usage` aggregate — the correctness anchor. New daemon test plus regenerated claude golden cover both the synthetic stream and the mock-replay path. No change to run execution; only telemetry/contract surface is added. claude-stream- json only for now; other runtime families ship per-request shapes in follow-ups. Closes #4610 * feat(daemon): thread per-request usage through Langfuse + PostHog run telemetry Addresses review on #4620: the parser emitted the per-request records but they stopped at SSE/message persistence and never reached the telemetry sinks named in #4610's acceptance criteria, so PostHog/Langfuse still had no per-request request_id + token coverage. Wire the records through the existing run telemetry choke point: - run-analytics-observability.ts adds `scanRunEventsForPerRequestUsageAnalytics`, a pure scanner that collects the per-request records keyed by request_id and checks the reconciliation invariant (per-request token sum == run-level `result.usage`). Both sinks read this one source. - langfuse-bridge.ts `collectAgentEvents` exports one `agent-request-usage` Langfuse event per model request, keyed by the provider request id. - routes/runs.ts adds `run_finished` coverage properties (`request_usage_count`, per-request token sums, and `request_usage_reconciles_aggregate`) so the PostHog request-level surface lifts off the ~0.9% floor; contract fields added to RunFinishedProps. Test extended: a multi-request claude run produces telemetry records keyed by request_id whose token sum reconciles with `result.usage`, plus a non-reconciling guard. langfuse-bridge + run-analytics suites stay green. * fix(daemon): fold cache tokens into per-request usage reconciliation Addresses review on #4620: request_usage_reconciles_aggregate compared only input/output tokens, so a run whose prompt/completion tokens matched but whose cache creation/read tokens drifted still reported `true` — letting the new PostHog/Langfuse telemetry certify the #4610 invariant when part of `result.usage` had not reconciled. scanRunEventsForPerRequestUsageAnalytics now reads the aggregate `cache_creation_input_tokens` / `cache_read_input_tokens` and folds every field `result.usage` reported into the boolean (each compared only when the aggregate carries it, mirroring the input/output leniency). Added a fixture where cache tokens mismatch while input/output still match, so the flag stays false and this cannot regress. * feat(daemon): map request_usage to durable PersistedAgentEvent * feat(daemon): wire per-request usage rollup into run_finished analytics lifecycle * ci: trigger fresh validation run for approved changes * feat(daemon): preserve per-request usage in truncation-proof run side ledger |
||
|
|
d465086b2c |
fix(amr): align client links with Cloud dashboard routes (#8126)
* fix(amr): use canonical cloud dashboard URLs * test(e2e): align AMR recharge link assertion Generated-By: looper 0.13.1 (runner=fixer, agent=codex) * fix(amr): honor selected dashboard profile Generated-By: looper 0.13.1 (runner=fixer, agent=codex) |
||
|
|
954f304dd1 |
fix(chat): restore next steps for successful image deliveries (#8057)
Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
5a3f0c8923 |
fix(daemon): archive owned Codex threads after terminal completion (#8051)
* fix(daemon): archive owned Codex threads after terminal completion * fix(daemon): archive owned Codex threads after interrupted close --------- Co-authored-by: lefarcen <935902669@qq.com> |