674 Commits

Author SHA1 Message Date
Aari
645ca08f16
fix(scheduler): enqueue busy scheduled task runs (#4918)
* fix(frontend): clarify reuse-thread scheduling behavior

* fix(scheduler): enqueue overlapping scheduled runs

* fix(scheduler): preserve queue lease fencing

* fix(scheduler): close queue concurrency races

* fix(scheduler): harden queue timeout bookkeeping

* fix(scheduler): preserve manual failure schedule

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-08-24 15:38:32 +08:00
Aari
1aa813ddb3
feat: add managed subagents and delegation scopes (#4887)
* feat: manage and scope subagents

* fix: address subagent review feedback

* fix: address managed subagent review feedback

* fix: harden subagent settings semantics

* fix: harden managed subagent cache invalidation

* fix: reuse assembled lead agent inputs

* fix: migrate managed subagent definitions

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-08-24 11:04:23 +08:00
陈志谦
9232e1e6a9
fix: DeerFlow brand casing in disclaimer, What's New title, setup-sandbox .PHONY (#4970)
* fix: DeerFlow brand casing in disclaimer, 'What's New' title, setup-sandbox .PHONY

- en-US disclaimer said 'Deerflow'; every other user-facing string
  brands it 'DeerFlow' (rendered under the chat composer)
- landing section title 'Whats New in DeerFlow 2.0' -> 'What's New'
- setup-sandbox was the only Makefile target missing from .PHONY

* test: align disclaimer assertions with DeerFlow brand casing

The unit and e2e suites still expected the old "Deerflow" spelling in
inputBox.disclaimer; update both to the corrected locale string so the
i18n load test and the chat footer assertion pass again.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-08-24 10:58:48 +08:00
wutongyuonce
6688d01c8f
fix(frontend): avoid recreating browser stream after reconnect (#4951)
* fix(frontend): avoid recreating browser stream after reconnect

* test(frontend): cover reconnect delay reset
2026-08-24 10:29:57 +08:00
Battleplus
34ba2cdf38
fix(docs): update middleware guide to current AgentMiddleware API (#4968) 2026-08-24 09:15:00 +08:00
PiedPiper911
582fa20001
fix(artifacts): serve SHA-256 via ETag so preview/edit work on non-secure contexts (#4865)
* fix(artifacts): serve SHA-256 via ETag so preview/edit work on non-secure contexts

crypto.subtle is only available in secure contexts (HTTPS or localhost). The frontend fell back to it to compute an artifact's SHA-256 when the Gateway did not return one, which threw on http://<lan-ip>:<port> and broke both artifact preview and inline editing (issue #4864).

- Gateway now returns the real SHA-256 as an ETag header for inline text and active-content artifact responses (and skill-archive members).
- Frontend prefers the ETag and only computes a hash as a last resort, falling back gracefully (FNV-1a) instead of throwing when crypto.subtle is missing.

* fix(artifacts): address PR review feedback for #4864

- Cache SHA-256 digests by (path, mtime_ns, size) so the many small Range
  requests a browser issues while scrubbing/paginating a preview do not each
  re-hash a potentially huge artifact from scratch (performance).
- Gate inline editing on a real 64-hex revision: hasRevision requires
  sha256.length === 64, so the FNV-1a fallback on non-secure origins keeps
  preview working but no longer 422s on save (contract).
- Anchor and lowercase the ETag regex and accept the weak W/ prefix gzip
  emits, so uppercase hex and longer digests (sha-384/512) can't masquerade
  as sha-256.
- Cover the forced-download ETag on the backend and add frontend tests for
  weak-ETag parsing and the non-secure-context FNV fallback.

Feedback from reviewer willem-bd on PR #4865.

* style: fix ruff format and prettier issues

- test_artifacts_router.py: collapse two over-split client.get() calls to
  satisfy ruff format (line-length 240)
- loader.ts / artifact-file-detail.tsx / loader.test.ts: apply prettier
  formatting and restore LF line endings

* style: fix ruff format and prettier issues

- test_artifacts_router.py: collapse two over-split client.get() calls to
  satisfy ruff format (line-length 240)
- loader.ts / artifact-file-detail.tsx / loader.test.ts: apply prettier
  formatting and restore LF line endings

* style: fix ruff format and prettier issues

- test_artifacts_router.py: collapse two over-split client.get() calls to
  satisfy ruff format (line-length 240)
- loader.ts / artifact-file-detail.tsx / loader.test.ts: apply prettier
  formatting and restore LF line endings

* style: fix ruff format and prettier issues

- test_artifacts_router.py: collapse two over-split client.get() calls to
  satisfy ruff format (line-length 240)
- loader.ts / artifact-file-detail.tsx / loader.test.ts: apply prettier
  formatting and restore LF line endings

* style: reformat artifact-file-detail.tsx for prettier with tailwind class ordering

* fix: invalidate SHA-256 cache after artifact edit

Clear the LRU cache after os.replace() so the next preview request
computes the new digest. Edits are rare, so clearing the whole
256-entry cache costs nothing (addressing PR review comment #5).

* fix(artifacts): skip ETag for oversized files + CRLF->LF + cache invalidation (#4865)

* fix(loader): use real empty-content SHA-256 for empty 416 range (#4865)

* test(artifacts): assert oversized artifacts carry no SHA-256 ETag (#4865)

* style(frontend): format long sha256 constant (prettier)

* test(backend): fix oversized-artifact ETag assertions and formatting (ruff)

* test(backend): keep oversized-payload line within ruff 240-col config
2026-08-24 07:45:30 +08:00
ajayr
7e95bef2e7
feat(mcp): per-user credential injection for shared MCP servers (#4868)
* feat(mcp): per-user credential injection for shared MCP servers

A single HTTP/SSE MCP server entry can now serve several users, each
authenticated to the remote service with their own credential. A server
opts in with a user_auth block mapping user ids to credential header
values ($ENV_VAR references supported):

  "user_auth": {
    "header": "Authorization",
    "users": { "<user-id>": "$SERVICE_TOKEN_ALICE" }
  }

The built-in user-scoped auth interceptor resolves the authenticated
runtime user on every tool call (request runtime -> ambient LangGraph
runtime -> auth config -> request-scoped user ContextVar) and rewrites
the configured header via request.override(), the same per-call
mechanism as the OAuth interceptor. It registers after OAuth in the
shared assembly so its per-user value wins the header when a server
declares both. The entry's static headers are used only for startup
tool discovery.

Fail-closed by default: an unmapped user - including the anonymous
default-user fallback - or a credential whose env reference resolved
empty gets an actionable ToolException instead of another user's
credential; on_missing: "passthrough" opts out per server. Combined
with the existing per-(user, thread) MCP session scoping this gives
credential isolation on shared servers.

Gateway API: user_auth.users values are masked in GET responses, and
PUT round-trips preserve stored credentials for masked values (same
contract as env/headers/oauth secrets); a masked value for a user id
not already stored is rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(mcp): address review — preserve stored user_auth sub-fields on partial PUT, warn-and-skip stdio, allow extras

Review findings from #4868:

1. A partial user_auth payload (e.g. {"enabled": false}) merged to
   users={} and default on_missing, irreversibly wiping stored
   credentials on PUT. The merge is now sub-field-aware via
   model_fields_set — omitted sub-fields carry the stored values, an
   explicitly sent users map still replaces (so full-round-trip removal
   works), masked values still swap back for stored credentials.

2. user_auth on a stdio server was a silent no-op: rewritten headers go
   to call meta, never a transport header, while deny errors still fired.
   The interceptor builder now warns and skips non-sse/http servers,
   matching the tool_call_timeout transport-mismatch convention.

3. McpUserScopedAuthConfigResponse now allows extra keys like the
   harness-side model, and extras survive masking and merge, matching
   the server-level model_extra handling.

Adds four regression tests (partial-PUT preservation, explicit-map
replacement, extras round-trip, stdio warn-and-skip).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(mcp): reject blank user_auth.header at the gateway

A blank header passed the gateway response model, was persisted, then
failed the harness-side ExtensionsConfig validator on reload — the PUT
returned 500 after the write and every later config load/startup failed
until the file was hand-edited. Mirror the harness non-blank validator
on McpUserScopedAuthConfigResponse so the PUT fails with 422 before
anything is written.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: ruff format extensions_config.py

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(mcp): harden the trust chain and masking around user-scoped credential selection

Address review round 4:
- Scrub client-supplied user_id from run context/configurable for
  external callers in inject_authenticated_user_context, before every
  early return, and restamp only from request.state.user. Now that
  user_id selects which user's credential user-scoped MCP auth injects,
  a forged value must not survive any future path that skips the
  restamp. Internal callers (IM channels, scheduler) keep supplying
  end-user identity as before (PR #3294 contract). Regression tests pin
  both the scrub and that a forged body.context.user_id can never
  resolve as another user through merge + inject ordering.
- Include the caller's own resolved user id in the fail-closed deny
  message so operators can copy the exact users key (it differs by
  deployment path), and document the key formats in the mcp.mdx doc.
- Mask sensitive extra keys inside user_auth on GET like server-level
  extras, and swap masked sentinels back for stored values on PUT via
  _merge_extra_value_preserving_masked.
- Extract the interceptor wrap loop into compose_tool_interceptors and
  pin the security property functionally: an OAuth interceptor that
  actually sets Authorization loses the final header to the per-user
  credential through the same composition the session-pool path uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 15:16:04 +08:00
marvin
ee7ae279c4
fix(docs): retarget docs-home Introduction links to why-deerflow (temporary) (#4916) 2026-08-23 15:03:03 +08:00
Aari
5ffc2d3e27
feat(mcp): complete durable task notifications and chat UI (#4833)
* feat(mcp): add reliable task notifications and cancellation

* feat(mcp): add background task chat UI

* fix(mcp): hide and sanitize task notification prompts

* fix(mcp): sanitize projected task names

* fix(mcp): harden task notifications and details

* fix(mcp): harden task lifecycle recovery

* fix(mcp): gate task UI and isolate cancellations

* test: scope plain-text response locator

* fix(mcp): align task notification boundaries

* fix(mcp): bound task delivery retries

* fix background task notification races
2026-08-22 16:53:32 +08:00
starslittle
7e4996eef3
fix(frontend): surface model loading failures (#4840)
* 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
2026-08-17 08:23:07 +08:00
Aari
16ecf7b006
fix(frontend): preserve completed message actions during streaming (#4844)
* fix(frontend): preserve completed message actions during streaming

* fix(frontend): address streaming action review feedback

* fix(frontend): reuse settled stream snapshots
2026-08-16 23:53:22 +08:00
starslittle
b341120a4a
fix(frontend): authenticate remaining gateway reads (#4827)
* fix(frontend): authenticate remaining gateway reads

* fix(frontend): authenticate artifact reads

* fix(frontend): include status in model errors
2026-08-16 12:03:13 +08:00
Ryker_Feng
ccff5f5ce7
docs: govern agent guidance size (#4799)
* docs: govern agent guidance size

* refactor: split agent guidance by code scope

* Clarify virtual path handling in AGENTS.md

Updated the translation section to clarify the role of `LocalSandboxProvider` and the handling of virtual paths in the tool layer.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-08-13 21:49:04 +08:00
Daoyuan Li
e23dd8f88b
refactor(frontend): share showcase chat page (#4765) 2026-08-12 09:16:58 +08:00
Ryker_Feng
1e8cedb9f4
feat(channels): polish Buzz frontend (#4727)
* feat(channels): complete Buzz frontend copy

* feat(channels): add official Buzz provider icon

* test(i18n): clarify translation suite scope
2026-08-12 08:33:35 +08:00
Ryker_Feng
9ba04bf80c
fix(frontend): reuse clipboard fallback for Lark auth (#4767) 2026-08-11 21:08:52 +08:00
dependabot[bot]
21e2cfd719
build(deps): bump nanoid from 5.1.6 to 5.1.16 in /frontend (#4748)
Bumps [nanoid](https://github.com/ai/nanoid) from 5.1.6 to 5.1.16.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.1.6...5.1.16)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-version: 5.1.16
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 22:43:47 +08:00
Ryker_Feng
e401ae2d7b
feat(integrations): support switching Lark app credentials (#4703)
* feat(integrations): support switching Lark app credentials

* fix(integrations): harden Lark app switching

* refactor(integrations): simplify Lark switch flow

* fix(integrations): reject superseded Lark flows

* test(integrations): pass Lark flow generation

* fix(integrations): preserve pending Lark flows

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-08-10 07:19:19 +08:00
Aari
e5c62cab5a
feat(frontend): add Browser Live to Custom Agent chats (#4719)
* feat(frontend): add Browser Live to Custom Agent chats

* test(frontend): cover mock Custom Agent Browser Live
2026-08-07 21:38:27 +08:00
Hao Zhe
6556d09d7f
refactor(memory): use official OpenViking adapter (#4707)
* refactor(memory): use official OpenViking adapter

* fix(memory): preserve OpenViking recall behavior

* fix(memory): ignore ambient OpenViking headers
2026-08-07 11:22:00 +08:00
Daoyuan Li
480a3757ed
fix(frontend): add public case study routes (#4635) 2026-08-05 08:49:14 +08:00
dependabot[bot]
d8ee55e5ac
build(deps): bump postcss from 8.4.31 to 8.5.25 in /frontend (#4683)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.31 to 8.5.25.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.31...8.5.25)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.25
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-05 07:42:00 +08:00
Vanzeren
bec6277930
fix(frontend): restore user message above same-run steps after mid-run reload (#4660)
* fix(frontend): restore user message above same-run steps after mid-run reload

After a mid-run page reload, replayed messages-tuple steps can reach the
merged list before the turn's human message (the retained replay buffer
may have dropped it), and the live-only human is woven in before the next
shared history anchor. The steps panel then renders above the user
message it belongs to and the per-turn token row splits one run in two.

restoreLocalTurnMessageOrder only covers the locally submitted turn, so
add restoreReconnectedTurnMessageOrder for the reconnected path: a
visible AI/tool step above the last visible human is provably misplaced
when the same run also appears below the human (canonical history is
seq-sorted), or when the step is run_id-less (live-only) and a
run_id-less step follows the human. Pagination orphans from older turns
(#4399) and resent turns after an interrupted run fail both checks and
keep their position.

* fix(frontend): never move a completed turn's terminal answer below the next human

Branch-seeded threads and mocked feeds can share one run_id across turns,
so the same-run sandwich check alone misfired on completed multi-turn
history: a previous turn's final answer was pulled below the next user
message, stripping the turn of its branchable assistant group (e2e
branch-thread.spec.ts).

Only steps after the last terminal assistant answer (visible content, no
tool calls) in the segment are now candidates for reordering. A
still-streaming text step can look like a terminal answer until its tool
call arrives (#4304); it then stays above the human until canonical
history heals the order — an accepted transient, far safer than moving a
completed answer.
2026-08-04 15:07:59 +08:00
Baldwinzc
91d7f4e4fa
fix(frontend): keep inline-code <think> pairs in content and restore copy for reasoning-only turns (#4647)
Two related fixes in the reasoning extraction path of
core/messages/utils.ts:

1. splitInlineReasoning's first pass stripped every closed
   <think>...</think> pair unconditionally, so a message that discusses
   the tag literally in markdown inline code (e.g. "Wrap your reasoning
   in `<think>...</think>`") had its code span hollowed out and the
   inner text shipped to the Reasoning panel. The streaming pass already
   guards backtick-adjacent openers; apply the same guard to the
   closed-pair pass so both passes agree on what counts as literal tag
   talk.

2. getAssistantTurnCopyData fell back to reasoning via
   `content ?? reasoning`, but extractContentFromMessage never returns
   null, so the fallback was dead code and a reasoning-only turn (e.g.
   stopped mid-thinking) rendered no copy button at all - inconsistent
   with getMessageCopyData, which does copy reasoning in that case. Use
   the same empty-string check it uses.
2026-08-03 08:27:23 +08:00
Aari
da282811b8
feat(frontend): reopen the skill list after a skill is selected (#4639)
* feat(frontend): reopen the skill list after a skill is selected

Selecting a skill closed the composer's skill list for good: `/` no longer
reopened it, so a skill could not be looked up or swapped without deleting
the chip first.

The list now reopens from the editable text beside the chip, and picking an
entry swaps the chip rather than stacking a second activation, since the wire
format carries exactly one leading /skill. Builtin commands are withheld in
that state because they own the whole composer line, and Enter navigates the
list before submitting except while an IME is composing.

The trigger is unchanged: a slash still opens the list only at the start of
the input.

* fix(frontend): keep builtin names reserved in the reopened skill list

Withholding the builtin list from getMatchingSkillSuggestions in chip mode
also disabled the reserved-name filter it drives, so a custom skill named
after a builtin command became selectable there. Nothing rejects such a name
at install time, and submitting the resulting chip runs the command instead
of the skill.

Pass the builtin list as before and drop the builtin entries from the result
instead. The new regression covers both sides of the reservation, and the
reopen test now waits for the list before pressing Enter.

* fix(frontend): hide skills the slash parsers refuse from the picker

The composer picker reserved only the two builtin command names, while both
slash parsers refuse the seven names in the shared contract. A skill named
bootstrap, help, memory, models, new or status was therefore offered, could
be selected into a chip, and submitted — and then activated nothing, because
parse_slash_skill_reference drops the name on the way in. The turn reached
the model as literal text with no skill loaded and no error anywhere.

Reserve the contract names alongside the builtin ones, so the picker cannot
offer what the parsers will not honour.
2026-08-02 22:58:15 +08:00
Ryker_Feng
78f7a5f7c0
fix(frontend): sort chats page by pinned state (#4643) 2026-08-02 22:56:18 +08:00
Kushida
7025ccee40
fix(artifacts): scope full previews to their thread (#4634) 2026-08-02 10:07:34 +08:00
Ryker_Feng
c0f1cfef69
fix(frontend): hide stale follow-up chips while a turn is streaming (#3395) (#3396)
* fix(frontend): hide stale follow-up chips while a turn is streaming (#3395)

Follow-up suggestion chips are generated when a turn finishes streaming,
but `showFollowups` did not exclude the streaming state. If a user sent a
new message before the previous response finished, the old chips (and the
lone close button) stayed mounted and overlapped the To-dos panel and the
input box.

Gate `showFollowups` on `status !== "streaming"` so stale chips are never
shown while a response is in progress.

* fix(frontend): suppress follow-up suggestions for user-interrupted turns (#3395)

Gating showFollowups on status alone was not enough: stopping a streaming
turn (or sending a new message mid-stream, which also stops it) flips
status back to a non-streaming state and triggers the follow-up generation
effect on that streaming->ready transition, producing chips for a
half-finished, interrupted response.

Track user interruption with a ref set in the stop path, and have the
generation effect skip that transition (and clear/hide any pending chips),
so follow-ups are only generated for turns that finished on their own.
2026-08-02 09:17:26 +08:00
DanielWalnut
459dd78707
perf(frontend): bound delivery, bundles, and long-running UI work (#4622)
* docs: design frontend performance remediation

* docs: plan frontend performance remediation

* test(frontend): add route asset performance budgets

* perf(nginx): compress textual responses safely

* perf(frontend): lazy load case study media

* perf(frontend): bound static demo file tracing

* perf(frontend): restore static locale boundaries

* perf(frontend): defer closed workspace panels

* perf(frontend): split editors and deduplicate highlighting

* perf(frontend): index incremental message derivation

* perf(frontend): stabilize paged history cache policy

* perf(frontend): bound streaming markdown renders

* perf(frontend): virtualize message history

* perf(frontend): bound and virtualize chat lists

* perf(frontend): suspend inactive decorative animation

* perf(browser): stream latest frames as binary

* perf(artifacts): stream bounded text previews

* docs: finalize performance runtime boundaries

* style(backend): apply test formatting

* fix(frontend): keep translation functions client-side

* perf(frontend): defer decorative animation bundles

* test(frontend): lock optimized route budgets

* fix: harden frontend performance boundaries

* test(frontend): update i18n provider fixture

* fix(frontend): preserve sidebar pagination position

* style(backend): format artifact range test
2026-08-01 22:19:59 +08:00
Baldwinzc
2f6cadb8b0
fix(frontend): stop matching <header> as <head> in HTML preview base injection (#4625)
appendHtmlPreviewBaseHref detected the head tag with /<head[^>]*>/i,
which also matches <header ...>. For a fragment with no <head> that
opens with <header> - a common shape in agent-generated report pages -
the <base> element was injected after the <header> opening tag instead
of being prepended, so relative assets appearing before that point
(e.g. a leading <img>) resolved without the base and failed to load in
the sandboxed iframe.

Use the word-boundary-safe /<head(?:\s[^>]*)?>/i that the sibling
appendHtmlPreviewScrollRestoration already uses, keeping the two
injectors consistent.
2026-08-01 21:59:27 +08:00
qin-chenghan
8234370a6a
feat(artifacts): inline editing for text artifacts in the panel (#4596)
* feat(artifacts): inline editing for text artifacts in the panel

Add a PUT /api/threads/{id}/artifacts/{path} endpoint that atomically
replaces an existing UTF-8 text file under /mnt/user-data/outputs after
verifying its SHA-256 revision. Active runs conflict (409); binary,
symlink, oversized, and non-output paths are rejected.

Frontend: edit/save/discard buttons, draft state with conflict detection,
CodeEditor onChange/onSave, loader SHA-256 from ETag, i18n, beforeunload guard.

Backend: PUT endpoint with thread reservation, atomic temp-file replacement,
sandbox sync for non-mounted providers, rollback on failure, ETag on GET.

Tests: 8 backend + 1 blocking-IO + 3 frontend test files.

* fix(artifacts): scope replacement permissions and release sandboxes

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-08-01 09:15:21 +08:00
Huixin615
5b7ada0cac
fix(frontend): dedupe injected user messages during long runs (#4620) 2026-07-31 22:50:11 +08:00
Amorend
85c3909c2e
feat: show real-time context window usage (#3125) (#3183)
* feat: show real-time context window usage in chat UI (#3125)

Adds a `context_usage` block to `GET /api/threads/{id}/token-usage`
(token count from the live checkpoint, the thread model's
`context_window`, and a percentage), introduces a new
`ModelConfig.context_window` distinct from the per-call `max_tokens`
output cap, and surfaces the percentage in the chat header — inside
`TokenUsageIndicator` when token-usage tracking is on, or as a
standalone badge when it's off so context capacity stays visible
independent of cost tracking.

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

* feat: per-category breakdown for context window usage

Replace the single-number context_usage payload with a Claude-Code-style
breakdown — messages, system prompt, skills, system/MCP tools (active +
deferred), custom agents, memory injection, autocompact buffer, and free
space — and surface it in the chat UI with a segmented progress bar and
per-row table.

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

* docs(config): document context_window across model examples

Add `context_window` to every example model in config.example.yaml so the
new chat-UI "% context used" indicator works out of the box for whichever
example a user adopts. Each value is the published default at the time of
writing; users are pointed at the official model spec to verify. Bumps
config_version to 11 so `make config-upgrade` flags outdated user configs.

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

* style: ruff format (line-length 240)

No behavior change — collapses two multi-line expressions that fit on
one line under the project's 240-char limit. Picked up by `make format`.

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

* review: address Copilot bot comments on #3183

- token-usage-indicator: switch `{contextPercentage && (...)}` to an
  explicit `!= null` check. (The string `"0"` is actually truthy in JS so
  the original code wasn't buggy, but the explicit check is clearer.)
- context-usage-breakdown: drop the `useMemo` around segments/totals — the
  computation is O(n) over a handful of rows and the previous memo deps
  omitted `t.contextUsage.categories`, so the bar's tooltips/aria-labels
  could stay in the old language after a locale switch.
- context_usage._split_tools: snapshot MCP names from
  `get_cached_mcp_tools()` directly instead of re-reading
  `extensions_config.json` after `get_available_tools()` already loaded
  it. Removes redundant file I/O on every `/token-usage` poll.
  (`get_available_tools()` still emits its own INFO logs — silencing
  those is out of scope here.)

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

* style(frontend): prettier --write context-usage-breakdown

CI's `pnpm format` (prettier --check) caught two lines previously
formatted by hand. Collapses one comma to fit on one line; no behavior
change.

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

* fix(gateway): correct context-usage breakdown + add exact token counting

The context-usage indicator shipped two bugs that silently zeroed whole
breakdown rows (both caught by try/except, so the feature looked alive but
produced wrong numbers):

1. _count_system_prompt passed app_config= to get_deferred_tools_prompt_section,
   which only accepts deferred_names -> TypeError swallowed -> system_prompt
   row always 0, and used_tokens/percentage undercounted by the full prompt.
   Also subtracted the deferred section twice (the rendered prompt already
   excluded it). Fix: derive deferred names deterministically and pass them to
   apply_prompt_template; drop the redundant subtraction.

2. _split_tools imported a non-existent get_deferred_registry -> ImportError
   swallowed -> all four tool-category rows always 0. Fix: classify via the
   public is_mcp_tool predicate + tool_search.enabled (mirrors
   build_deferred_tool_setup); the MCP tag is set by get_available_tools.

Added token_usage.counting (approximate|exact). 'exact' routes text/schema/
message counting through the model tokenizer (tiktoken cl100k_base) via the
existing memory-module machinery (lazy load + cache + cooldown + CJK-aware
fallback), so CJK-heavy threads stop being undercounted by chars//4.

Regression + e2e tests added; 6621 backend tests pass.

* fix(gateway): harden context usage accounting

* fix(gateway): count promoted MCP tools as active in context usage

Promoted tools (deferred MCP tools the thread has fetched via tool_search)
have their full schema bound on every subsequent turn by
DeferredToolFilterMiddleware, so they consume context like any active tool.
The breakdown previously left them in the reserved *_deferred rows, under-
counting the thread's used_tokens.

Classification now treats a tool as deferred only when tool_search is enabled,
it is MCP-sourced, AND it has not been promoted. The promoted set is read from
the checkpoint's channel_values and scoped by catalog hash — matching the
runtime middleware, so a stale promotion from MCP-config drift cannot inflate
the active count.

The static system prompt still lists all deferred tool names (promotions only
affect schema binding, not the prompt), so _count_system_prompt's deferred
rendering is intentionally left unchanged.

8 new tests cover classification, catalog-hash scoping (match / drift /
compute-failure / malformed), and checkpoint extraction.

* fix(context): address review feedback

* fix(context): count structured message payloads

* fix(context): harden usage accounting

* fix(config): bump schema for context usage fields

* refactor: narrow context usage to core indicator

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-07-31 21:57:22 +08:00
Baldwinzc
17461ee52e
fix(frontend): parse uploaded filenames containing parentheses (#4608)
parseUploadedFiles stopped the filename capture at the first "("
([^\n(]+), so an entry like "- photo (1).png (12.3 KB)" failed to
match and the file silently disappeared from the message's file chips.
Browsers produce such names for duplicate downloads, making this a
common real-world shape.

Anchor the size group on the "(<number> <unit>)" pair the backend
emits (uploads_middleware formats sizes as "%.1f KB"/"%.1f MB") and
let the filename match greedily up to it, so parenthesized filenames
parse correctly.
2026-07-31 18:08:34 +08:00
Huixin615
133a82c6c2
fix: isolate MCP server toggles from invalid peer configs (#4577)
* fix: isolate MCP server toggle updates

* fix: write extensions config atomically

* fix: normalize MCP transport aliases
2026-07-31 08:32:39 +08:00
Ryker_Feng
150f7740c7
security: harden auth next-path validation (#4587)
* security: harden auth next-path validation

* chore: address auth review nits
2026-07-30 23:53:52 +08:00
qin-chenghan
60fe0c4433
fix(frontend): refresh active artifact content (#4584)
* fix(frontend): refresh active artifact content

* fix(frontend): remove 1Hz polling, keep only final refetch after run

Address review feedback: the 1Hz refetchInterval could poll
indefinitely when a write tool call is left unresolved (abort,
error, or missing ToolMessage). Removing the polling entirely
eliminates this risk while still satisfying the core requirement:
artifact content is refreshed once when the run settles, so edits
are visible without a manual reload.

- Remove refetchInterval / hasActiveWrite logic from hooks.ts
- Delete refresh.ts (hasActiveWriteForArtifact helper)
- Delete refresh.test.ts

* docs: align README and AGENTS.md with settle-time refetch behavior
2026-07-30 23:47:16 +08:00
qin-chenghan
0d8e11ad49
fix(frontend): persist artifact panel state (#4580) 2026-07-30 15:54:15 +08:00
Aari
c066819f69
fix(frontend): keep streaming reasoning above the answer text (#4578)
A reasoning model's turn showed its thinking below its answer while
streaming, then flipped to thinking-above-answer once the turn settled.
One message is rendered by two components with opposite ordering rules:
while streaming, an AI message with content and reasoning but no tool
calls yet is deliberately held out of the terminal bubble (#4304) and
rendered by MessageGroup's chain-of-thought panel, which pinned the
trailing reasoning disclosure to the bottom; the settled bubble paints
its <Reasoning> disclosure above the content.

Render the trailing reasoning disclosure before the assistant text that
follows it, and emit a message's reasoning step before its content step
in convertToSteps -- the step list was content-first, so ordering by
step position alone could not fix it. Assistant text emitted before that
reasoning keeps its earlier position.

This also covers two cases the report does not mention: tool-using turns
reversed the same way, and expanding "N more steps" showed a message's
answer above its own thinking.
2026-07-30 13:55:08 +08:00
now-ing
d07a4bf7eb
test(auth): lock in POST logout from gateway-offline banner (#3001) (#4506)
* test(auth): lock gateway-unavailable logout to POST (#3001)

Issue #3001 reports that the gateway-unavailable fallback rendered the
recovery action as a plain link to /api/v1/auth/logout, which browsers
navigate via GET against a POST-only endpoint — returning 405 and
leaving the stale session cookie intact while the gateway is down or
restarting.

The code-level fix already landed in #3495: <GatewayOfflineBanner>
renders a <button onClick={logout}> wired to AuthProvider.logout's
fetch(..., { method: "POST" }). That PR's test suite, however, only
covers the banner's pure helpers (visibility + retry interval) and the
gateway_unavailable SSR tag — it never asserts that the recovery action
actually reaches the network as a POST, so a regression back to a
GET-style link/navigation would slip through silently.

Add a DOM-level regression test that renders the banner inside a real
AuthProvider, simulates a still-down gateway for the /auth/me probe (so
the banner stays mounted and its recovery button stays actionable),
clicks the button, and asserts that the resulting request is
POST /api/v1/auth/logout — never GET. This pins the exact behaviour
#3001 requires and fails loudly if the affordance ever regresses.

Closes #3001.

* test(auth): guard logoutCall against undefined in gateway-offline-banner test

TypeScript's noUncheckedIndexedAccess types logoutCalls[0] as T|undefined,
which surfaced as TS18048 on the three logoutCall.{url,method} accesses.
The waitFor callback already asserts toHaveLength(1) before returning; add
an explicit throw guard so the value narrows to a defined Call and the
assertions below type-check.

Unblocks lint-frontend on #4506.

---------

Co-authored-by: now-ing <24534365+now-ing@users.noreply.github.com>
2026-07-29 23:00:51 +08:00
Aari
3c5e3d9de5
fix(frontend): keep panel open after reversed drag (#4566)
* fix(frontend): keep panel open after reversed drag

* test(frontend): model reversed panel drag cumulatively
2026-07-29 21:07:00 +08:00
Wu JiaCheng
e317f7b8d9
perf(streaming): batch main thread stream updates (#4410)
* perf(streaming): batch stream updates

* docs(streaming): clarify throttle runtime default

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-07-29 14:10:51 +08:00
Aari
43e5ef865d
fix(frontend): render one workspace-change card per run (#4559)
* fix(frontend): render one workspace-change card per run

The workspace-change card is resolved from (threadId, runId) alone, so
every AI message in a run fetches the identical summary. It was rendered
per AI message.

getMessageGroups() opens a separate terminal assistant group for every AI
message that has content and no tool calls, so a run ends in more than one
bubble whenever the model emits answer text mid-run that never gains a
tool call. Each bubble then painted a byte-identical "Edited N files"
card.

Fold the card onto a single position per run, matching how run duration
already anchors its own run-scoped display after the run's last visible
group. Anchoring keys on group index rather than message id because a
terminal assistant group holds exactly one message whose id may be absent.

Fixes #4555

* docs(frontend): explain the workspace-change anchor's group-type restriction

The helper narrows anchor candidates to terminal assistant groups while
getRunDurationDisplaysByGroupIndex accepts a run's last group of any type.
That asymmetry is load-bearing: run duration is emitted by MessageList
around every group, but the workspace-change card comes from
MessageListItem, which MessageList invokes only for human/assistant
groups. Anchoring a run that ends in an assistant:processing group would
pick a position that never renders and silently drop the card.

Record the rule in both places that could invite a future "unification":
the helper's docstring, and the frontend AGENTS.md paragraph that tells
maintainers where run-scoped displays belong.
2026-07-29 14:01:28 +08:00
Aari
4e66acbbb4
fix(frontend): sync panel state when a drag collapses the side panel (#4556)
The shared right panel is collapsible with collapsedSize="0%", so dragging
the divider past minSize makes the library collapse it to zero without
going through the state that owns the panel. The panel disappears while
that state still reads open, leaving the divider draggable but inert and
the panel's trigger needing two clicks to bring it back.

Mirror a zero-width resize back into the owning state so a drag-collapse
closes the panel the same way its trigger does, and keep recording
non-zero widths there as the size to reopen at.
2026-07-29 11:51:51 +08:00
ShitK
4e44938551
fix: align pnpm consumers with Corepack fallback (#4405)
* fix: align pnpm consumers with Corepack fallback

* fix: run pnpm helper from frontend workspace

* fix: preserve Corepack resolution hint
2026-07-29 08:08:33 +08:00
dependabot[bot]
e41f4c9402
build(deps): bump postcss from 8.4.31 to 8.5.24 in /frontend (#4550)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.31 to 8.5.24.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.31...8.5.24)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.24
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-29 06:45:42 +08:00
RongfuShuiping
2aaf74b0f8
feat(memory): add OpenViking HTTP backend (#4509)
* feat(memory): add OpenViking HTTP backend

* fix(memory): harden OpenViking lifecycle

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-07-28 23:36:25 +08:00
Ryker_Feng
aacb99cfd2
feat(lark): sidecar credential broker for sandbox lark-cli (Pattern B) (#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 #4338

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

Follow-up to the sidecar credential broker addressing the PR #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.
2026-07-28 22:54:44 +08:00
qin-chenghan
1bccc8e20e
feat(frontend): allow chat replies during clarification (#4530)
* 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.
2026-07-28 22:19:50 +08:00
Aari
9a43d8276d
fix(gateway): replay edit and rerun from a settled checkpoint (#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 #4531
2026-07-28 22:12:27 +08:00