Commit Graph
3745 Commits
Author SHA1 Message Date
罗高 229c8a04b1 fix(preview): refresh file snapshots after completed runs 2026-10-14 23:10:48 +08:00
7da9a8aaa2 chore(pack): update Vela CLI to 0.1.4 (#8728)
Co-authored-by: nettee <nettee.liu@gmail.com>
2026-10-14 09:35:11 +00:00
CaprikaandGitHub 3faaab0f20 fix(e2e): align packaged Codex fixture with recipe identity (#8727) 2026-10-14 09:11:23 +00:00
Ray XiandGitHub 81f1249fb9 fix(amr): switch to the catalog default when the selected model was delisted (#8602)
On AMR 0.24.1, 9 runs from 7 users (10/5 to 10/7) ended with "session/set_model
modelId is not available": the model the user had picked (deepseek-v4-flash,
claude-opus-4.8, claude-sonnet-5, gemini-3.1-pro-preview) had left the AMR
catalog. The daemon forwards such a model on purpose, because its cached catalog
can lag the live one, and vela's set_model is the authoritative check.

Act on that rejection instead of ending the turn: the daemon passes the catalog
default as fallbackModel, and the ACP session switches to it once when vela
rejects the selected model, then tells the user with a model_fallback status.
If vela rejects the fallback too, the turn fails as before.
2026-10-14 08:22:50 +00:00
e0b250663d fix(release): allow re-cutting an unchanged packaged version (#8725)
Co-authored-by: lefarcen <935902669@qq.com>
2026-10-14 07:53:13 +00:00
1343f374c6 docs: align README positioning and clarify service disclosures (#8718)
* docs: align README positioning and clarify service disclosures

* docs: restore shipped migration and import contracts

* docs: list task-kind values in condensed translations

---------

Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
2026-10-13 12:00:42 +00:00
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>
2026-10-13 11:14:14 +00:00
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>
2026-10-13 10:32:45 +00:00
Ray XiandGitHub f29b1d6a02 fix(web): re-measure marquee travel when the document direction changes (#8692)
MarqueeLabel read the computed direction only when its text changed or a box
resized. I18nProvider writes <html dir> in a passive effect, so on first mount
in Arabic and on an English -> Arabic switch the label kept the LTR sign and
slid the workspace name the wrong way. Watch the dir attribute as well.

Refs OPEND-3112
2026-10-13 08:20:24 +00:00
netteeandGitHub 9041ca60bc chore(release): bump main to 0.25.0 ahead of stable 0.24.1 (#8700) 2026-10-13 03:28:22 +00:00
Abhijit SinghandGitHub 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.
2026-10-13 02:34:39 +00:00
海洋饼干andGitHub 8d602a5265 docs: document headless BYOK runs over REST (#8622) 2026-10-13 02:34:28 +00:00
Gabriel HenriqueandGitHub 8e5bd8ec59 fix(packaged): propagate OD_JSON_IPC_TRACE to sidecars (#8539) 2026-10-13 02:33:53 +00:00
Ray XiandGitHub 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.
2026-10-12 10:36:02 +00:00
af48a420eb fix(web): keep empty Home content reachable in short windows (#8649)
* fix(web): keep empty Home content reachable in short windows

Use safe centering when the empty Home hero exceeds the scroll viewport, and let wheel input continue from the examples list to Home at its boundaries.

Add CLI and BYOK browser regression coverage for short windows, responsive boundaries, scrolling, and centering when content fits. Validated with the focused Playwright cases, pnpm guard, and pnpm typecheck.

* test(web): align Home acceptance with safe centering

Update the existing empty-Home style acceptance to expect safe center while preserving its padding checks. The accepted layout change keeps content reachable when the hero exceeds the viewport.

Verified the CI assertion fails before the test update and all 8 cases in acceptance-visual-fixes.test.ts pass afterward. Also passed pnpm guard, pnpm typecheck, focused TypeScript diagnostics, and git diff --check.

* test(web): cover safe Home scroll boundaries

---------

Co-authored-by: PerishCode <perishcode@gmail.com>
2026-10-12 09:32:52 +00:00
Ray XiandGitHub e5d19cbf5f fix(desktop): record why a run ended without a clean shutdown (#8571)
A desktop run that dies without a clean shutdown is reported as
desktop_unclean_exit, but the report could not tell a crash from the
machine shutting down, sleeping, or a quit whose teardown never finished.
In production 26 of 28 recent unclean exits behind AMR runs failed with
DAEMON_RESTARTED carried no exit evidence at all.

- session-lifecycle: the run's marker keeps the last 8 host events
  (suspend, resume, shutdown, session-end with its reasons,
  quit-requested), written synchronously so they land before the OS ends
  the process. Repeats of the same event within a second (one per window)
  are kept once. An unclean run carries its events into the next launch.
- observability: desktop_unclean_exit adds exit_hint (system_shutdown,
  user_logoff, closed_by_system, system_suspend, quit_incomplete or
  unknown), ended_suspended, quit_requested and the last host event.
  attachDesktopHostLifecycleRecorder wires powerMonitor suspend/resume/
  shutdown and the Windows window session-end event.
- main: the recorder is attached once the session marker exists, and a
  quit records quit-requested when its cleanup starts.
2026-10-12 09:20:08 +00:00
Ray XiandGitHub 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.
2026-10-12 07:34:30 +00:00
Ray XiandGitHub 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.
2026-10-12 06:27:40 +00:00
lefarcenandGitHub 39e8301764 fix(daemon): drive Antigravity through agy's stream-json stdin channel (Windows ENAMETOOLONG) (#8594)
* fix(daemon): deliver Antigravity prompt on stdin to avoid Windows ENAMETOOLONG

agy received the full composed OD prompt as the -p argument. On Windows
that routinely exceeds CreateProcess's 32,767-char command line, so the
spawn failed with ENAMETOOLONG before agy started and users saw a generic
"This task failed to run" (OPEND-3495).

Pass a short fixed pointer as the -p value and deliver the prompt on stdin,
which agy print mode reads (verified on agy 1.3.1).

* fix(daemon): drive Antigravity through agy's stream-json stdin channel

The previous attempt passed `-p "Follow the instructions provided on
stdin."` and wrote the prompt to stdin, but agy ignores stdin whenever a
prompt is given with `-p` (agy 1.1.1), so the model never received the
OpenDesign prompt. Earlier "successes" came from the model reading files
in its working directory on its own.

agy's documented headless stdin channel is
`--input-format stream-json --output-format stream-json`, which reads
one NDJSON `{"event":"user","message":{"content":...}}` message per
turn. The prompt now travels only through that frame, handed over as a
complete file-backed stdin at spawn, so argv no longer grows with the
prompt (fixes Windows `spawn ENAMETOOLONG`, OPEND-3495).

- add an `encodeStdinPrompt` runtime hook used by the chat-run spawn and
  the connection test
- parse agy stream-json events (text deltas, tool steps, terminal
  result, usage) in json-event-stream, falling back to the Gemini JSONL
  branch for frames without `event`
- drop the antigravity-only plain-stdout buffering, Gemini re-parse and
  log-file empty-output classification
- recognise agy's headless signed-out output as AGENT_AUTH_REQUIRED and
  keep the per-model quota guidance for `Individual quota reached`
- fixtures recorded from agy 1.3.1 plus the samples in agy's headless docs

* fix(web): show Antigravity quota guidance instead of the generic run-failure copy

The Antigravity RATE_LIMITED card had no message key, so it rendered
chat.runError.fallbackMessage ("This task failed to run. Please retry")
-- the exact sentence in the OPEND-3495 report -- with no hint that the
per-model quota was the cause. Give it its own copy (switch model in the
model menu or via agy /model) and the quotaExhausted title, in all 19
locales.

* Revert "fix(web): show Antigravity quota guidance instead of the generic run-failure copy"

This reverts commit 68a84c3ff3.

The Antigravity rate-limit card intentionally uses the generic approved
copy (#8140, product copy supplement rev 96): blank product rows get no
invented copy, and the P0 e2e 'antigravity rate limits keep
classification and use only the Cloud switch' pins that behavior.
2026-10-12 03:57:38 +00:00
Ray XiandGitHub c2a28e3cee fix(chat): clear the composer on submit instead of after the send gate settles (#8380)
* fix(chat): clear the composer on submit instead of after the send gate settles

The host paints the sent turn and flips to Working before the OpenDesign
Cloud balance preflight settles, but the composer only reset after onSend
resolved, so the sent prompt lingered in the input for the preflight round
trip. Snapshot the composer at submit, clear it immediately, and restore the
snapshot only when the host hands the send back ('restore-draft' or a
throw) and the user has not started typing a new prompt.

Refs OPEND-3392

* test(chat): type the deferred onSend mock so its call args typecheck

* fix(chat): skip restoring a handed-back send over any new composer content

Review follow-up: the restore guard only looked at editor text, so an
attachment, visual comment, skill, MCP server, connector, workspace
context, plugin or quote staged while the send was pending was
overwritten by the old snapshot. Mirror committed composer content in a
ref and restore only when nothing new appeared since reset().
2026-10-12 03:24:58 +00:00
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>
2026-10-10 02:47:36 +00:00
802708f6c9 feat(web): land sign-in on Hosted and pitch free credits across entry points (#8567)
* feat(web): land sign-in on Hosted and pitch free credits across entry points

- Onboarding: drop the "Choose your model source" step. Signing in from the
  welcome screen finishes onboarding on OpenDesign Hosted; Local AI and API Key
  stay reachable from the welcome screen. Hosted select_runtime now reports
  step `connect` instead of `model_source`.
- Welcome screen: the pending sign-in shows one quiet "Sign-in page didn't
  open? Reopen" line (after 5s, or at once when the browser failed to open)
  instead of a nested card with two cancels, and the primary button no longer
  moves between idle and pending states.
- Home rail card: "Get free credits" pitch with a sparkle mark; the pending
  state turns the call to action into "Signing in…" with a single cancel and
  the same delayed reopen line. Dark mode keeps the button visible.
- Settings → Local CLI: callout reuses the free-credits copy; selecting a
  signed-out OpenDesign row selects it in place and offers Sign up / Sign in
  (no bounce to onboarding), committing the CLI once a session exists. Both
  buttons share the solid pill and show "Signing in… | Cancel".
- Fix the selected CLI card flashing on hover, and make the Home @ picker
  tabs clickable where they overlap the desktop window's drag strip.

* fix(web): drop pending OpenDesign pick when DeepSeek Harness setup commits

Installing DeepSeek Harness from its setup dialog committed agentId without
clearing amrPendingSelect, so the OpenDesign card stayed highlighted and a
later sign-in overwrote the DeepSeek choice with 'amr'. Clear the pending
intent alongside that commit and cover the sequence with a regression test.

---------

Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
2026-10-09 11:45:47 +00:00
lefarcenandGitHub 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 "正在重试".
2026-10-09 10:00:10 +00:00
d9dd6fefa7 feat(release): add one-dispatch withdrawal for an unshipped cut (#8509)
* feat(release): add one-dispatch withdrawal for an unshipped cut

A release that was cut but will not ship had no undo. cut-release creates a
branch, a backport label, a Feishu card, and — through the prerelease build its
push triggers — moves prerelease/latest onto the new line. Abandoning that
version meant reversing four surfaces by hand, and the one that actually
matters is easy to forget: internal validators auto-update, so they sit on a
package nobody intends to release.

release-withdraw reverses each, in the order that leaves the least mess if a
later step fails: repoint prerelease/latest at an older published prerelease,
delete the label, delete the branch, post the matching withdrawal card. The
branch delete uses the release App token because the release/v* ruleset blocks
deletion and that App is one of its bypass actors — the same token that created
the branch.

It refuses more than it does. A version that reached stable is not an unshipped
cut, and the error names release-rollback instead. A branch carrying any commit
that is not on main and not from the release bots would lose work, so it stops.
Open PRs targeting the branch, or still carrying its label, stop it. The
prerelease pointer is read live rather than trusted from the input, and is only
moved when it actually belongs to the withdrawn version — at which point a
target becomes mandatory rather than silently skipped. dry_run defaults to true:
a withdrawal is never the urgent case.

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

* fix(release): make the withdrawal queue behind the pointer's other writer, and give the dry run teeth

Both findings from review.

Concurrency. `open-design-release-withdraw` was a group of one, so it serialised
this workflow against nothing, and the header comment claimed a guarantee it
never had. `release-prerelease.yml` is the only other writer of
`prerelease/latest` and holds `open-design-release-prerelease`; the withdrawal
now joins it.

The publish this races is the ordinary cut timeline, not a manual re-run: the cut
push and the preview-manifest push that follows each start
`notify-release-feishu` -> `release-prerelease`. Three failures came from the
unique group. Reading the pointer before an in-flight publish finishes resolves
`belongs=false`, so the repoint is skipped and the publish then installs the
withdrawn version after a green run. Interleaved writes mix `latest`, because
the publisher writes metadata first and feeds second while `applyLatestRollback`
writes feeds first and metadata last, and every PUT is unconditional. And a
publish landing *after* a repoint restores the abandoned pointer, because the
publisher's CAS refuses only backward moves.

The group is workflow-level and must stay that way, which is the part worth
keeping in mind: it is held through the branch delete, so a queued trailing
publish cannot enter the publisher until `release/vX.Y.Z` is gone and then fails
closed on a checkout of a deleted ref. A job-level group would release the lock
first and lose that. A P0 assertion pins the shared string against
release-prerelease — renaming the publisher's group now breaks a test rather
than the invariant — and pins the single workflow-level block.

`cut-release.yml` stays out of the group: it writes no R2 object of its own, and
what needed serialising was the publisher it triggers.

The branch also gets a compare-and-delete, re-reading the tip and refusing if it
has moved off the SHA that "nothing but cut commits" and "no open PRs" were
established against. Not a substitute for the lock, which is what fixes the
publish race — this covers the likelier window the lock does not see, a backport
landing while a human reads the dry run. GitHub's ref delete takes no
expected-SHA precondition, so the compare is explicit.

Dry run. `Setup pnpm`, `Install dependencies` and `Repoint prerelease latest`
were all gated on `!inputs.dry_run`, which removed the credentialed half of the
validation from the mode that exists to validate. `prerelease_to_version` only
had to be syntactically plausible: an absent, incompletely published or
feed-less target, or one below the prerelease launcher floor, passed the plan and
failed for the first time during the real run. It still fails before the label
and branch deletes, so nothing was destroyed by it — but the dry run is the path
this workflow tells the operator to trust, and `[P0] plans before it destroys`
required `!inputs.dry_run` on the repoint step, locking the skip in.

The three steps now run whenever the pointer needs moving and the planner takes
`RELEASE_DRY_RUN: ${{ inputs.dry_run }}`, matching how release-rollback already
wires it. "The dry run passed" now means the real run has nothing left to
discover, and the header and the step summary say so. The two deletions and the
Feishu card stay gated on the input.

Each of the five behaviours was mutation-checked: reverted at exactly one site
(three for the `if:` gate), exactly one test went red. Workflows re-linted with
the CI-pinned pair, actionlint 1.7.12 with shellcheck 0.11.0 on PATH, proven
wired by a probe workflow that reports SC2086.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-09 07:11:02 +00:00
lefarcenandGitHub 426386581b docs(chat): record the OPEND-2807 error-card ruling (#8271)
The error card has shipped with a fixed set of three actions since #8140,
but that ruling was never written down: `git grep -i 2807 -- specs docs`
returns nothing. Two documents still describe the world before it.

`chat-panel-decisions-sheet.md` still reads "稿子 2 颗, 产品 4 颗" and
points at `run-error-catalog.md:421-430` for "A/B 两案仍挂着等产品挑".
Neither is true: the catalog's three candidates were all overruled, and
the card has three actions in both environments. `run-error-catalog.md`
§6.ZB still carries that candidate table as live, and §6.T still frames
the mapping as a ranking of secondary actions that no longer exists.

This records the ruling from main's own behaviour, not from the proposal
that lost: the criteria are quoted from `showCloudRetry` and
`showCloudSwitchCta` as they exist today, and the three exceptions
(a host that cannot take the handoff, `git_bash_missing`, a zero balance)
are the ones main actually ships. Historical passages stay; each one now
carries a pointer to the outcome.

The comment above `withCloudSwitchCta` pointed readers at `ChatPane`'s
`errorActionVariant`, a symbol #8140 deleted. It now describes the two
branches that exist.

No behaviour change: every `.ts` line in this diff is a comment.

Refs OPEND-2807
2026-10-09 03:28:31 +00:00
0520fde34b fix(chat): retain live thinking when older history arrives (#8167)
* fix(chat): retain live thinking when older history arrives

* test(web): cover full replay through the actual recovery host

---------

Co-authored-by: lefarcen <935902669@qq.com>
2026-10-09 03:28:26 +00:00
Aron LeeandGitHub e38462fd50 fix daemon skill roots missing from the run-scoped external_directory allowlist (#8178)
resolveChatExtraAllowedDirs built the allowlist from SKILLS_DIR only, so a
skill installed under the daemon data dir or loaded from a bundled plugin
folder resolved to a root outside it. withSkillRootPreamble still advertises
that root as the absolute fallback path, so OpenCode asked for
external_directory approval on files OD itself pointed the agent at, and a
headless run reported the ask as a rejected tool call.

Pass the turn's active skill dirs so the allowlist is keyed on the roots
actually in play. Codex stays excluded: it treats allowlisted dirs as
writable and skill roots are read-only resources.
2026-10-08 08:48:37 +00:00
Marin StoyanovandGitHub 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.
2026-10-08 08:45:03 +00:00
PerishFireandGitHub 92fe7e87b1 feat(release): decouple stable Docker reconciliation (#8517) 2026-10-08 07:52:45 +00:00
Pratik RaiandGitHub f86adccf3a fix(web): reveal full model names on hover in model pickers (#8409)
* fix(web): reveal full model names on hover in model pickers

Long model ids in the model pickers ellipsise to a shared prefix (e.g.
openrouter/google/gemini-...), so rows become indistinguishable and no title
was available to confirm a choice. Add the full label as the hover title on
every model-selection surface named in the report:

- the shared SearchableModelSelect option rows and trigger;
- the compact home list in InlineModelSwitcher;
- the composer/sidebar AvatarMenu lists (daemon and BYOK).

A locked row keeps its upgrade reason as its title, and accessible names are
unchanged. Adds regression tests using two same-prefix long OpenRouter ids on
each surface.

Fixes #8407

* chore(ci): retrigger web workspace tests after flaky artifact-tab shard

The ProjectView.auto-open-turn-artifacts spec timed out on shard 2/2; it
passes locally and is unrelated to the model-picker title change.
2026-10-08 07:36:52 +00:00
33fd35e07b fix(packaged): treat Chromium ERR_NO_BUFFER_SPACE as local resource exhaustion (#8283)
Electron net.fetch reports ENOBUFS as net::ERR_NO_BUFFER_SPACE. The existing
guard only matched the POSIX name, so the proxy fell through to undici and
kept retrying on an already-exhausted T2 Intel Mac.

Co-authored-by: leoric <leoricchen@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-08 07:11:00 +00:00
matthew6sandGitHub 4771bb2f87 fix: remove duplicate web-clone chip in HOME_HERO_CHIPS (#7777)
* fix: remove duplicate web-clone chip in HOME_HERO_CHIPS

HOME_HERO_CHIPS defined two objects with id: 'web-clone'. They were identical
except the second lacked automaticDefault: true. A duplicate id collides as a
React key and makes findChip/orderedCreateChips lookups ambiguous, and the
second copy silently dropped the automatic-default binding. Remove the second
duplicate, keeping the canonical entry that carries automaticDefault: true.

* test(home-hero): assert HOME_HERO_CHIPS ids are unique

Existing findChip and rail tests only observe the first match for an id, so
they passed while two web-clone chips were present. Add a catalog-wide
uniqueness invariant that fails on a reused id, so a duplicate cannot silently
return with its React-key and lookup ambiguity.
2026-10-08 06:37:50 +00:00
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>
2026-10-08 05:55:15 +00:00
1bf9bf52f0 fix(projects): settle empty catalogs and expose list failures (#8443)
Co-authored-by: lefarcen <935902669@qq.com>
2026-10-08 05:28:22 +00:00
open-design-crew[bot]GitHubelifive555555open-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com>
3449fa6ffd feat(templates): add original Aster Atelier studio website (#8314)
* feat(templates): add original Aster Atelier studio website

* fix(templates): brighten Aster Atelier artwork and palette

* chore: ensure PR CI

---------

Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
Co-authored-by: open-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com>
2026-10-08 04:02:02 +00:00
LukasandGitHub c190f264e0 fix(daemon): keep pi's config-directory override across a config save (#8119)
`agentCliEnv` is validated against a per-agent allowlist on every save, and
pi exposed only `PI_BIN`. `PI_CODING_AGENT_DIR` — pi's own override for its
config directory, default `~/.pi/agent` — was therefore dropped on every save
cycle, leaving no way to isolate a pi config but a wrapper script that sets
the variable and is pointed at by `PI_BIN`.

This extends pi's contract by the one key rather than turning the allowlist
into a pass-through: `PI_CODING_AGENT_DIR` is the same kind of entry
`CLAUDE_CONFIG_DIR` and `CODEX_HOME` already are for their CLIs, and nothing
else about pi's environment exposure changes.

Persistence is half the fix, so the tests follow the value the rest of the
way: one cell writes the config, reads it back and hands
`agentCliEnvForAgent`'s projection to `spawnEnvForAgent`, asserting the
variable arrives in the env pi is launched with; two more pin the launch env
directly, including `~` expansion. A neighbouring key that is NOT part of the
contract (`PI_CODING_AGENT_SESSION_DIR`) is asserted to still be dropped.
2026-10-08 04:01:33 +00:00
Ray XiandGitHub 3f83439dfe fix(chat): wait for successor run boundary before folding (OPEND-3463) (#8401) 2026-10-08 03:49:39 +00:00
53231d40b7 docs(readme): refresh product tour and 13 translations (#8534)
* docs(readme): refresh product tour and translations

* docs(readme): keep product tour media under blob limit

* docs(readme): introduce OpenDesign Go plan

---------

Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com>
2026-09-30 15:31:47 +00:00
5b19dfa435 fix(release): resolve the rollback tag from published metadata (#8508)
The first live dry run failed at the badge preflight: I had written the tag
as v<version>, but prepare-stable.ts publishes open-design-v<version>. A
hand-built prefix reads plausibly and is wrong, so take the tag from
.versionTag in the restored release's own immutable metadata — the value the
publisher recorded — probing the same prefix candidates rollback-latest.ts
probes, and hand it to the badge step as a step output.

The summary checklist had the same wrong shape; it now names the version
rather than constructing a tag. The regression test pins both: the tag must
come from .versionTag, and no step may rebuild one from the version input.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-29 03:41:33 +00:00
54139504d4 feat(release): add one-dispatch stable rollback (#8507)
* feat(release): add one-dispatch stable rollback

A stable release publishes its artifacts to an immutable
stable/versions/<version> prefix and then copies the pointer objects into
stable/latest. latest is therefore derived state, and retracting a release is
copying an older version prefix over it again — but until now nothing in the
repo could do that, so a bad stable release had no stop button.

Add `tools-release rollback-latest` plus a release-rollback workflow that
dispatches it. The CLI refuses unless the live pointer still serves the version
the operator typed as from_version, the target published completely (not
partial, not a dry run), every ready platform's manifest and updater feed exist
in the target prefix, and the current installer floor is satisfiable by the
target. It re-resolves control.launcher.version from live channel policy rather
than restoring the snapshot's, records rollback provenance, writes metadata.json
last so latest never advertises a version whose feeds it does not yet serve, and
verifies every object through both the origin and the public URL.

The workflow shares release-stable's publish concurrency group so a rollback
cannot overlap a publish, moves only the GitHub Release "Latest" badge (never a
release body), and ends with a checklist of what a rollback does not reach: the
download page's version-pinned links, the What's New card, the group
announcement, and above all the installs already on the retracted version —
the updater never offers a version that is not strictly newer than the running
one, so this stops a rollout and cannot recall one.

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

* fix(release): group the rollback summary fallback redirects

shellcheck SC2129 — CI runs actionlint with shellcheck wired in, which my
local actionlint could not see without it installed.

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

* fix(release): prove the restored tag has a release before writing

The badge move runs after the R2 write, so a missing GitHub Release for the
restored version would have failed the run with stable/latest already moved.
Check it up front instead.

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

* fix(release): pass the stable installer-floor policy into rollback

resolveLauncherVersionFloor() reads process env, and repository variables are
not process env. Without the handoff a rollback resolved no floor at all: it
would strip an active control.launcher.version from the restored metadata and
stop refusing targets below the current minimum — the safeguard the workflow
documents as enforced. Latent today only because no stable floor is set.

release-stable.yml passes the same pair to metadata publish and verify; mirror
it, with a topology assertion so a later edit cannot drop it silently. Also
stamp RELEASE_BRANCH/RELEASE_COMMIT so the rollback provenance is not blank.

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

* fix(release): require the restored release to be badge-eligible

gh release view succeeds for a draft or a prerelease, but GitHub will not put
the Latest badge on either — so the preflight passed and the edit failed with
the R2 rollback already done, which is the half-complete state the preflight
exists to prevent. Check isDraft and isPrerelease too, and cover the
eligibility guard in the workflow test alongside its ordering.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-29 03:21:12 +00:00
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>
2026-09-28 10:16:34 +00:00
6ed97944c8 fix(diagnostics): keep Windows rotated logs inside the consent boundary (OPEND-3497) (#8487)
* fix(diagnostics): keep Windows rotated logs inside the consent boundary (OPEND-3497)

The packaged launcher rotates latest.log to previous.log and immediately
re-creates latest.log. On Windows, file-system tunneling gives the new file
the creation time of the file that previously held the name, so the consent
fence saw a new inode with a pre-consent birthtime and omitted the whole
file as pre_consent_source on every launch after opting in. The rotated
previous.log was omitted too because its baseline was keyed by the old path.

Match baselined identities across paths so a rotated file keeps its offset,
and admit a new identity whose creation time equals a baselined one, which
only a re-created (tunneled) name produces. Unrelated old files are still
omitted, and no pre-consent bytes are backfilled.

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

* fix(diagnostics): scope tunneled log admission to its source path

Preserve original consent offsets and persist admitted identities for later rotations and daemon restarts. Cover unrelated birth-time collisions, rotation ordering, and consent resets.

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

* fix(diagnostics): remember tunneled logs at startup, keyed by identity

A session that recorded no incident never called apply(), so its tunneled
latest.log was not remembered as admitted. After the next launch rotated it
to previous.log, that incident-time log was omitted as pre_consent_source,
which is the relaunch-after-crash case. Admitted identities were also keyed
by path, so a newer latest.log replaced the entry of the file that had just
rotated to previous.log.

Observe the baseline sources once per daemon process, after the launcher has
rotated, and key admitted identities by inode and creation time, keeping only
those still present. Verified on Windows (NTFS, default tunneling) with real
rotations: with no incident in the middle session, previous.log is admitted
on the next launch.

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

* fix(diagnostics): report a missing source as not found, not pre-consent

The fence labelled a source that does not exist as pre_consent_source, which
reads as a consent decision. A packaged Windows bundle showed
logs/desktop/renderer.log this way although the file was never created.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:40:21 +00:00
PerishFireandGitHub b52fafd474 feat(ci): enforce reusable workload plans (#8393)
* feat(ci): enforce reusable workload plans

* fix(ci): skip incompatible convergence upgrades
2026-09-28 08:29:12 +00:00
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>
2026-09-28 06:56:27 +00:00
Ray XiandGitHub 512f302d40 fix(web): retire stale recent-project status polling (#8442) 2026-09-28 06:56:12 +00:00
1b47e60bd4 fix(cms): mount Test campaigns with the requested app locale (#8432)
Co-authored-by: lefarcen <935902669@qq.com>
2026-09-24 06:58:26 +00:00
3ede8daf11 test(daemon): answer Codex fixture probes without waiting for stdin (#8426)
Co-authored-by: lefarcen <935902669@qq.com>
2026-09-24 05:26:37 +00:00
61bda5c78c fix(ci): allow setup headroom for prerelease daemon tests (#8424)
Co-authored-by: lefarcen <935902669@qq.com>
2026-09-24 04:22:53 +00:00
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>
2026-09-24 03:22:31 +00:00
5885573546 feat(web): localize welcome screen and starter credit ribbon (#8351)
Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
2026-09-24 03:13:16 +00:00