Skip to content

fix(frontend): route / to setup or login instead of the landing page (#3909) - #2

Closed
now-ing wants to merge 173 commits into
mainfrom
fix/homepage-redirect-3909
Closed

fix(frontend): route / to setup or login instead of the landing page (#3909)#2
now-ing wants to merge 173 commits into
mainfrom
fix/homepage-redirect-3909

Conversation

@now-ing

@now-ing now-ing commented Aug 17, 2026

Copy link
Copy Markdown
Owner

test

DaoyuanLi2816 and others added 30 commits July 27, 2026 22:18
* feat(sandbox): add Tenki cloud sandbox provider

Adds deerflow.community.tenki, a SandboxProvider backed by Tenki cloud
microVMs, alongside the existing e2b_sandbox / boxlite / aio_sandbox
backends. Selected via `sandbox.use: deerflow.community.tenki:TenkiSandboxProvider`
(resolved by class path, so the change is purely additive).

The full Sandbox contract is implemented — execute_command plus
read/write/update/download_file and list_dir/glob/grep — with file ops run
as busybox-portable shell commands (cat / find / grep / chunked base64),
reusing deerflow.sandbox.search, mirroring e2b_sandbox and boxlite. Tenki's
SDK is synchronous, so unlike boxlite there is no event-loop bridge.

Tenki sandboxes run as an unprivileged user with /mnt root-owned, so the
/mnt/user-data virtual prefix is remapped under the writable home dir (like
e2b_sandbox); the provider also best-effort sudo-symlinks /mnt/user-data to
that home dir so agent shell commands using the literal path still work.
Sandboxes are pooled per (user, thread) with warm reclaim, a replica cap,
and an idle reaper via the shared WarmPoolLifecycleMixin. Transient
transport blips get one bounded retry; terminal session errors evict and
recreate.

Only the stable Tenki surface is used (create/terminate + exec/shell/fs) —
no volumes, snapshots, or template builds — so any stock base image works.
The tenki-sandbox SDK is an optional extra (deerflow-harness[tenki]) and is
imported lazily, so a default install and every other provider are
unaffected.

Tested: unit suite runs in CI without tenki-sandbox installed; a live
integration test and full-surface e2e were verified against real Tenki
sandboxes.

* fix(sandbox): remove unsafe auto-retry from Tenki exec

Pre-merge review caught that the transient-transport retry sat at the
universal _exec layer, so it retried every operation — execute_command and
base64 file-write chunks included. gRPC has no exactly-once guarantee: a
"socket closed" ack-drop after the server already ran the op means the retry
runs it twice, double-firing command side effects and duplicating a write
chunk mid-file (silent binary corruption on multi-chunk writes).

exec is not idempotent, so it must not be auto-retried. Reverts to the
boxlite/e2b behavior: a transient error surfaces to the caller (returned as
text by execute_command, raised by the file ops); a terminal session error
still evicts the sandbox so the next acquire rebuilds it. Verified live
end-to-end across 31 edge cases (empty/binary/unicode/chunk-boundary files,
shell-metachar content, error paths, list/glob/grep, warm-pool reclaim,
concurrency).

* fix(sandbox): address Tenki provider review feedback

- Use Tenki's native sandbox.fs API for all file transport (read_text,
  read_bytes, write_stream, mkdir) instead of cat/chunked-base64 over shell.
  Uploads stream in 1 MiB frames; append is read-modify-write because the
  write stream has no append mode (same approach as community/e2b_sandbox).
- download_file streams via fs.read_stream and enforces the 100 MB cap on
  bytes actually received, closing the TOCTOU window between the old
  wc -c size probe and the read.
- list_dir/glob/grep report paths back under /mnt/user-data instead of the
  sandbox-internal home dir, so results feed straight into the file APIs.
- Create with wait=False and await wait_ready() here: create(wait=True)
  raises with the session handle still inside the SDK, leaking a running
  microVM this provider could never terminate.
- Configure the sandbox lifetime (max_duration, default 4h) and expose
  sticky; without it Tenki reaps a reused thread's sandbox after ~30 min.
- close() terminates before marking the adapter closed and re-raises real
  failures, so a failed termination stays retryable instead of silently
  leaking a billed microVM; an already-gone session still counts as closed.
- Bump the optional extra to tenki-sandbox>=0.4.0 and commit backend/uv.lock.

* fix(sandbox): scope tenki grep() glob filter to its directory prefix

Mirrors bytedance#4168, which fixed the same defect in the E2B provider. The tenki
adapter reduced a directory-scoped pattern like "src/*.js" to its basename
before filtering, so the search silently broadened to every matching-extension
file in the tree. Post-filter grep's hits through path_matches() against the
path relative to the search root, the same way glob() already does, so both
agree on what a directory-scoped pattern means.

* fix(sandbox): address Tenki provider review — eviction, id width, write lock, grep -H

Four fixes from the upstream review:

download_file no longer swallows terminal transport errors. The broad
`except OSError: raise` re-raised ConnectionError/BrokenPipeError/EOFError
(all OSError subclasses that _is_terminal_failure treats as terminal) before
_note_failure ran, so a session that died mid-download was never evicted. Only
our own EFBIG size-cap now passes through without eviction.

Sandbox id widened from 32 to 64 bits (`[:8]` to `[:16]`), matching
community/e2b_sandbox. The warm pool is keyed by this id with no full-seed
fallback, so a collision could let one user reclaim another's parked sandbox on
a multi-tenant gateway.

_fs_op now holds the lock across the op, not just the fs lookup, so concurrent
calls on the same sandbox serialise over the SDK's shared connection. The
eviction callback runs after the lock is released to avoid a lock-order
deadlock with the provider. The append read-modify-write is serialised by a
dedicated _write_lock so two concurrent appends can't clobber each other.

grep passes -H so a search whose path resolves to a single file still prints
the filename; without it the file:line:text unpack dropped every match.

* fix(sandbox): address Tenki provider review round 2

- validate config `environment` at load time (_validate_extra_env) so a bad
  key fails fast instead of surfacing as an SDK error mid-command
- document the deliberate lock decision in download_file: the instance lock is
  dropped before streaming so a 100 MB download can't block every other tool;
  terminal transport errors still evict via _note_failure
- tighten the terminal-error comment to note ConnectionError/BrokenPipeError/
  EOFError are also treated terminal via isinstance
- document TenkiSandboxProvider in backend/AGENTS.md (provider detail, warm-pool
  destroy hook, community provider list)
- add a commented Tenki block to config.example.yaml for parity with AIO/BoxLite
- tests: config env validation, grep -F/case-sensitive flags, glob include_dirs,
  list_dir max_depth, bootstrap-failure warning branch

* fix(sandbox): make Tenki bootstrap non-interactive and time-bounded

The create-time bootstrap runs under the per-scope acquire lock, so a hang
would stall acquire for that scope indefinitely:
- use `sudo -n` so a password-requiring sudoers entry fails fast (swallowed by
  the existing `|| true`) instead of blocking on a tty password prompt
- pass a timeout to the bootstrap `remote.exec` so any other stall drops to the
  existing warning path rather than wedging acquire

Best-effort by design; the file APIs still work via the home remap on failure.

* test(sandbox): pin Tenki bootstrap timeout to its actual value

Assert bootstrap["timeout"] == _BOOTSTRAP_TIMEOUT instead of `is not None`, so
a regression to timeout=0 (treated as no timeout by some SDKs) or an unrelated
value is caught rather than passing a weaker non-None check.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
* fix: render Telegram replies as rich messages

* Исправление fallback Rich Messages в Telegram
…bytedance#4210)

* feat(channels): share inbound webhook dedupe across pods via Postgres (bytedance#4120)

* ci: run cross-pod inbound dedupe integration tests in CI

Expose the job Postgres service via DEDUPE_TEST_POSTGRES_URL so the integration tests (issue bytedance#4120) actually execute instead of silently skipping. Normalize the URL for asyncpg (postgresql:// -> +asyncpg, drop libpq-only sslmode) and await the now-async _is_duplicate_inbound in test_github_dispatcher.
* feat(memory): integrate FTS5 retrieval adapter

* deps: add jieba as default dependency for Chinese tokenization

Without jieba, FTS5 unicode61 tokenizer treats entire Chinese sentences
as single tokens, making single-character or sub-phrase searches
impossible (e.g. '吃' or '油泼面' returns 0 hits against
'用户喜欢吃油泼面'). jieba segments Chinese text into meaningful tokens
before indexing.

* fix(memory): avoid treating hyphens as FTS5 operators

* feat(memory): make Chinese tokenization optional

* fix(memory): warm every requested retrieval scope

* fix(memory): close retrieval resources on shutdown

* fix(memory): close backend when shutdown flush fails

* fix(memory): recreate corrupt retrieval index

* fix(memory): tolerate partial retrieval rebuilds

* fix(memory): warm retrieval index in background

* fix(memory): preserve shutdown flush budget

* fix(memory): stop retrying partial lazy rebuilds

* fix(memory): close retrieval through storage

* refactor(memory): simplify retrieval scope limit

* docs(memory): clarify retrieval shutdown lifecycle

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
The commented Crawl4AI web_fetch example still describes the pre-0.9 server:
it says JWT auth is off by default, marks `token:` as only needed "if the
server has JWT auth enabled", and pins 0.8.6 in the docker run line.

Crawl4AI 0.9.0 made the Docker API server secure-by-default. Auth is on for
every request except GET /health, and a server started without
CRAWL4AI_API_TOKEN binds 127.0.0.1 only. Following the current example against
a current image therefore yields either HTTP 401 on every fetch, or a server a
containerised DeerFlow cannot reach -- with no hint that auth is the cause.

0.8.6 is also worth moving off: 0.8.7 fixed two pre-auth RCEs (CVSS 9.8), and
0.8.8/0.8.9 closed SSRF gaps in the same server.

No code change is needed -- Crawl4AiClient already sends
`Authorization: Bearer <token>` whenever `token` is set, so this is purely
the example catching up with the upstream server. Comment-only, so
config_version is unchanged.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…bytedance#4519)

`browserless` reads `cfg["timeout_s"]`, while its sibling web providers
`crawl4ai` and `jina_ai` read `cfg["timeout"]`. Tool configs allow extra
fields, so the unrecognised spelling is dropped without a diagnostic: someone
adapting one provider's config snippet for another silently gets the 30s
default instead of the timeout they set. (Observed in the other direction, on a
deployment whose crawl4ai entry carried `timeout_s`.)

Accept both keys, preferring the documented `timeout_s` when both are present.

While adding coverage, two pre-existing bugs in the same three lines surfaced,
both already guarded in crawl4ai/jina_ai but not here:

- `timeout_s: "30s"` (or any non-numeric string) raised ValueError out of
  `float(raw)` during tool construction rather than falling back.
- `timeout_s: off` -- YAML parses that as `False`, and `float(False)` is
  `0.0`, so every request timed out immediately against a healthy server.

`_coerce_timeout` now mirrors the sibling providers: booleans and unparsable
strings fall back to the default, with a warning for the string case.

Tests: five cases in tests/test_browserless_client.py covering both keys, the
precedence order, and both coercion bugs. Verified red before the fix (3 of 5
fail) and green after.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(frontend): preserve message order during long runs

* test(frontend): fix history pagination regression mock

* fix(frontend): validate thread history sequences
)

* fix(frontend): render citation links from React children

* fix(frontend): handle nested citation link text
…4539)

Renaming a conversation and then editing one of its turns reverts the
title to whatever it was before that turn ran, so the user's own name for
the thread is silently replaced by an older automatically generated one.

Edit replay resumes from the checkpoint before the edited turn, and that
checkpoint predates the rename. Regenerate already guards against exactly
this rollback by replaying the current title as graph input; the edit
replay path was added later and did not carry the guard over.

Replay the title the same way, but only when the replay base already has
one. An untitled base belongs to a thread the title middleware has not
named yet — pinning the current title there would keep a name generated
from the prompt this edit just replaced, and stop the middleware from
naming the rewritten turn.
* fix(frontend): sync streaming list markers with content

* fix(frontend): preserve streaming ordered-list counters
* feat(models): support cumulative vLLM stream usage

* fix(models): preserve active cumulative usage streams
…ce#4448)

* docs: design run metadata secret admission

* docs: refine run metadata secret boundaries

* docs: plan run metadata secret fix

* fix(security): centralize legacy run metadata policy

* fix(security): reject secrets at run admission

* fix(security): hide legacy secrets from history APIs

* docs(security): migrate MCP credentials to secret context

* fix(security): redact legacy runnable config metadata

* fix(security): reject legacy config metadata credentials

* fix(security): hide legacy secrets from run kwargs

* docs(security): clarify config redaction boundary

* docs: keep issue 4416 planning local
* docs(runtime): design cross-worker cancellation

* fix(runtime): cancel runs across gateway workers

* fix(runtime): harden cross-worker cancellation races

让取消请求与 owner 终态写入通过持久化 CAS 决定先后,保证首次取消 action 在不同 worker 路由下保持一致。\n\n将 heartbeat 收敛为续租后仅发送本地中止信号,并补齐完成竞态与路由重试的回归用例。

* docs(runtime): drop implementation plan from PR

移除仅用于实现过程的跨 worker 取消设计记录,保留 README 和 backend/AGENTS.md 中面向最终行为的文档。

* fix(runtime): preserve local cancel fallback

* test(runtime): adapt worker run manager fakes

* docs(runtime): fix run cancel migration registry

---------

Co-authored-by: MiaoRuidx <12540796+MiaoRuidx@users.noreply.github.com>
* fix frontend streaming step flicker

* fix frontend post-tool streaming text
…nce#4534)

Editing the only turn of a thread reran the original prompt: the model
answered the question the edit was replacing while the UI showed the
edited text, and the edit vanished on reload.

The replay-base lookup decided whether a checkpoint predates the target
user message by message id alone. DynamicContextMiddleware re-keys the
first user turn to `{id}__user` mid-run, so every checkpoint written
before it holds the same prompt under an id the lookup cannot match. The
scan walked past those and anchored inside the run that produced the
turn — a checkpoint that still contains the original prompt and owns the
injection node's pending writes, which the replay then re-added after the
edited message.

Require the replay base to be a settled checkpoint (no pending tasks) in
both the lineage walk and the chronological fallback. That rule is
middleware agnostic: the first turn now anchors on the thread's empty
initial checkpoint and later turns on the previous run's tail, which also
drops the existing reliance on LangGraph discarding a stale `__start__`
write.

Edit replay additionally passes `head_checkpoint` so it resolves its base
lineage-first like regenerate does, and a replayed user message is
restored to its pre-swap id: replaying `{id}__user` into a state that has
no reminder yet makes the middleware treat the turn as already injected
and silently drops its date and memory block.

Frontend: a prepared replay masks the turn it supersedes, so the
optimistic-message baseline is taken from the post-mask human count. The
pre-mask count can never be exceeded when the replay puts exactly one
human message back, and on the first turn the runtime re-keys the
replacement message so identity comparison cannot stand in for the count.

Fixes bytedance#4531
* feat(frontend): allow chat replies during clarification

* fix(frontend): unlock input polish during clarification

Remove hasOpenHumanInputCard from inputPolishDisabled so the polish
button stays available when a clarification card is open, matching
the composer unlock behavior. Clean up the now-unused useMemo and
import.
bytedance#4501)

* feat(lark): sidecar credential broker for sandbox lark-cli (Pattern B)

Removes the plaintext Lark credential mounts (appSecret + OAuth tokens)
from the sandbox container. A long-running broker sidecar owns lark-cli
and the per-user config/data dirs and serves the command surface over
Pod loopback; the sandbox gets only a forwarding shim on PATH, so the
raw credential files never exist in the sandbox filesystem.

- lark_broker.py: stdlib-only loopback broker (argv passthrough with
  shell=False, server-injected credential env, bounded I/O) + shim
  script constant + install-shim mode.
- docker/lark-cli-broker: init(install-shim) + serve image.
- provisioner: LARK_CLI_BROKER_IMAGE + provision_lark_cli_broker →
  shim init container + lark-cli-broker sidecar (config/data mounted
  sidecar-only); credentials dropped from the sandbox container;
  /api/capabilities reports lark_cli_broker_image. Broker supersedes
  the Pattern A init-container binary when both are configured.
- gateway: lark_cli_env_overlay(broker=True) omits config/data env;
  sandbox_lark_broker_active() TTL-cached mode resolver; broker added
  to sandbox_runtime_mode / readiness and the settings UI.

Opt-in and off by default (empty LARK_CLI_BROKER_IMAGE ⇒ no change).

Closes bytedance#4338

* fix(lark): address Pattern B broker review findings (bytedance#4501)

Follow-up to the sidecar credential broker addressing the PR bytedance#4501 review:

- shim: split the on-PATH lark-cli into a /bin/sh launcher + Python shim body
  so broker mode fails loudly (exit 127, actionable message) instead of ENOEXEC
  when the sandbox image ships no python3; interpreter pinnable via
  DEERFLOW_LARK_BROKER_PYTHON. Launcher bakes in the shim's absolute path since
  $0 is the bare command name when run off PATH.
- broker: drop the dead cwd payload field (broker can't see the sandbox FS) and
  document the command-surface-only / no-file-IO limitation.
- broker: return a structured 500 JSON on unexpected exec errors so the shim
  gets a meaningful message, not an opaque transport failure; set a handler
  socket timeout to bound slow/stuck connections.
- broker: add an opt-in DEERFLOW_LARK_BROKER_DENY_SUBCOMMANDS denylist that
  refuses secret-dumping subcommands before spawning the binary, forwarded from
  the provisioner sidecar.
- gateway: tighten the per-bash-call broker probe timeout (1.5s) and cache
  negatives longer (300s) so non-broker remote-provisioner users don't pay a
  latency hit; guard the mode cache with a lock; drop the dead
  _probe_provisioner_lark_cli_init_image wrapper.
- docs: remove the broken design-doc link from the broker README.

Adds tests for launcher python resolution, cwd omission, denylist enforcement,
500-on-error, hot-path probe timeout + negative caching, and provisioner
denylist-env wiring.
…ytedance#4538)

* fix(runtime): honor LangGraph Server identity for user-scoped data

* fix(runtime): scope custom agent SOUL by resolved user
…ce#4516)

* feat(checkpoint): make delta snapshot_frequency configurable

* fix(config): carry legacy checkpoint_delta_snapshot_frequency with warning

Addresses review on bytedance#4516: the rename from the flat
database.checkpoint_delta_snapshot_frequency key to nested
database.checkpoint_delta.snapshot_frequency silently dropped the old
value (pydantic extra="ignore"). Add a before-validator that maps the
legacy key onto the nested one with a deprecation warning (nested key
wins when both are set), plus a CHANGELOG breaking-change note covering
the rename and the 1000 -> 10 default change.

* fix(checkpoint): validate frozen snapshot frequency
* feat(memory): add OpenViking HTTP backend

* fix(memory): harden OpenViking lifecycle

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
MeiSiristhebest and others added 17 commits August 15, 2026 15:40
* fix: restore standalone LangGraph Studio compatibility

* fix: secure standalone Studio assistant ownership

* fix: harden Studio provenance reconciliation

* fix: repair Studio persistence before runtime startup

* fix: harden standalone Studio compatibility
…k injection (bytedance#4667)

* fix(middleware): target the latest user message on first-turn fallback injection

When an earlier turn ends without any dynamic-context reminder — e.g.
the async abefore_agent degraded path times out and skips injection
(issue bytedance#3402's guard) — the next turn enters the first-injection branch
(last_date is None) on a history that already holds several turns.
That branch scanned from the start and attached the ID-swap to the
FIRST user message.  The swap's {id}__user copy is appended by
add_messages, so the stale first prompt moved to the tail of history,
ahead of the current question — and the model answered the old prompt
as if it were the current turn.

Scan from the end instead (matching the midnight-crossing branch) so
the reminder attaches to the latest user message and history order is
preserved.  Genuine first turns are unaffected: they have exactly one
message, which is both first and last.  The pre-existing
test_injects_only_into_first_human_message_not_later_ones case encoded
the buggy target selection and is updated to the corrected contract.

* refactor: rename first_idx to target_idx after reversed scan

The branch now scans from the end, so the local holds the LAST user
injection target; first_idx read misleadingly.  Match the
midnight-crossing branch's naming convention and clarify the log line
accordingly.  No behavior change.
* fix(sandbox): bound E2B mount uploads

* fix(sandbox): revalidate E2B mount files
* fix(frontend): authenticate remaining gateway reads

* fix(frontend): authenticate artifact reads

* fix(frontend): include status in model errors
Signed-off-by: KXH <shepherdlaurie238@gmail.com>
…orms (bytedance#4830)

* fix(skills): fail closed on drifted projection namespace on all platforms

* test(skills): add regression test simulating swallowed unlink on drifted namespace
…ytedance#4844)

* fix(frontend): preserve completed message actions during streaming

* fix(frontend): address streaming action review feedback

* fix(frontend): reuse settled stream snapshots
* fix: support Studio file-based app loading

* docs: clarify Studio loader invariant
* feat: integrate MiniMax Code as an ACP agent

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* feat(memory): add hybrid fact eviction policy

* refactor(memory): simplify confirmation count update

* fix(memory): clean up eviction audit metadata

* fix(memory): harden eviction cleanup boundaries

* fix(memory): address hybrid eviction review
…4783)

* fix(memory): validate Honcho timeout and character limits

* fix(memory): enforce HonchoConfig invariants
* fix(frontend): surface model loading failures

* refactor(frontend): reuse model error UI primitives

* fix(frontend): address model banner review feedback

* refactor(frontend): remove unused model fetch state
After `make docker-init && make docker-start`, visiting http://localhost:2026
served the marketing landing page, so a fresh local install looked like the
official website instead of the app — and first-boot users never reached the
admin setup flow the gateway tells them to visit.

The root page now checks the server-side auth state and redirects first-boot
installs to /setup, signed-in users to /workspace, and everyone else to
/login. The landing page itself moves to
`src/components/landing/landing-page.tsx` and is still rendered at `/` for
`NEXT_PUBLIC_STATIC_WEBSITE_ONLY=true` (static demo) builds, which have no
gateway session to inspect.

Fixes bytedance#3909

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.