4595 Commits

Author SHA1 Message Date
Xuehao Xu
b6ce047510
feat(workflows): add durable restart recovery (Phase 3) (#7075)
* feat(workflows): recover durable workflows after Gateway restart

* docs(workflows): keep recovery guidance within inherited budget

* fix(workflows): retain cancellation ownership and stabilize turn-limit test

* fix(workflows): log service-owned action failures
2026-10-19 15:24:53 +08:00
spud
850b3b2547
fix(runtime): attribute terminal commits and isolate lease renewals (#6919)
* fix(runtime): confirm terminal commits and isolate lease renewal

* fix(runtime): diagnose unsupported terminal store contracts

* fix(runtime): attribute atomic claims and close lease lifecycle gaps

* fix(runtime): require explicit terminal completion capability

* fix(runtime): retry renewals that race rollback admission
2026-10-19 15:06:35 +08:00
Jay0130-a
0a1c86a371
fix(memory): parse a UTF-8 BOM in the DeerMem summary instead of raising corruption (#7083) (#7084) 2026-10-19 14:44:15 +08:00
zzr
afc96c667a
docs(feishu): explain CA trust troubleshooting for TLS failures (#6869)
* docs(feishu): explain CA trust troubleshooting for TLS failures

* docs(feishu): sync Chinese TLS and notification pointers
2026-10-19 11:51:38 +08:00
Dragon ze
4ff0b0bff1
fix(github-research): resolve the tree against the repository's default branch (#7074)
* fix(github-research): resolve the tree against the repository's default branch

The tree command asked for "main" and, when that was missing, for "master".
A repository whose default branch is neither gets a name-guessing fallback, and
when the stale "master" still resolves GitHub returns it happily: the agent is
handed the tree of an abandoned branch and reports it as the current structure.

Ask for HEAD instead, which GitHub resolves to the repository's own default
branch, so no candidate names are guessed and no extra request is added.

* test(github-research): clarify stale branch tree comment

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-19 11:38:13 +08:00
Qiu
a1f23b294a
fix(frontend): preserve memory mutations against stale reads (#6959)
* fix(frontend): preserve memory mutations against stale reads

* test: register memory guidance in scoped inventory

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-19 11:33:35 +08:00
ordinary being
eed7fa8f05
fix(mcp): fail durable tasks whose connection no longer resolves (#7058)
* fix(mcp): fail durable tasks whose connection no longer resolves

A personal MCP runtime name is derived from the server's complete definition, and a deployment task server belongs to the Gateway startup snapshot. Editing, disabling, or deleting a personal server while a durable task is in flight therefore makes the persisted server name unresolvable. McpTaskToolCaller raised LookupError, which the service classifies as transient: status polls and cancellation retried forever, leaving the task stuck in working and impossible to cancel.

Raise a dedicated McpTaskConnectionUnavailableError instead, and terminalize it: status polls persist failed with the connection error and stop; a pending cancellation persists failed locally because the remote task can no longer be reached. Transient status/cancel errors keep their existing capped-backoff retry policy.

Tests: new service tests pin both terminal paths, and caller tests pin the exception for missing deployment and stale personal server names.

* fix(mcp): preserve LookupError compatibility for connection errors

McpTaskConnectionUnavailableError now subclasses LookupError instead of RuntimeError. The four existing personal-MCP assertions in test_personal_mcp.py still treat a missing or stale connection as a lookup failure; keeping that base preserves their behavior and avoids breaking callers that caught the previous type, while the task service still catches this exact type for permanent-failure classification.

Add a compatibility test and rerun the personal-MCP and task suites.

---------

Co-authored-by: Tong-bit-art <Tong-bit-art@users.noreply.github.com>
2026-10-19 11:32:28 +08:00
Jay0130-a
50cd235a74
test(skillscan): pin the destructuring-target instance-client false negatives (#4296) (#6982)
* test(skillscan): pin the destructuring-target instance-client false negatives (#4296)

* test(skillscan): drop the dangling case-number citation

---------

Co-authored-by: Jay0130-a <Jay0130-a@users.noreply.github.com>
Co-authored-by: Jay0130-a <309071492+Jay0130-a@users.noreply.github.com>
2026-10-19 11:03:46 +08:00
PeaceMaker-best
dbebd18dd7
fix(deer-flow): retry failed agent setup saves (#6628)
* fix(deer-flow): retry failed agent setup saves

Signed-off-by: PeaceMaker-best <221849497+PeaceMaker-best@users.noreply.github.com>

* fix(frontend): fence superseded agent save callbacks

---------

Signed-off-by: PeaceMaker-best <221849497+PeaceMaker-best@users.noreply.github.com>
Co-authored-by: PeaceMaker-best <221849497+PeaceMaker-best@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-19 10:55:50 +08:00
Totoro
92e9afe6ea
feat(context): support client context capabilities (#7051)
* feat: support client context capabilities

(cherry picked from commit b7c223f9415dcd545cbe05a5f5a6e47073a42d68)
[Totoro-qaq: resolved onto main. The persisted-reminder middleware hunks and their tests are dropped (delivery is redesigned in a follow-up); the RunCreateRequest description moved to app/gateway/run_models.py.]

* fix: address client context review comments

(cherry picked from commit bce6da6b819228eda58e887872720b6f9b5fab69)
[Totoro-qaq: middleware hunks dropped with the redesigned delivery.]

* feat(agents): deliver client context as a request-scoped data block

The cherry-picked #3106 put context.client into the persisted first-turn
reminder, with change detection and a "not provided" clear marker. Main
has since moved the date reminder to a SystemMessage, made request-scoped
context (the project block, #5443) request-only, and agents/AGENTS.md now
requires user-influenced values to ride the HumanMessage data channel,
never system text.

Render runtime.context["client"] on every model call in
DynamicContextMiddleware.wrap_model_call/awrap_model_call as one hidden
HumanMessage right after the leading SystemMessages, where DurableContext
puts its data. It is never a state update and never flagged
dynamic_context_reminder, so nothing is checkpointed, summarized or
replayed, and a run without a client gets no block. An unchanged client
keeps the previous request as the next request's prefix.

The renderer sanitizes again (direct graph callers bypass the Gateway),
html-escapes values and frames them with frame_untrusted_text: the same
--- BEGIN/END USER INPUT --- boundary the user's own turn and the MCP task
notification get. The lead prompt treats everything outside those markers
as framework data, so unframed values would rank above the user's own
text; framed, context.client can say nothing the same caller could not
already say in input.messages. client_context joins _BLOCKED_TAG_NAMES so
a forged block in user input or a tool result is escaped, and one static,
value-free sentence in the lead prompt names the block as untrusted hints,
never instructions, permissions or identity. The now unused empty-context
marker is removed.

* fix(gateway): log dropped context.client fields without values

A run request is never rejected over context.client: unknown fields,
nested values and malformed entries are silently dropped, the same
leniency as the other context keys. That left no signal when a custom
client sent something the Gateway ignored (review on #3106).

Route both the config.context.client and the body context.client paths
through one helper that emits a debug line whenever sanitizing changed
the value. Like the malformed client_timezone branch, the line carries no
values; unlike client_timezone, client.name and preference strings are
free caller-controlled text, so they are never logged, not even
sanitized. A clean client, an explicit null or an absent key logs
nothing.

Tests also pin the config-only path (sanitized before the early return)
and the end-to-end start_run admission: the sanitized client reaches
config["context"] and never configurable.

* fix(runtime): bound numeric client context preferences

String preferences are capped at 200 characters, but integers were kept
at any size. A JSON body may carry integers of up to 4300 digits, so 32 of
them rendered a block of about 138,000 characters, re-sent on every model
call of the run. In-process callers could also pass an int past the str()
digit limit, which failed the model call, or a number subclass with its
own __str__.

Keep integers within JavaScript's Number.MAX_SAFE_INTEGER, the range a
browser client can send exactly, and coerce numbers to plain int and
float.

* docs(api): describe client context as advisory request-scoped hints

The cherry-picked section described a hidden reminder that no longer
exists. Document what a custom client can rely on: the allow-list and its
limits (strings, the integer range, and only the first 32 entries of each
map), debug-level logging of dropped fields, runtime-only forwarding, and
delivery as a per-request user-role block, framed like user input, rather
than system text or thread history.

Only top-level context.client is documented. An explicit
config.context.client is sanitized for the run as well, but the run record
keeps the caller's config as sent, so it is not offered as an alternative.

It also answers the #3106 review: capabilities and preferences are
advisory hints, not enforcement, which belongs outside the model; name is
informational and never for authorization; the same object should be
sent on every run to keep the cached prefix stable; subagents and
embedded DeerFlowClient runs do not receive it, and server-started runs
(schedules, IM channels, GitHub agents, MCP notifications, extensions
unless the plugin passes one) carry none.

* fix(context): declare client delivery and keep client out of configurable

The request-only <client_context> block is model-visible, so
DynamicContextMiddleware now declares its delivery and the sanitizer's
limits in release_policy_parameters(); runs before and after this change
no longer share a policy fingerprint.

build_run_config copies body.config['configurable'] verbatim and
configurable is persisted with checkpoints, so a client placed there is
dropped instead of being stored raw. Client context stays runtime-only.

Document context.client in the README and in the middleware guide.

* fix(context): apply the PII redaction policy to the client block

DynamicContextMiddleware adds the <client_context> message inside the
outer PiiRedactionMiddleware, and the hidden message is not re-sanitized,
so with pii_redaction enabled a preference such as an email address
reached the model verbatim while the same address in the user turn was
redacted. Pass the rendered block through the shared redact_text seam
with this middleware's app_config.pii_redaction, and declare the setting
in release_policy_parameters().

* fix(context): keep client hints out of stored run config

---------

Co-authored-by: Blair Leng <25675774+BlairLeng@users.noreply.github.com>
Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-10-19 10:41:18 +08:00
yi-coding
a3c65c72e5
fix(skills): preserve source and SQL semantics across data analysis (#6739)
* fix(skills): preserve source and SQL semantics across data analysis

* test(skills): use a portable filename for quoted SQL aliases

* test(guidance): register data-analysis scoped instructions

* test(guidance): keep skill registration independent of new examples

* fix(skills): require explicit data analysis dependency setup

---------

Co-authored-by: xyi74976-del <290725798+xyi74976-del@users.noreply.github.com>
2026-10-19 09:19:01 +08:00
Willem Jiang
a2fcfe0d9b
docs: keep inherited backend guidance within budget (#7087) 2026-10-19 09:03:35 +08:00
ShiYuan Xiong
2254ec6ed5
feat(benchmark): add OpenSandbox provider coverage (#7059) 2026-10-19 07:45:59 +08:00
haotianqi666
81daa578f7
fix(workspace): keep snapshots when files vanish during hashing (#7057)
Co-authored-by: haotianqi666 <250384991+haotianqi666@users.noreply.github.com>
2026-10-19 07:40:52 +08:00
TYY-2005
5f9a521d60
fix(frontend): stop stalled subtask pagination without losing steps (#6729)
* fix(frontend): stop stalled subtask pagination without losing steps

* docs(frontend): note the non-advancing page stop in the subtask backfill

The AGENTS.md backfill sentence only said the loop pages until a short page. It also stops on a full page that fails to advance the after_seq cursor, so record that stop condition as well.

The file sits at 49,149 of its 49,152-byte hard limit, so this is the minimal wording change that fits.

---------

Co-authored-by: TYY-2005 <334029581+TYY-2005@users.noreply.github.com>
2026-10-19 07:32:43 +08:00
LingYi
49adb0edec
fix(frontend): ignore message edit shortcuts during IME composition (#6768)
Co-authored-by: LingYi-Liang <270316896+LingYi-Liang@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-19 07:11:53 +08:00
yetuge
e33024446a
fix(crawl4ai): report a format error for non-object 200 payloads (#7065)
Crawl4AI's client assumed a JSON 200 was an object and indexed it. A reverse
proxy, a version-skewed server or an error document delivered with HTTP 200
can return a list or a bare scalar instead, and then:

- `data.get(...)` raised "'list' object has no attribute 'get'", and the
  generic handler echoed that Python text to the agent;
- a non-string `markdown` field reached `.strip()` and raised the same way
  one level in (["# heading"], 123, {"text": ...}).

Both now report "Crawl4AI returned an unexpected response format" and log
the unexpected type. This matches the sibling providers' contract -- tavily's
`_response_objects` returns a format error for a non-object payload -- and
the same containment the search providers already apply.

Measured: 8 new parameterised cases fail on the base revision (5 for the
payload, 3 for the markdown field); 39 tests in the file, 122 across the
crawl4ai/browserless/sanitization neighbourhood, ruff check/format clean.
Each guard was mutation-checked separately.
2026-10-19 07:07:11 +08:00
tmt-123
7ab42861cc
fix(infoquest): reject invalid extracted fetch content (#6760)
* fix(infoquest): reject invalid extracted fetch content

* fix(infoquest): address fetch response review feedback

Preserve reader_result precedence so malformed primary content is reported instead of silently masked by a secondary field. Include request URLs in invalid/empty content diagnostics and reject non-object JSON before field selection.

* test(infoquest): respect URL redaction in fetch diagnostics

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-19 06:59:40 +08:00
Jay0130-a
62130caa82
fix(skills): strip a leading BOM from the SKILL.md content handed to the model (#6984)
* fix(skills): strip a leading BOM from the SKILL.md content handed to the model

* fix(agents): hash the same BOM-free skill content the middleware injects

* docs(skills): state the BOM mechanism as it is, and sync the encoding convention

* test(skills): name the real BOM mechanism in the activation test docstring

* fix(ci): satisfy lint checks for skill BOM handling

---------

Co-authored-by: Jay0130-a <Jay0130-a@users.noreply.github.com>
Co-authored-by: Jay0130-a <309071492+Jay0130-a@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 22:03:27 +08:00
哈基米
816ebd5563
fix(memory): filter honcho search by category instead of stamping it on unfiltered results (#6981)
* fix(memory): filter honcho search by category instead of stamping it on unfiltered results

HonchoMemoryManager.search sent an identical unfiltered query whether or
not a category was requested, then stamped the requested category on
every hit, so memory_search(category=...) returned unfiltered results
attested to be that category (e.g. a work fact labeled "preference")
— the opposite of the contract, where category filters before the
top_k slice and only facts with that exact category are returned.

Send the category as a real server-side metadata filter (Honcho applies
"metadata" filters in its ranked query, before the limit) and label
each result from the record's own metadata, falling back to "memory"
as before. HonchoClient.search gains a keyword-only filters argument
that is only put on the wire when set, so unscoped requests are
byte-identical to before.

* docs(memory): note honcho category filtering requires stored metadata
2026-10-18 21:55:19 +08:00
Jay0130-a
dfb32ef52b
fix(agents): strip a leading BOM from SOUL.md before prompt injection (#7061)
Co-authored-by: Jay0130-a <Jay0130-a@users.noreply.github.com>
2026-10-18 21:51:39 +08:00
Ryker_Feng
d1d0958bea
feat(skills): add guarded evolution host APIs (#5645)
* feat(skills): add guarded evolution host APIs

* docs(agents): avoid growing saturated guidance chains

* fix(skills): address mutation host review findings

* fix(skills): order name fence before projection lock

* fix(skills): preserve composite toggle lock order

* fix(skills): address catalog and pagination review feedback

* docs: keep subagent guidance within merged instruction budget

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 21:17:02 +08:00
ordinary being
cd0166ae76
fix(memory): drop out-of-range extraction confidence instead of failing the batch (#6971)
* fix(memory): drop out-of-range extraction confidence instead of failing the batch

A model-emitted confidence outside [0, 1] (a percentage such as 95, or 1.5)
passed extraction normalization, which only rejected non-finite values. The
value then reached storage, whose own validation raises for out-of-range
confidence, so one malformed field failed the whole update: the extraction
watermark never advanced and the same paid call repeated on every turn, while
every valid fact, summary and removal in the batch was lost each cycle.

Treat the range like the other malformed shapes in
_normalize_memory_update_fact: drop only that item and let the rest of the
update apply. Read paths already clamp stored corrupt values
(_coerce_source_confidence), so no read-side change is needed.

* fix(memory): bound the confidence drop and make it observable (review)

Review follow-ups on the out-of-range confidence fix:
- range-check integer confidence before float(), so a model emitting 10**400
  is dropped instead of raising OverflowError and failing the batch;
- log the raw value when a fact is dropped, so a systematically
  percentage-scale model is visible instead of silently losing every fact;
- document that a batch which also proposes factsToRemove still fails closed
  when any new fact is dropped (replacementFactIndex positions shift), and
  pin that boundary with a test. Values are documented as dropped, never
  rescaled.
2026-10-18 21:00:49 +08:00
早上肚子疼
51352fd2bc
fix(sandbox): search long lines for literal grep patterns (#6969)
* fix(sandbox): search long lines for literal grep patterns

* test(sandbox): cover literal long lines across providers

* fix(sandbox): bound case-insensitive literal grep work

Replace repeated-prefix regex searches with KMP over single-character Unicode case matches. Keep local long-line literal results and existing regex and preview limits. Add a subprocess-deadline regression for the review counterexample and case-semantics coverage.

* docs(sandbox): keep grep guidance within inherited size limit

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 20:58:35 +08:00
MsNBBc3R
3dbee8f4aa
fix(skill-creator): preserve recorded token counts in benchmarks (#7033)
* fix(skill-creator): preserve recorded token counts in benchmarks

Read timing.json token measurements independently of grading duration and retain recorded zero values. Keep the existing character-count fallback for missing measurements and add benchmark/report regression coverage.

* fix(skillscan): distinguish token metrics from credentials

Recognize typed token counts, numeric benchmark summaries, and signed deltas so skill-creator documentation passes deterministic review. Preserve credential names, string values, duplicate-key detection, and cloud-token findings; add regression coverage and document the boundary.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 20:44:39 +08:00
哈基米
12e619c233
fix(models): tolerate non-mapping when_thinking_enabled.extra_body on the reasoning-contract path (#7039)
* fix(models): tolerate non-mapping when_thinking_enabled.extra_body on the reasoning-contract path

* docs: keep frontend guidance below the hard size limit

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 20:27:17 +08:00
AochenShen99
05db1d66a0
fix(workflows): stop idle agent waits from consuming worker turns (#7024)
* fix(workflows): stop idle agent waits from consuming worker turns

While a script step waited on a child Agent run, the supervisor sent an
empty response frame to the QuickJS worker every 20 ms and each round
trip counted toward the 10,000-turn cap, so any workflow whose Agent
calls kept it waiting for about 209 s failed with "Worker turn limit"
regardless of `timeout_seconds`.

The worker now reports when its job queue has drained, and the
supervisor waits on the pending calls locally while the worker is idle,
still re-checking cancellation and faults every 20 ms, without
exchanging frames. `timeout_seconds` is the only wall-clock bound again;
scripts that spin the job queue still hit the turn cap. The cap is a
module constant so tests can lower it.

Fixes bytedance/deer-flow#7022

* fix(workflows): default a frame without idle to the polling path

Review follow-up: read `idle` with `frame.get("idle", False)` so an
outcome-less frame that lacks the key degrades to the previous poll
behaviour instead of failing the run with a KeyError, and give the
cancel-latency regression test a 3 s budget so store writes and process
reaping on a loaded CI runner cannot flake it.

* fix(workflows): detect worker exit during idle Agent waits

Check worker exit in the existing local poll and clean up pending child runs. Adapt the work-frame budget regression from PR #7025 while retaining precise idle detection.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 20:26:17 +08:00
dependabot[bot]
9e88669851
chore(deps): bump mcp from 1.28.1 to 1.30.0 in /backend (#7067)
Bumps [mcp](https://github.com/modelcontextprotocol/python-sdk) from 1.28.1 to 1.30.0.
- [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases)
- [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md)
- [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.28.1...v1.30.0)

---
updated-dependencies:
- dependency-name: mcp
  dependency-version: 1.30.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-18 19:46:09 +08:00
BUG设计工程师
218fff3c76
fix(artifacts): preserve balanced ASCII delimiters in references (#6967) 2026-10-18 19:23:19 +08:00
yetuge
73f6749068
fix(searxng): contain malformed search payloads instead of crashing on them (#6931)
* fix(searxng): contain malformed search payloads instead of crashing on them

`_search_page` indexed the decoded body directly:

    data = resp.json()
    return data.get("results") or []

so a self-hosted instance that answers HTTP 200 with something other than a
result page -- an HTML or plain-text error page from a reverse proxy or captive
portal -- reached the model as a raw Python error, or as an empty search:

    {"results": "maintenance window"}  -> {"error": "'str' object has no attribute 'get'"}
    {"results": {"a": 1}}              -> {"error": "'str' object has no attribute 'get'"}
    {"results": [1, 2, 3]}             -> {"error": "'int' object has no attribute 'get'"}
    {"results": [None]}                -> {"error": "'NoneType' object has no attribute 'get'"}

Worse, a single malformed entry in an otherwise valid page crashed the whole
call and discarded every good row:

    {"results": [{"title": "ok", ...}, 7]} -> {"error": "'int' object has no attribute 'get'"}

This is the containment rule the sibling providers already follow (brave #6550,
groundroute #6603, tavily #6645, sofya #6719, serper #6779, serply #6796,
tencent-wsa #6799): a payload that is not a result page is a format error, not an
empty result, because "No results found" tells the agent the query legitimately
matched nothing and ends a research loop as "no hits".

The client now raises `SearxngResponseError` for a payload that is not an object,
a body that is not JSON, a `results` container that is not a list, and a
non-empty list holding no object entries, while still skipping non-object entries
in an otherwise valid page so the valid rows survive. `web_search_tool` reports
the provider's format error instead of leaking the exception text. Missing, null,
and empty `results` keep the existing "No results found" behavior.

Diagnostics log the container field name and its type, or the absence of usable
objects -- never the query, credentials, or payload values.

* test(searxng): pin the mid-walk format-error trade-off and fix the docstring

The paginating client aborts the walk and discards already-collected rows when
a later page comes back malformed, which is a different outcome from the
single-request siblings. Pin that trade-off with a test so the behaviour is a
deliberate decision rather than an accident, and reword the
_unexpected_format_error docstring: it promises the query is not echoed, but
the sibling providers all echo it back on purpose and only withhold the
payload.

* test(searxng): log skipped entries and pin the dedicated format-error handler

Two follow-ups from review, both about making the family contract observable
and pinned:

- Skipped non-object entries now log their index ("Skipping malformed SearXNG
  result entry at results[1]"), matching the merged InfoQuest sibling. SearXNG
  is the provider where drift is most expected -- a self-hosted instance serves
  whatever sits behind its reverse proxy -- and a search spans up to
  _MAX_PAGES requests, so rows could otherwise be shed with no trace at all.
- The dedicated `except SearxngResponseError` branch had no test that could
  fail: the exception is a ValueError whose message is already the format-error
  string, so the generic handler returns byte-identical JSON. The new test
  asserts the generic branch's log line is absent, which is what actually
  distinguishes the two paths.

* docs(searxng): name the empty result it actually returns, and pin the dedicated except

Two low findings from the follow-up review:

- The README said missing/null/empty `results` keep the existing "No results
  found" response. That response does not exist in this provider: all three
  produce a JSON empty list, and the string appears only in comments
  explaining what must NOT be reported. The family convention distinguishes
  (Tavily: "`[]` for search, Error: No results found for fetch"; InfoQuest:
  "keep the existing empty result"), so this now reads "keep the existing
  empty result (`[]`)".
- The client-level `except SearxngResponseError: raise` was unpinned: deleting
  it left the whole file green, because the tool-level test mocks the client
  and the payload tests only assert the raise type. The observable regression
  is the generic branch logging "An unexpected error occurred" for every
  contained page, so the existing payload test now asserts that line is absent
  from caplog.

Mutation check: removing the clause now fails 8 parameterised cases (was 33
green); with the clause in place the suite is 33 passed and 91 across the two
provider files, ruff check/format clean.
2026-10-18 13:12:23 +08:00
RongJie G
1098fb4c60
fix(harness): scope deferred tool promotion to user turn (#6977)
* fix(harness): scope deferred tool promotion to user turn

Signed-off-by: CorgiBoyG <111257566+CorgiBoyG@users.noreply.github.com>

* fix(harness): preserve legacy promotions during routing resume

---------

Signed-off-by: CorgiBoyG <111257566+CorgiBoyG@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 11:24:41 +08:00
haotianqi666
07f0d74f70
fix(agents): clear incompatible knowledge defaults on self-update (#6995)
* fix(agents): clear incompatible knowledge defaults on self-update

* fix(agents): share knowledge default cleanup with HTTP updates

---------

Co-authored-by: haotianqi666 <250384991+haotianqi666@users.noreply.github.com>
2026-10-18 11:12:57 +08:00
TAI-YUAN GUO
a1349a1fe1
fix(runtime): persist the effective model name for every run (#6980)
run_agent() only refreshed runs.model_name when the request named a model,
and it read the effective model from agent.metadata, which compiled
LangGraph graphs do not have. The default model (no model_name requested)
and fallback models were therefore never persisted. Use the effective
model that lead-agent assembly already resolves, and write only when it
differs from the stored value.

Supersedes #3110. Fixes #3109.

Co-authored-by: Yi Tang <6054101+yitang@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 11:10:38 +08:00
Nan Gao
8d5f5fd0a8
feat(channels): share encrypted runtime channel config across Gateway instances (#6988)
* feat(channels): share encrypted runtime channel config across Gateway instances

IM bot credentials entered in the web UI lived in each host's plaintext
channels/runtime-config.json, and the router cached the merged config on
app.state for the life of the process. With several Gateway instances a
save or disconnect served by an instance that does not hold the
provider's leader lease never reached the transport holder: it kept the
old credentials until it next started the channel, and a disconnect
could leave the old bot connected.

- New shared table channel_runtime_configs (migration 0044): one row per
  provider with the config block encrypted under DEER_FLOW_CREDENTIALS_KEY,
  a plaintext disconnect tombstone, and a version bumped by every save.
- SqlChannelRuntimeConfigStore replaces the JSON file for sqlite/postgres
  (memory keeps the file); it is synchronous on app_sync_sqlalchemy_url
  because its callers already run in worker threads.
- The router re-reads the store on every request instead of caching the
  merged config, and refuses (503) to save credentials without a key.
- The leader-lease supervisor compares the versions on each renewal tick
  and restarts or stops the holder's transport when a provider's config
  changed; a takeover reads a disconnect instead of reviving cached
  credentials.
- The first start imports an existing runtime-config.json (rows already
  in the table win) and deletes the plaintext file.

* docs(changelog): reference #6988 for the shared runtime channel config

* ci: rerun checks after #6999 fixed the frontend guidance budget on main

* fix(channels): start a held transport that is down when its runtime config changes

When the lease holder's transport was down (a start that failed on the
old credentials), applying a peer-saved config only updated the cached
config, and the transport started on the next lease round: two renewal
ticks instead of the one the docs promise. The apply step now starts it
on the held lease right away, with the lease round's backoff bookkeeping.
2026-10-18 11:10:31 +08:00
Hyeonsang Cho
9b0c7cbc37
fix(browser): never replace an existing output file with browser_screenshot (#6913)
* fix(browser): never replace an existing output file with browser_screenshot

browser_screenshot published straight onto outputs/<name> through the
replacing atomic publisher, so a screenshot saved as report.png silently
replaced the user's own report.png, an earlier capture, or another
default-named capture taken in the same second (the default stem has
second resolution). The sibling web_capture tool already keeps existing
files and takes a -1, -2, ... suffix.

Stage the complete capture under a hidden sibling and hard-link it onto
the first free candidate name. The link is the whole no-overwrite guard:
it fails with FileExistsError when the name exists as anything, so no
name is checked before it is claimed and a concurrent writer cannot be
replaced; the final name never shows a partial file. Staging shares the
outputs directory, so the link is same-filesystem, and like Gateway
uploads other link failures propagate instead of falling back to
replacement. New files keep the publisher's normal creation mode.

Hidden per-action progress frames keep the replacing _write_screenshot:
their names carry microsecond timestamps in a server-owned directory.

* docs(changelog): reference #6913 for the browser_screenshot no-overwrite fix

* fix(browser): refuse a staged screenshot swapped before it is linked

The sandbox can write outputs/, so it can see the hidden staged capture
and replace it with a symlink before the hard link. os.link follows
symlinks by default, which would publish a hard link to the host file as
the screenshot artifact (the replacing publisher this PR moved away from
never followed one).

Stage the capture with an exclusively created descriptor and keep its
identity from fstat, link without following symlinks where the platform
supports it (decided once at import), and require the published name to
be that inode; anything else is removed and the capture fails, like
Gateway uploads' identity check. A failed staging write still leaves no
files behind.

* docs(readme): note that browser_screenshot never replaces an output file

* fix(browser): keep the staged screenshot open until its link is verified

Linux filesystems reuse a freed inode number at once. Closing the staged
capture before linking let a file swapped in at the staged name take the
same number and pass the samestat check, so the symlink and planted-file
swap tests failed on Linux CI while passing on macOS. Hold the staged
descriptor through the link and identity check, as Gateway uploads do,
and require the published name to be a regular file.

* test(browser): race the peer claim against the real publication step

The claimed-name test patched _publish_bytes_atomically, which the
screenshot path no longer calls, so its hook never fired and the race
assertions passed vacuously. Plant the peer file right before os.link or
os.replace lands bytes on a visible name, and assert that the claim
happened, the capture moved to report-1.png, and the peer file survived.

* refactor(capture): share one output-name candidate sequence

browser_screenshot hand-copied web_capture's probe bound and suffix
order, and the copies had already drifted: Browserless retried a held
timestamped name five times, the screenshot tool once. Move the bound,
the retry count and the candidate order into utils/output_names.py and
have both tools iterate it, so the naming cannot drift again. Browserless
behavior is unchanged; its tests now patch the shared module.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 10:52:22 +08:00
DDT
453563af5b
fix(tencent_wsa): fall back when digit-class max_results cannot parse (#6973)
* fix(tencent_wsa): fall back when digit-class max_results cannot parse

str.isdigit() accepts values like "²" and "1²" that int() rejects, so a
configured max_results could crash web_search_tool before the HTTP call.
Parse strings with int() inside try/except (matching sibling providers)
while still rejecting bools and non-integral floats.

Fixes #6921

* docs(tencent_wsa): clarify that all float max_results fall back to default
2026-10-18 10:51:13 +08:00
yinx65588-boop
75de35628d
fix(scheduler): fence expired launch recovery against fresh claims (#6975)
Co-authored-by: yinx65588-boop <yinx65588-boop@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 10:46:32 +08:00
ranjianghong526-hash
606db804d5
fix(sandbox): surface local grep file read failures (#7004)
* fix(sandbox): surface local grep file read failures

* fix(sandbox): skip non-regular local grep candidates

* fix(sandbox): preserve local grep failure context and scan errors

* fix(sandbox): reverse resolve local grep error filenames

---------

Co-authored-by: ranjianghong526-hash <228174768+ranjianghong526-hash@users.noreply.github.com>
2026-10-18 10:46:13 +08:00
NanPan
f96aca5059
fix(checkpoints): skip unserializable Redis history cache entries (#7014)
* fix(checkpoints): skip Redis cache entries that cannot be serialized

* style(checkpoints): format Redis serialization regression

* fix(test): assert Redis serialization payload bytes

* test(checkpoints): format Redis cache serialization regression

* chore: sync frontend agent guidance with upstream size-compliant main

* fix(checkpoints): track Redis cache serialization failures

* test(checkpoints): consolidate Redis serialization fallback regression

* style(tests): format Redis cache regression signature

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 10:30:14 +08:00
TYY-2005
ff020ff6ea
fix(frontend): preserve submitted input during initial stream gap recovery (#6800)
* fix(frontend): preserve submitted input during gap recovery

* docs(frontend): clarify replay recovery input precedence

* docs(frontend): keep replay gap guidance within budget

---------

Co-authored-by: TYY-2005 <TYY-2005@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: TYY-2005 <334029581+TYY-2005@users.noreply.github.com>
2026-10-18 10:29:17 +08:00
Xuehao Xu
c056ee556a
feat(knowledge): add WeKnora read-only extension (phase 3) (#6950)
* feat(knowledge): add WeKnora read-only extension (phase 3)

* fix(weknora): complete extension catalog and harden response validation

* test(extensions): cover WeKnora discovery in browser catalog

* docs(weknora): explain extension and MCP use cases

* docs(weknora): correct guide language and sync Chinese overview

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 10:28:30 +08:00
DEVJHAWAR11
158c54508f
test(backend): fix three more Windows-host test failures (#7008)
- test_local_dev_loopback_bind: the stale-render test compared Git Bash's
  MSYS path (/tmp/...) against a native path; route it through
  _normalize_rendered_path like its sibling tests.
- test_project_documents_repo: TestShelfSnapshot relied on back-to-back
  inserts getting increasing updated_at, which ties on Windows' ~15.6 ms
  clock and falls through to the id tie-break. Pin updated_at explicitly.
- test_multi_worker_run_ownership: the transient-renewal test compared two
  immediate lease timestamps with a strict >; start from a deadline inside
  the lease window, as test_scheduled_goal_handoff already does.
2026-10-18 10:25:26 +08:00
Totoro
eae908c00d
fix(sandbox): make failed AIO teardown safely retryable (#7001)
* fix(sandbox): make failed AIO teardown safely retryable

* docs(sandbox): add sanitized teardown retry verification

* fix(sandbox): clear stale teardown fence on warm reclaim

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 09:57:12 +08:00
ZHOU YANG
6e0ab091e7
fix(infoquest): screen result URLs through the shared public-URL owner (#7031)
* fix(infoquest): screen result URLs through the shared public-URL owner

Three providers already delegate result-URL screening to
community/url_safety.safe_public_result_url; infoquest registers the
image_search tool too but imported the owner zero times, so organic,
news and image rows all handed raw provider URLs to the model. A row the
owner rejects is now dropped rather than raised, keeping the containment
rule #6932 established for this client.

tests: test_infoquest_result_url_safety.py (new, red on main),
test_image_result_url_safety.py (infoquest added to the pinned modules).

* fix(infoquest): reject unscreened search response fallbacks

---------

Co-authored-by: ZHOU YANG <236804849+Zhouyang66639@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 09:55:27 +08:00
Guo-Yixin
1c433c0273
fix(streaming): retire memory buffers without orphaning subscribers (#6992)
* fix(streaming): retire memory buffers and wake existing subscribers

* fix(streaming): preserve end for fully consumed retired streams

* docs: keep frontend guidance within its hard budget

* fix(streaming): report gaps when retirement wins initial attachment
2026-10-18 09:51:31 +08:00
RongJie G
8941fbe872
fix(runtime): bound RunJournal progress teardown (#6990)
Cancel event and progress writes before force-close waits, bound progress settlement, and supervise cancellation-resistant reporters until their late result is observed.

Fixes #6986

Signed-off-by: CorgiBoyG <111257566+CorgiBoyG@users.noreply.github.com>
2026-10-18 09:39:08 +08:00
DennyHo0917
513c03e8c0
feat: add API Route setup wizard preset (#6579)
* feat: add API Route setup wizard preset

Signed-off-by: dennyho0917 <dennyho0917@gmail.com>

* fix: address API Route wizard review feedback

Signed-off-by: dennyho0917 <dennyho0917@gmail.com>

---------

Signed-off-by: dennyho0917 <dennyho0917@gmail.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 09:26:45 +08:00
Louisss
829edac602
fix(tests): pass Windows base system env to the isolated live-policy pytest child (#6439)
* fix(tests): pass Windows base system env to isolated pytest child

The live-test policy regression tests spawn a pytest child with a minimal,
from-scratch env. On Windows that env lacks SystemRoot/TEMP/USERPROFILE, so
the child dies during import asyncio (WinError 10106, winsock provider init)
and tempfile cannot resolve a directory, making the tests fail for
environment reasons instead of testing the opt-in policy.

Extract the env assembly into _child_pytest_env() and pass through a small
allowlist of Windows base system variables (SystemRoot, windir, COMSPEC,
PATHEXT, TEMP, TMP, USERPROFILE) when present. These carry no credentials,
so the isolation contract (no API keys, no real .env) is unchanged, and
POSIX behavior is byte-identical. A new unit test pins the contract: only
the allowlisted base vars may pass through, and SystemRoot must be present
on Windows.

* test(live-policy): seed Windows base env vars in the bootability contract

The contract test asserted TEMP/TMP pass-through without seeding them, so
on Windows hosts that do not export those optional variables (CPython then
falls back to %USERPROFILE%\AppData\Local\Temp) it failed on host
configuration rather than on the helper's contract.

Seed the representative base variables with monkeypatch and assert the
forwarded set exactly, so the isolation contract is deterministic on every
host. Red/green evidence: with TEMP/TMP stripped from the environment the
old assertion fails (extra = {COMSPEC, PATHEXT, SystemRoot, USERPROFILE,
windir}) while the seeded assertion passes.

Addresses the P3 finding in the review of PR #6439.

* ci(tests): cover Windows live-test policy

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 09:24:51 +08:00
Winoren
e1f20a6099
docs(docker): document the locked PyPI index requirement (#7011)
* docs(docker): keep UV_INDEX_URL on the index pinned by uv.lock

The README told users on restricted networks to export
UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple, but the gateway
image installs with `uv sync --locked` and uv.lock is locked to
https://pypi.org/simple, so any other index fails the build with "The
lockfile at `uv.lock` needs to be updated, but `--locked` was provided"
(#7003).

Document the constraint instead of the mirror: name the pinned index,
quote the exact failure, and note that installing from a mirror requires
regenerating the lock or dropping --locked. CONTRIBUTING.md already
showed the working value and gains the same note, and README_pt.md and
README_ko.md carried the same sentence and are updated to match.

* docs(docker): correct the NPM_REGISTRY scope and qualify the repro

NPM_REGISTRY is not frontend-only: the gateway build also consumes it.
docker/docker-compose.yaml (lines 95 and 117) and
docker/docker-compose-dev.yaml (lines 154 and 189) pass it as a build arg
to both the frontend and the gateway image, and backend/Dockerfile declares
ARG NPM_REGISTRY (line 16) and applies it via npm config set registry
(line 43) for the @larksuite/cli install.

While re-verifying the uv repro on this head, also found that the quoted
uv sync --locked --check only discriminates between indexes when the local
environment is already in sync; if the environment is stale it reports the
stale environment for either index. Point at uv lock --check for that case.

Reported-by: willem-bd

---------

Co-authored-by: D41910 <d41910@users.noreply.github.com>
2026-10-18 08:27:21 +08:00
NanPan
e6a3c9ffb7
fix(checkpoints): serialize shared memory history cache operations (#6708)
* fix(checkpoints): serialize shared memory history cache operations

* fix(checkpoints): align memory cache contract with locking

* test(checkpoints): make cache race regression deterministic

* docs: remove overlapping cache note from memory-cache PR
2026-10-18 08:13:27 +08:00