3836 Commits

Author SHA1 Message Date
Grapette.L
8df4286f41
fix(channels): split Telegram messages by UTF-16 code units, not code points (#6067)
* fix(channels): split Telegram messages by UTF-16 code units, not code points

* perf(telegram): clip stream previews without walking the whole reply

Every cumulative stream update measured the full answer and then split the full
answer to keep only its first chunk, so a long reply cost two complete passes
per update on the async channel path. _first_utf16_chunk stops at the first
character that does not fit, and the preview is built from that bounded prefix.

The tests now measure chunk length with the UTF-16 codec instead of a copy of
the production formula, cover the width helper and the bounded clip directly,
and fail if the preview stops being bounded by the limit.
2026-10-01 10:24:25 +08:00
SuperSgdk
f74a290c42
fix(uploads): reject reserved staging filenames before batch ingestion (#6122)
* fix(uploads): reject filenames reserved for temporary staging

Guard thread and shelf upload names, validate SDK batches before copying, and preserve legacy shelf downloads. Add regression coverage and document the reserved staging namespace.

* fix(uploads): report reserved names before HTTP batch ingestion

Preflight reserved staging basenames before thread storage or sandbox setup. Return HTTP 400 with a rename hint; add HTTP and strict blocking-I/O regressions and update upload documentation.

* fix(uploads): reject Windows-style staging basenames on POSIX

Normalize backslashes before the HTTP reserved-name preflight. Add POSIX basename regressions for single and mixed batches and strict blocking-I/O coverage. Clarify the preflight scope and upload documentation.

Follow-up to the review of PR #6122; AI-assisted with OpenAI Codex.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-01 10:22:38 +08:00
PeaceMaker-best
90d466c917
feat(mcp): propagate cache resets across workers (#6126)
* feat(mcp): propagate cache resets across workers

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

* docs: keep MCP reset guidance within instruction budgets

---------

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-01 10:21:24 +08:00
Guo-Yixin
4d5607229e
fix(subagents): preserve direct-return tool results (#6083)
* fix(subagents): preserve direct-return tool results

* fix(subagents): include middleware tools and surface direct failures
2026-10-01 10:05:39 +08:00
Hyeonsang Cho
fe22e3899a
fix(channels): keep blocking SDK teardown off the event loop in stop() (#6134)
* fix(channels): keep blocking SDK teardown off the event loop in stop()

SlackChannel.stop() called SocketModeClient.close() inline. close() joins
the SDK's message-processor thread (~0.7s every call, measured) and waits
for in-flight listeners, whose blocking Web API calls can run up to the
WebClient timeout, so every run, stream and channel on the Gateway stalled
during shutdown and POST /api/channels/{name}/restart.

Feishu and DingTalk had the same shape: they joined their SDK threads
inline with a 5s timeout, and since those threads only return on a fatal
error, the join normally froze the loop for the full 5s.

The teardown now runs through asyncio.to_thread. Slack detaches the client
first so a still-queued connect skips it, and shields and tracks the close
task so a cancelled stop() leaves it running and a retried stop() awaits
the same close instead of closing twice.

tests/blocking_io/test_channel_stop_offloop.py anchors all three stop()
paths to the strict Blockbuster gate, driving a real slack-sdk client.

* docs(changelog): reference #6134

* fix(channels): join the Discord client thread off the event loop in stop()

DiscordChannel.stop() still joined its client thread inline with a 10s
timeout. The thread usually exits right after the cross-loop client close,
but a timed-out close or a slow _run_client() drain keeps it alive for the
whole timeout, stalling the shared Gateway loop the same way the Slack,
Feishu and DingTalk teardown did. Run the join through asyncio.to_thread so
stop() matches the channels/AGENTS.md teardown rule, and add Discord to the
strict Blockbuster anchor.
2026-10-01 10:00:53 +08:00
lihongyuan99
241a7b3b9b
fix(frontend): key the Julia entry of extensionMap by its extension (#6130)
extensionMap is keyed by extension and valued by language, but the Julia row
was written backwards as `julia: "jl"`. checkCodeFile tests membership with
`extension in extensionMap`, so ".jl" never matched and a Julia artifact was
not a code file at all, while ".julia" resolved to a language named "jl".

Add both `jl: "julia"` and `julia: "julia"`, the same pair form the map already
uses for haskell/hs and elixir/ex, so no extension that classified before stops
classifying.
2026-10-01 09:58:23 +08:00
Willem Jiang
5c6c074542
chore(doc): updated the CHANGLOG with lasted changes (#6121) 2026-10-01 09:38:04 +08:00
Li-john1021
90c5d526e2
fix(paths): reject Windows reserved names on host-visible paths (#6102)
* fix(paths): reject Windows reserved names on host-visible paths

Uploads, skill support files, and sandbox path checks now share one helper that rejects CON, PRN, AUX, NUL, COM1-COM9, and LPT1-LPT9 (including names like con.txt) and components that end in a dot or space. The check is not limited to Windows so a Linux host cannot store a name that breaks when the same tree is used on Windows.

* fix(paths): validate quoted local bash paths as whole words

The absolute-path scan stopped at whitespace, so a quoted upload name such as report. final.txt or CON notes.txt was rejected as a truncated segment. Local bash checks now use the shlex argument for traversal and Windows-incompatible segments.

* fix(paths): do not block reads of existing host names

* style(paths): format with ruff 0.15.12
2026-10-01 09:11:19 +08:00
creed
9c75236ea1
fix(frontend): strip closing hashes from web fetch titles (#6103)
* fix(frontend): strip closing hashes from web fetch titles

Signed-off-by: 97three <2212371308@qq.com>

* fix(frontend): scan web fetch heading suffixes in linear time

Signed-off-by: 97three <2212371308@qq.com>

---------

Signed-off-by: 97three <2212371308@qq.com>
2026-10-01 08:51:40 +08:00
Shxiao
52b21d13ac
fix(middleware): validate exact byte size on sandbox tool output externalization (#6112)
* fix(middleware): validate exact byte size on sandbox tool output externalization

* test(middleware): simulate partial write in sandbox externalization size test

* Fix PR number in CHANGELOG.md

Updated the changelog to reflect the correct issue number for a truncated file write.

* Update CHANGELOG_zh.md for issue #6112

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-01 08:38:08 +08:00
kuse
ee3159a01d
fix(threads): re-anchor re-persisted history rows to their earliest seq (#6128)
* fix(threads): re-anchor re-persisted history rows to their earliest seq

reconcileThreadHistoryRows collapsed a repeated (run_id, identity) to
its latest-seq copy, deleting the earliest-seq row before
buildVisibleHistoryMessages could apply its earliest-seq-wins
anchoring. A message re-persisted under a newer seq therefore jumped
to the tail of the thread, contradicting the documented contract in
this function's docstring, in buildVisibleHistoryMessages, and in the
backend get_message_seqs earliest-seq-wins rule.

Re-anchor the surviving row to the earliest seq its identity held in
the merged window after deduping, and make the retained-history
stability check value-based so re-anchored rows do not force a state
update on every pass.

Fixes #6125

* fix(threads): address history reconciliation review

---------

Co-authored-by: liwenjie200543 <175601277+liwenjie200543@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-01 08:20:42 +08:00
inchang-ing
4ced9dddf5
fix(vllm): fall back to legacy reasoning_content field (#6048)
* fix(vllm): fall back to legacy reasoning_content field

* fix(vllm): document the reasoning_content fallback and pin precedence

The previous change set left the docs describing the provider as
preserving only `reasoning`:

- the module docstring now mentions the legacy `reasoning_content`
  wire field, and the vLLM Provider bullet in models/AGENTS.md is
  extended to match the repo's documentation-update policy;

- _create_chat_result evaluated choice["message"] twice, once per key;
  hoist it into a single local and read both keys from it;

- two regression tests pin that a payload carrying both fields keeps
  `reasoning`, so a future refactor cannot silently invert the
  precedence (the existing tests only covered the absent-`reasoning`
  case).

Reported-by: willem-bd

* refactor(vllm): share reasoning fallback and cover follow-up payloads

---------

Co-authored-by: inchang-ing <197932532+inchang-ing@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-01 08:12:32 +08:00
NanPan
1414e9fae7
fix(memory): drain persistent mutations on cancellation (#6092)
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-01 07:59:28 +08:00
sjr666666
357bea8285
docs(zh): add the missing Startup Modes section to README_zh (#6129)
Mirror the English README's Startup Modes section (introduced and refined in #5995) into the Chinese version: the startup mode matrix, the stop/restart commands, and the SKIP_FRONTEND_BUILD opt-in note. README_zh previously had no documented way to run services in daemon or production mode or to stop/restart them.

Co-authored-by: sjr666666 <204130665+sjr666666@users.noreply.github.com>
2026-10-01 07:57:14 +08:00
yetuge
300abb36d2
fix(auth): reject a non-ASCII ID token nonce instead of raising (#6115)
* fix(auth): reject a non-ASCII ID token nonce instead of raising

The nonce comparison in validate_id_token still used the local
secrets.compare_digest on str operands, which raises TypeError when either
side holds non-ASCII characters. The nonce claim is provider-controlled
text, so such a provider response turned the callback's sso_failed redirect
(OIDCValidationError) into an unhandled 500.

Route it through the shared constant_time_equals helper #6076 introduced
for the same five compare sites, and drop the now-unused local helper.

Signed-off-by: yetuge <2219677952@qq.com>

* fix(auth): reject a non-string ID token nonce claim like a mismatch

A provider returning a truthy non-string nonce claim (e.g. an int)
slipped past the missing-claim guard and crashed the comparison helper
with AttributeError, which the callback turned into a 500 instead of
the sso_failed redirect. Guard the claim type before comparing so every
malformed nonce takes the same OIDCValidationError path.

Signed-off-by: yetuge <2219677952@qq.com>

---------

Signed-off-by: yetuge <2219677952@qq.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-01 07:43:16 +08:00
Storm
69004bd52c
fix(sandbox): give fresh bash.exec runs immediate stdin EOF (#6117)
Why this change:
In Lark broker mode the shim forwards piped stdin to the broker only
after EOF. AIO's /v1/bash transport keeps a subprocess stdin pipe open
for writes, so a bare `lark-cli ...` never saw EOF and the shim blocked
forever on input that would never arrive. An earlier brace-group/eval
approach fixed the hang but changed top-level parsing (aliases/extglob)
or let a trailing backslash consume the appended delimiter.

What changed:
- _run_bash_exec prefixes the original command with `exec < /dev/null`
  on its own line, giving the fresh, per-invocation session immediate
  EOF on default stdin. Explicit pipes/heredocs/files still override
  fd0. The plain prefix keeps top-level parsing and appends no
  delimiter a trailing backslash can consume.
- The persistent /v1/shell PTY transport keeps commands unchanged; the
  shim already treats TTY stdin as no input.
- The shim drains non-TTY stdin under grace/tail idle budgets
  (DEERFLOW_LARK_BROKER_STDIN_{GRACE,TAIL}_SECONDS, default 2s, valid
  (0, 600]); an idle pipe exits 124 without contacting the broker, and
  read errors fail loudly instead of becoming empty input.
- Broker logs each exec as argc/exit/elapsed only (never argument
  values) and warns on concurrency-cap rejections.

Problem solved:
Lark commands without explicit stdin hung indefinitely in broker mode,
stalled producers could execute partial writes, and broker logs risked
capturing argument values. Docs updated: README, harness AGENTS.md,
docker/lark-cli-broker README.
2026-10-01 07:34:32 +08:00
Wenchao An
4c05e88c52
feat: add inline @ references with multiple skill activation (#6063)
* feat(frontend): unify composer references with an @ picker

* feat: edit references inline and activate multiple skills

* docs: describe inline references and multiple skill selection

* fix: prevent sent drafts from returning and repair CI checks

* fix: reconcile inline references and address review feedback

* fix: preserve references through discovery errors and polish
2026-09-30 23:09:05 +08:00
Daoyuan Li
5c67d61d65
fix(frontend): detect artifact types from file basenames (#6091) 2026-09-30 20:11:26 +08:00
ZJPex
8e282c4c24
fix: preserve existing task notes during parallel additions (#5954)
* fix: preserve task notes during parallel additions

* docs: scope task note guidance to continuity module

* fix: reserve task note slots using resolved execution keys

Signed-off-by: ZJPex <3258236335@qq.com>

* refactor: share task-note argument key and align documentation language

Signed-off-by: ZJPex <3258236335@qq.com>

* fix: ignore malformed sibling arguments in task-note reservations

Signed-off-by: ZJPex <3258236335@qq.com>

* fix: skip structurally invalid task-note reservations

Signed-off-by: ZJPex <3258236335@qq.com>

---------

Signed-off-by: ZJPex <3258236335@qq.com>
2026-09-30 20:03:35 +08:00
Fish
2000bffa8a
fix(harness): anchor DeerFlowClient agent-name validation with fullmatch (#6104) 2026-09-30 19:51:47 +08:00
TAI-YUAN GUO
25671538af
fix(cli): exit non-zero when a headless run ends in an LLM error fallback (#6056)
* fix(cli): exit non-zero when a headless run ends in an LLM error fallback

LLMErrorHandlingMiddleware turns provider failures (e.g. an expired
credential returning 401) into an AIMessage flagged
`deerflow_error_fallback` instead of raising. The Gateway run status and
the subagent executor already treat that flag as a failure. #5963 gave
`deerflow --print` / `--json` an error boundary for raised exceptions,
but a flagged fallback never raises, so both modes still printed the
fallback text and exited 0.

Both headless modes now follow #5963's failure contract when the run's
final AI message carries the flag: `--print` keeps the fallback text on
stdout and adds an `Error:` line on stderr; `--json` appends the terminal
`{"type": "error"}` record. Both exit 1.

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

* refactor(cli): share final-answer accumulation with the embedded client

* docs: scope headless accumulator guidance to TUI

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 19:48:32 +08:00
hataa
1cd3eb742b
feat(authz): enforce skill authorization at assembly and slash-activation (#4063 Phase 3) (#4541)
* feat(authz): enforce skill authorization at assembly and slash-activation (#4063 Phase 3)

Phase 3 / Skills — the second of three resource-type PRs (Models, Skills,
Sandbox). The RBAC provider already maps "skill" → config key "skills"
(rbac.py _RESOURCE_POLICY_KEYS), so no schema change is needed.

Layer 1 (assembly-time, mirrors Phase 1B):
- filter_available_skills_by_authorization() in skill_filter.py filters the
  skill-name allowlist (set[str] | None) by the provider's "skill" policy.
  In lead agent, runs after _available_skill_names() so the catalog
  (describe_skill) and SkillActivationMiddleware share one filtered set.
  In subagent executor _load_skills(), filters config.skills before disk load.
- filter_resources_by_authorization() in enforcement.py — generic version of
  filter_tools_by_authorization for any resource with a name attribute.

Layer 2 (slash-activation):
- No new middleware code needed. SkillActivationMiddleware._resolve_activation
  already checks "reference.name not in self._available_skills". Since Layer 1
  filters the allowlist, denied skills cannot be slash-activated.

authorization.enabled: false is a complete no-op. available_skills=None (no
agent allowlist) + enabled: resolves all skill names from config and filters.
12 new tests + 243 existing authz/skills tests pass.

* test(subagents): wait on the chained future instead of polling done() in caller-loop teardown test

* feat(authz): enforce action-scoped skill:activate at runtime and reuse the catalog loader for filter candidates

* feat(authz): arm skill:activate on the subagent chain, unify the client skill-surface user, and record the Layer 2 reversal in the design notes

* chore(authz): align the subagent filter's fallback user with the chain's bucket convention

* feat(authz): gate autonomous skill loading on skill:activate, close the bootstrap assembly leak, and reuse one provider per subagent assembly

Review round 4 (willem-bd, CHANGES_REQUESTED):

- [P1] Autonomous load path: describe_skill filters its match set on the
  action-scoped skill:activate decision (all-denied is indistinguishable
  from "no match"), and ToolErrorHandlingMiddleware stamps skill_context
  entries only when the action allows it — a denied SKILL.md read gets a
  skill_context_denied marker instead, so durable context, allowed-tools
  policy, and autonomous secret bindings never activate a denied skill.
  File visibility itself stays Layer 1 (sandbox-projection semantics).
- [P1] Bootstrap assembly: build_middlewares/apply_prompt_template now
  receive the authorization-filtered set instead of the raw
  _BOOTSTRAP_SKILL_NAMES, and an empty skill_names index is preserved
  when deferred discovery is on rather than round-tripping through None
  into the legacy full-metadata prompt that re-advertises a denied
  skill (deferred-off keeps the legacy rendering; the filtered empty
  allowlist blanks it on denial).
- [P2] SubagentExecutor resolves ResolvedSkillAuthorization once per
  assembly (_resolve_skill_authorization, memoized per executor) and
  shares it across the Layer 1 filter (authorization=), the activation
  middleware, and both new gates — uncached provider factories can no
  longer hand the layers different instances/policy snapshots.

All gates share one helper (skill_filter.skill_activation_allowed) so
slash, describe, and skill-file-load cannot drift; provider errors keep
the configured fail-closed/fail-open policy. skill_authorization is
threaded through _build_runtime_middlewares into both runtime builders.

Tests: describe gating (deny/fail-closed/fail-open/disabled), read
stamping (denied marker vs entry) + extract_skills skip without warning,
bootstrap three-way param (allow/deny x deferred on/off), lead + subagent
chain wiring for the stamp gate, and the distinct-instance provider
factory repro. Each fix is mutation-verified (reverting it fails its
test). Full A/B suite run: no new failures vs HEAD.

* feat(authz): re-authorize persisted skill_context before reuse and route async paths through aauthorize

Review round 5 (willem-bd):

- [P1] Persisted entries: skill_context survives across runs, so both
  consumers now re-check the action-scoped skill:activate decision before
  reuse. SkillToolPolicyMiddleware._active_skills_for_paths skips a
  denied skill (same skip as a disabled/unallowlisted one; all-denied
  keeps the existing fail-closed-to-builtins semantics) and
  _in_context_secret_sources binds no secrets for a denied entry.
  skill_authorization is threaded into SkillToolPolicyMiddleware on both
  chains.
- [P2] Async provider API: skill_activation_allowed_async mirrors the sync
  helper through await provider.aauthorize() (same pattern as
  authorize_sandbox_execution_async). Async hooks precompute an
  activation_decisions map on the event loop (slash target + persisted
  entry names, one batch of awaits) and hand it to the worker-thread
  handler, which consults the map instead of the sync API:
  SkillActivationMiddleware.awrap_model_call (activation + secret
  binding), ToolErrorHandlingMiddleware.awrap_tool_call (_amaybe_stamp
  awaits the decision before stamping), and SkillToolPolicyMiddleware's
  awrap_model_call/awrap_tool_call (entry names + slash source name).
  describe_skill becomes a StructuredTool with both a sync function
  (invoke -> authorize) and a coroutine (ainvoke -> aauthorize), so async
  agent execution never routes a loop-affine provider through the sync
  API while sync callers keep their semantics.

Tests: allow-read-then-deny-next-run regressions for secret binding and
allowed-tools (partial deny does not poison, all-denied fails closed to
builtins), and an _AsyncOnlyProvider (sync authorize always fails,
aauthorize works) driving four async-path assertions (activation, stamp
allow/deny, describe, tool policy). All six fixes are mutation-verified;
full A/B suite run against HEAD shows no new failures.

* fix(authz): authorize skill activation by declared Skill.name, not directory basename

ShenAC-SAC's P2: bundled skills may declare a name that differs from their
directory (skills/public/vercel-deploy-claimable declares name: vercel-deploy),
but the runtime gates derived authorization targets from the container path.

- New deerflow/skills/container_registry.py: build_container_path_registry()
  + canonical_skill_name() — the one path-to-Skill.name resolution shared by
  the activation, tool-policy, and read-stamping middlewares.
- ToolErrorHandlingMiddleware resolves the read path through the (user-scoped,
  via new user_id kwarg threaded from both runtime builders) registry before
  the skill:activate decision, sync and async (registry lookup in a worker
  thread, aauthorize() on the loop). RBAC allow by the declared name now
  activates; allow by the directory name does not.
- SkillToolPolicyMiddleware async hooks canonicalize the policy paths first
  (three-phase: thread registry -> loop aauthorize -> thread filter reusing
  the registry; the cached per-model-step decision still short-circuits),
  so the decision map is keyed by the names _active_skills_for_paths checks —
  no more map miss falling back to the sync provider API from the thread.
  _entry_names removed: persisted entry["name"] is path-derived by
  construction; entries are resolved by path instead.
- SkillActivationMiddleware collects slash names (already canonical) plus
  persisted entry *paths*, canonicalized off-loop via
  _canonical_names_for_paths().

Regressions (test_skills_authorization.py 45->50): sync stamp target, RBAC
allow declared-name/deny directory-name both directions, async stamp via
_AsyncOnlyProvider, composed async slash -> tool-policy keeps the skill's
tools with zero sync fallback, persisted-entry secret binding survives the
name mismatch. All three mutations (stamp target, policy map key, entry
canonicalization) fail their tests.

* fix(authz): close the sync-fallback class on skill activation paths

willem-bd's two P2s on the canonical-name fix, plus a fourth instance of
the same class found while sweeping the membership:

- SkillToolPolicyMiddleware: a transient prepass registry failure is now
  preserved (_REGISTRY_LOAD_FAILED marker) — the worker-side filter applies
  its own fail-closed treatment instead of silently retrying storage,
  which recovered skills absent from the decision map and fell back to
  the synchronous provider API from the thread (fail-open retaining a
  denied skill's allowed-tools).
- SkillActivationMiddleware: an empty entry-path list no longer pays a
  full skill-tree scan on ordinary authorization-enabled async model
  steps (previously a passive path with zero registry I/O).
- 4th instance (reproduced with the PR fixtures, then fixed): the entry
  prepass failure on the secret-binding path let the consumer-side
  _load_skill_registry_by_path silently recover and resolve entry names
  outside the decision map — the same sync fallback, fail-open binding
  the aauthorize-denied skill's declared secret. The fix goes one step
  further than failure propagation: the prepass hands down the registry
  *snapshot* the decision map was keyed by, and entry sources resolve
  against that snapshot, so a map miss is impossible by construction
  (this also closes the narrower window where both loads succeed but
  disagree because storage changed mid-step). The slash source is
  deliberately unaffected: it does not consult the decision map
  (validated once at activation) and keeps its fresh per-call load.
- Class membership swept: skill_activation_allowed() sync call sites are
  exactly four (stamping sync path and describe's sync implementation are
  legitimately sync); the only two async-reachable fallback points (the
  _activation_allowed helpers) are both construction-guarded now.
  Structural hardening (typed async-batch decision object where a map
  miss is fail-closed by construction) recorded in the plan notes as a
  follow-up, anchored at those two helpers.
- _RegistryArg type alias replaces the degenerate dict|object|None union
  in the policy middleware signatures.

Tests (test_skills_authorization.py 50->54): transient prepass failure ->
builtins-only with exactly one storage attempt and zero provider calls;
ordinary async step -> zero registry scans; two-call scenario -> entry
binding zeroed while the slash binding survives; entries resolve against
the prepass snapshot under a mid-step rename. Mutations verified red:
marker reverted to None, over-broad slash kill, snapshot bypass.

* fix(authz): correct secret-binding reference points and gate the rendered skill reminder

Review round 9 (willem-bd) plus construction-audit follow-ups on the same
class — every async divergence between two loads of the same data now has
an explicit, pinned reference point:

- Slash secret source resolves from a FRESH post-activation registry: the
  prepass snapshot is taken before the aauthorize awaits, so binding the
  slash source from it would activate NEW_KEY content while injecting
  OLD_KEY when a skill's declared secrets change in that window. The
  'one scan per step' bonus that motivated sharing the snapshot is
  withdrawn — consistency wins. The two secret sources resolve
  independently: a transient fresh-load failure zeroes only the slash
  binding; entries keep binding from their own snapshot.
- Same-skill dominance: when a skill is both the slash source (fresh) and
  a persisted entry (snapshot), only the activation-era declaration binds
  — unioning both eras injected a secret the skill no longer declares.
  Aligns with the tool-policy rule (slash activation dominates for the
  run); different-skill entries still stack additively.
- The rendered 'Active skills' reminder (DurableContextMiddleware) no
  longer advertises skills the provider denies — surface consistency with
  describe_skill. Publication/consumption split: the activation
  middleware publishes per-step path-keyed skill:activate decisions into
  the run context (async: reused from the prepass snapshot, zero extra
  I/O; sync: one scan + authorize() only when entries exist — passive
  steps stay scan-free), the renderer consumes them and filters its
  render copy (state untouched; unpublished = permissive, matching the
  other run-context carriers). New carrier key listed in
  REDACTED_CONTEXT_KEYS with a redaction regression.
- Wiring regressions on both chains for everything added above (user_id
  to the stamp gate, skill_authorization + token pairing + activation-
  before-durable ordering to the durable renderer): each mutation
  verified red (M4-M13 across this change set).
- Tests 54->65: slash post-activation lookup, source independence,
  dominance, sync-chain sync-API path, render filter (async/sync/
  disabled with load-count assertions), chain wiring x2, redaction
  listing. Plan notes record rounds 9-2..6 including the reference-point
  post-mortem and the audit axes exhausted.

* fix(authz): anchor same-skill dominance on the slash identity, not binding results

Review round 10 (willem-bd) plus a self-audit extension the new checklist
items caught in the first fix itself:

- R10 (P2): the same-skill exclusion was derived from successfully BOUND
  slash sources. When the prepass snapshots required-secrets [OLD_KEY]
  and the operator strips the declaration during the await window, the
  slash source binds nothing (required_secrets gate) — the exclusion set
  stayed empty and the stale entry view kept injecting OLD_KEY for a
  skill that now declares no secrets. The exclusion now derives from the
  AUTHENTICATED slash activation identity (run-context source path,
  owner-token checked): the identity's declared name resolves from the
  fresh registry first, then the entry snapshot, and joins the exclusion
  set regardless of binding success.
- Self-audit (value->gate->downstream on the fix itself): the declared
  name can also change mid-run (same path, foo->bar). A name-anchored
  exclusion then holds only the new name while the snapshot entry
  resolves the same path to the old one — both eras bound again. The
  PATH is the stable identity shared by the activation and every earlier
  read: entries at the slash identity's path are now excluded before
  registry resolution (exclude_paths), independent of what either
  version calls the skill; the name-based set remains for cross-path
  same-name shadowing.

Tests 65->67: no-secrets-era dominance (ACTIVE secrets None under the
OLD_KEY-to-empty interleaving), path-anchored dominance across a mid-run
rename (only the activation-era key binds). Mutations verified red:
identity-derived exclusion disabled (M14), path exclusion disabled
(M16).

* test(authz): pass explicit config to lead middleware wiring test

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 19:45:07 +08:00
yetuge
8432e4b71a
fix(gateway): drain agent and user-profile writes across cancellation (#6087)
* fix(gateway): drain agent and user-profile writes across cancellation

A client that disconnects mid-request cancels the handler task. On the four
persistent-write endpoints of /api/agents - create, update, delete, and the
USER.md write - a bare asyncio.to_thread then either cancels a still-queued
worker (the write silently never happens) or detaches from a running one and
drops its failure, because the handler's except never runs.

Route the four writes through await_drained like the managed-subagent (#6023),
managed-model (#6024), and custom-skill (#6078) mutations, logging a lost
worker failure with the exception type only before the drain consumes it.
Expected domain errors (AgentExistsError on create) re-raise unlogged; the
caller still maps them to a 409 while connected. Reads stay bare: abandoning
them loses nothing.

Signed-off-by: yetuge <2219677952@qq.com>

* fix(gateway): address review nits on the agents write drain

Make expected_errors a positional-only parameter before *args so the
ParamSpec signature is checker-clean under PEP 612 (the one call site with
extra worker arguments passes () explicitly), acknowledge that
non-cancelled failures log twice like artifacts.py's drained commit does,
and drop the unused request stub helper from the tests.

Signed-off-by: yetuge <2219677952@qq.com>

---------

Signed-off-by: yetuge <2219677952@qq.com>
2026-09-30 19:43:01 +08:00
NanPan
4343ced685
fix(mcp): drain personal config mutations on cancellation (#6093)
* fix(mcp): drain personal config mutations on cancellation

* test(mcp): cover cancelled personal config mutation

* test(mcp): remove unused cancellation event

* test: remove unused event loop from personal MCP cancellation regression

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 19:39:11 +08:00
Grapette.L
f61372764e
fix(agents): anchor agent-name validation against a trailing newline (#5960)
* fix(agents): anchor agent-name validation against a trailing newline

Two copies of the shared ^[A-Za-z0-9-]+$ grammar still used re.match,
whose $ also matches before a final newline, while every other copy uses
fullmatch. POST /api/agents {"name": "reviewer\n"} therefore passed the
router guard and died inside the strict file store as an HTTP 500, and
DeerMem accepted the same name as an agents/{name}/facts directory.

* test(agents): state which trailing-newline params the regression actually covered

.match only accepted "reviewer\n" on main; "reviewer\n\n" and "reviewer \n"
were already rejected, so the docstrings should not read as if every param in
these parametrizations was broken before the fix.
2026-09-30 19:38:09 +08:00
Anton Dziatkovskii
a83aebe702
fix(docs): harness docs document an async client API that does not exist (#5843)
* docs: replace nonexistent async client API in harness docs

DeerFlowClient has no async methods: client.astream() and client.ainvoke()
do not exist, so the "Create Your First Harness" tutorial and the harness
integration guide fail with AttributeError on their first call. Overrides
were also shown as a nested config={"configurable": {...}} dict, which
stream()/chat() swallow via **kwargs and silently ignore.

Switch the examples to the shipping API (sync stream()/chat(), flat kwarg
overrides, agent_name as a constructor arg) in the EN and ZH copies of both
pages. backend/docs/STREAMING.md is left alone: its astream mentions are
design discussion and LangGraph internals, not user-facing examples.

Assisted-by: Claude Code / claude-opus-5
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Dziatkovskii <194927794+tonydzi@users.noreply.github.com>

* docs: serialize StreamEvent before StreamingResponse, use real event names

Review follow-up on the two points raised in #5843.

P2 — the streaming examples handed a generator of `StreamEvent` dataclasses
straight to Starlette. `StreamingResponse` calls `.encode()` on any chunk that
is not `str`/`bytes`, so the first event raised
`AttributeError: 'StreamEvent' object has no attribute 'encode'`. Both
occurrences (the `run_agent` snippet and the inline FastAPI route, which shipped
the dataclass `repr()` through an f-string) now serialize to SSE frames built
from `event.type` / `event.data`.

P3 — the tutorial showed mapping literals with `messages` and `thread_state`,
neither of which exists. The real contract is
`StreamEventType = Literal["values", "messages-tuple", "custom", "end"]`
(backend/packages/harness/deerflow/client.py:128) and the stream yields
dataclasses, so the section now shows `StreamEvent(...)` with the real names
plus a consuming loop.

The zh tutorial had no equivalent section at all; added for parity.

Assisted-by: Claude Code / claude-opus-5
Machine: A-Mac16-2019-PaloAlto
Account: tonydzi
Operator: robot:pr-reply-daily

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

* docs: drop the nonexistent load_config() from every harness snippet

Review follow-up on #5843 (@willem-bd): the rewritten tutorial still opened
with `from deerflow.config import load_config`, which raises ImportError
before `client.stream(...)` is ever reached — `deerflow/config/__init__.py`
exports `get_app_config` and friends, and the string `load_config` does not
appear anywhere under `backend/packages/harness/deerflow/` (the only near
match is `load_configured_extension_middlewares`).

The call was also unnecessary: `DeerFlowClient.__init__` already resolves
configuration itself (client.py:220-222 — `reload_app_config(config_path)`
when a path is given, otherwise `get_app_config()`, which auto-loads from
the default path or DEER_FLOW_CONFIG_PATH).

Fixed the class rather than the one snippet you flagged: the same dead
import appeared 10 times across 4 pages (EN + ZH tutorial and integration
guide). Every one is gone. The "configuration in embedded mode" section now
shows the API that exists — `DeerFlowClient(config_path="...")` — instead of
`load_config(config_path="...")`.

Also from the review:
- the `end` example showed only `total_tokens`; `stream()` always yields
  input/output/total (client.py:920), and the snippet right below prints the
  whole usage dict, so a reader saw keys the docs omitted. Both EN and ZH now
  show the full payload, and the `values` example visibly elides `artifacts`
  and `summary_text` instead of pretending they are absent.
- the two SSE comments on the ZH integration page stayed English while the
  example above them was translated; translated.

Verified statically, not executed: `grep` for the symbol across the harness
package (zero hits) and a read of `DeerFlowClient.__init__`. I did not stand
up a backend to run the tutorial end to end.

Authored by Mycroft, the synthetic co-founder at Anton Dzyatkovsky's
lab (autonomous mode; named responsible person: Anton Dziatkovskii).

Assisted-by: Claude Code / Opus 5 (review response, implementation)
Machine: MacBook-Anton
Account: a
Operator: mycroft (autonomous routine github-thread-watch)
Signed-off-by: tonydzi <194927794+tonydzi@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: fix native artifact SSE encoding and custom config advice

---------

Signed-off-by: Anton Dziatkovskii <194927794+tonydzi@users.noreply.github.com>
Signed-off-by: tonydzi <194927794+tonydzi@users.noreply.github.com>
Co-authored-by: Anton Dziatkovskii <194927794+tonydzi@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 19:36:54 +08:00
Grapette.L
0110845f66
fix(config): guard the recovered stream cleanup delay like its heartbeat sibling (#5850) 2026-09-30 19:26:50 +08:00
Totoro
295437692a
fix(agent,goal): stop coaching scripts when no bash tool is bound, and show the goal evaluator Human Input Card answers (#6082)
* fix(agent): drop script guidance from the lead prompt when no bash tool is bound

The workspace section told the agent how to write scripts and commands
("prefer relative paths", "avoid hardcoding /mnt/user-data/... inside
generated scripts") even when no tool could run them. With the default
LocalSandboxProvider host bash is off and the bash tool is removed, so
agents wrote helper scripts nothing could execute, then stalled or
computed by hand.

apply_prompt_template() takes bash_available (default True, so other
callers keep today's text). The lead agent (both assembly branches) and
the embedded client pass whether their authorized tools, bound or
deferred, include bash. Without it:
- the two script bullets become one line: no bash tool is bound, work
  out results directly and write them with write_file;
- the subagent section no longer shows direct bash examples, and marks
  the bash subagent unavailable, since it inherits the lead's tool groups;
- the ACP hint no longer suggests `bash cp`.

* fix(goal): show the goal evaluator the user's answer to a Human Input Card

The web UI sends a Human Input Card answer as a hidden human message
(hide_from_ui with a human_input_response) and shows it on the card.
format_visible_conversation() skipped every hidden message, so the
evaluator saw the question but never the answer. Following the user's
answer then read as the assistant guessing, and the goal stood down with
needs_user_input although the user had answered.

A hidden human message whose human_input_response passes
read_human_input_response() now adds a line
"User (Human Input Card answer): <value>" from the structured value; the
question is already on the card's own line. Other hidden messages, such
as goal continuations, stay out. Over the 12,000-char cap the latest
answer the cut drops is kept at the top next to the latest request, and
is never taken for the request itself. Without card answers the cut is
unchanged.

* fix(agent): keep the subagent examples and bash hint in line with the bound tools

Review follow-up for the lead prompt without bash.

- The last delegation example follows the tools. With a bash subagent on
  offer it is unchanged. A lead with bash and no bash subagent keeps
  "Run a routine test, build, or git command directly." without the Bash
  subagent sentence. A lead without bash gets a file-work example instead.
- The unavailable-bash description no longer points at the sandbox
  provider. Since #2253 it renders only when the bash subagent is
  registered and the lead binds no bash, where the sandbox is not the cause.

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-09-30 17:54:42 +08:00
Hyeonsang Cho
14cd840bbf
fix(gateway): reject non-ASCII tokens instead of raising in constant-time compares (#6076)
* fix(gateway): reject non-ASCII tokens instead of raising in constant-time compares

hmac.compare_digest raises TypeError when a str operand holds non-ASCII
characters. Starlette decodes header bytes as latin-1 and query values as
UTF-8, so a single 0xE9 byte in a CSRF token, GitHub webhook signature,
internal auth token, or OIDC state turned the expected 403/401 into a 500.

Route the five client-facing comparisons through a new
app.gateway.utils.constant_time_equals helper that compares the UTF-8
encodings ("surrogatepass" so even a lone surrogate cannot raise). ASCII
results are unchanged, and no bypass was possible before the fix.

* fix(provisioner): reject non-ASCII API keys instead of raising; drive OIDC test over HTTP

The standalone provisioner compared X-API-Key with secrets.compare_digest on
str operands, so a non-ASCII header raised TypeError (500) the same way the
Gateway sites did. It cannot import app.gateway, so encode inline.

The OIDC state regression test now mounts the auth router and sends the
percent-encoded state through a real signed state cookie instead of stubbing
get_state_cookie and calling the handler directly.

* docs(changelog): reference #6076

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 17:41:11 +08:00
Wenchao An
1e462ae4a2
fix(frontend): consolidate run duration into reasoning headers (#6057)
* fix(frontend): show run duration in reasoning headers

* fix(frontend): cover all reasoning duration disclosures

* fix(frontend): collapse restored reasoning and cache duration targets

* chore: remove PR-only reasoning screenshot
2026-09-30 17:22:41 +08:00
kuse
8ecff3be95
test(config): make refresh_skew rejection test load-bearing (#6080)
test_refresh_skew_rejects_negative_and_boolean omitted the required
token_url, so McpOAuthConfig(refresh_skew_seconds=-1) failed validation
on the missing field alone -- deleting the ge=0 bound and the boolean
validator would leave the test green. Pass token_url (the same URL the
refresh_skew_seconds=0 boundary test uses) so only the guards can
satisfy pytest.raises; verified by stripping both guards from
extensions_config.py, which now fails exactly the two parametrized
cases, and restoring them, which returns the suite to 42 passed.

Also move SandboxOwnershipConfig up beside the other config imports
and drop the redundant in-test `import pytest` (review nits on #6026).

Generated-by: ZCode (GLM, coding agent)

Co-authored-by: liwenjie200543 <liwenjie200543@users.noreply.github.com>
2026-09-30 17:21:43 +08:00
PeaceMaker-best
b73a4e79bf
feat(memory): scope management API by agent (#5565)
* feat(memory): scope management API by agent

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

* fix(memory): preserve scoped management isolation

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

---------

Signed-off-by: PeaceMaker-best <221849497+PeaceMaker-best@users.noreply.github.com>
Co-authored-by: PeaceMaker-best <221849497+PeaceMaker-best@users.noreply.github.com>
2026-09-30 16:50:07 +08:00
Hyeonsang Cho
af67738521
fix(agents): treat a null max_total_subagents override as unset (#6088)
* fix(agents): treat a null max_total_subagents override as unset

An API caller sending "max_total_subagents": null in the run context
failed that run with a TypeError: the key was present, so
dict.get(key, default) returned None and building SubagentLimitMiddleware
compared None with an int.

Resolve the per-run delegation cap through one helper,
effective_total_subagents_per_run, which treats None as "use
subagents.max_total_per_run" and clamps to 1-50. The lead agent's
middleware and assembly paths, DeerFlowClient, and the system prompt all
use it, so the extension host policy and release policy now report the
enforced cap instead of an out-of-range request.

* docs(changelog): reference #6088 in the null max_total_subagents entry
2026-09-30 16:10:29 +08:00
xihongshichaojidan8
d0e4d3525c
fix(frontend): show media icons for APNG, AVIF and WebM (#6089) 2026-09-30 15:58:45 +08:00
NanHan Qing
c714629faf
fix(sandbox): expose DEERFLOW_USER_ID to every bash command (#6058)
* fix(sandbox): expose DEERFLOW_USER_ID to bash commands

A skill's SKILL.md can trigger scripts, but those scripts run as bash
subprocesses inside the sandbox and cannot reach the Gateway auth context,
so get_effective_user_id() is unavailable to them and user-scoped skills
are impossible to write. The maintainer settled the direction on #3919:
inject the id as DEERFLOW_USER_ID.

Every bash command now begins with 'export DEERFLOW_USER_ID=<resolved>; '
(or 'unset' when the resolved id is unusable), mirroring the existing
DEERFLOW_CHANNEL_USER_ID channel-identity prefix (#3914). The id rides the
command string rather than execute_command(env=...), because a non-empty env
switches AioSandbox to the bash.exec API reserved for request-scoped secrets.

The earlier attempt (#4029) was closed for making every command carry the
new export without updating the exact-command assertions in
tests/test_channel_user_id_env.py. Those assertions are updated here, and the
composition of the two prefixes is pinned by a dedicated test.

* fix(sandbox): fix the DEERFLOW_USER_ID unit test and cover review findings

Follow-up to the review on this PR.

Unit test (CI `backend-unit-tests (2)`): the change moved the command
contract but left the exact-command assertion in
tests/test_skill_request_scoped_secrets.py::test_local_bash_forwards_env_and_timeout
expecting the bare `echo hi`. The local validation run used a `-k` filter that
did not select that file, so the breakage was invisible there. The assertion
now pins the composed command, and `_is_windows` is patched explicitly so it no
longer depends on the host OS.

[P2] Windows local sandbox: `export` is invalid under the PowerShell/cmd.exe
fallback, but skipping the prefix outright left the promise unmet on Windows.
The id is published through `execute_command(env=...)` there instead:
LocalSandbox layers `env` into the per-process environment and, unlike
AioSandbox, has no persistent session that could carry a stale value. It is
deliberately kept out of `injected_env` so the identifier does not enter the
secret-redaction set.

Docstring: `_user_identity_prefix` now carries the same "informational, not
authenticated identity" caveat the channel variable has in
backend/app/channels/AGENTS.md. The value is authorization-grade at the Gateway
boundary, but any bash command can overwrite its own environment, so skills
must not treat the shell variable as proof of identity.

Docs: the en/zh sandbox.mdx identity sections and the channels/AGENTS.md
identity paragraph now describe the second, outermost prefix, its ordering
against the channel prefix, and the Windows behaviour.
2026-09-30 14:56:03 +08:00
Totoro
f75d89f39d
fix(goal): record a failed evaluator call as a stand-down (#6073)
* fix(goal): record a failed evaluator call as a stand-down

When the goal evaluator call raised (a provider error, or an answer that
was not valid JSON), _prepare_goal_continuation_input() logged a warning
and returned without writing anything. The goal stayed active with no
last_evaluation, or with an earlier run's verdict and run id, unlike the
other paths that stop the goal loop.

Record it like the no_durable_end_of_turn stand-down: blocker run_failed,
stand_down_reason evaluator_failed, the current run id, and a reason that
names only the exception type. Nothing is recorded if the run was aborted
meanwhile. A failure before the evaluator is called keeps its behavior and
now logs "Could not prepare goal evaluation".

* docs(gateway): fit the evaluator-failure note in the AGENTS.md budget

Main grew backend/app/gateway/AGENTS.md to 49,006 bytes, and the longer
note pushed the merged file past the 49,152-byte hard limit that
tests/test_agent_guidance_check.py enforces. Say it in one clause.

* docs(gateway): keep main's AGENTS.md line after the merge

The merge from main kept this branch's version of the goal paragraph,
which restored the Human Input Card wording #6068 replaced and put the
file at 49,194 bytes, over the 49,152-byte limit the agent-guidance
check enforces. Take main's line. It already says every non-satisfied
evaluation is persisted with last_evaluation, which this fix makes true
for a failed evaluator call.

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 11:35:19 +08:00
PeaceMaker-best
66597ae89d
fix(subagents): retain completed model usage on cancellation (#5910)
* fix(subagents): retain completed model usage on cancellation

* docs(subagents): fit guidance within chain budget

Signed-off-by: Willem Jiang <willem.jiang@gmail.com>

---------

Signed-off-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: PeaceMaker-best <221849497+PeaceMaker-best@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 10:56:52 +08:00
kuse
f2f9e6f6fe
fix(config): guard numeric config fields that accepted silently-broken values (#6026)
* fix(config): guard numeric config fields that accepted silently-broken values

Ten numeric fields across six config models admitted zero, negatives, or
booleans (YAML/JSON true coerces to 1) and only failed at runtime — or kept
running with a nonsense value:

- CircuitBreakerConfig: failure_threshold >= 1, recovery_timeout_sec > 0
- McpServerConfig: tool_call_timeout > 0, session_init_timeout > 0
- McpOAuthConfig.refresh_skew_seconds >= 0 (0 stays valid)
- ModelConfig.stream_chunk_timeout > 0; context_window keeps gt=0 and
  gains boolean rejection (review follow-up)
- RunEventsConfig.max_trace_content >= 1
- SandboxConfig.port 1..65535, idle_timeout >= 0 (0 = documented disable)
- SummarizationConfig.trim_tokens_to_summarize >= 1

Review follow-ups: boolean rejection extended to every bounded numeric
sibling in sandbox_config (replicas, acquire_timeout, burst_limit,
health_check_skip_seconds, bash_output_max_chars, read_file_output_max_chars,
ls_output_max_chars, bash_command_timeout, temporary_grant_ttl,
renewal_interval_seconds, ttl_multiplier) so the file is uniform, and to
model_config.context_window. Tests cover allow_inf_nan=False rejection
(float('inf')/'nan') and the refresh_skew_seconds=0 boundary.

Generated-by: ZCode (GLM, coding agent)

Authored-by: liwenjie200543 <liwenjie200543@users.noreply.github.com>

* style(config): apply Ruff formatting to numeric guards

Signed-off-by: Willem Jiang <willem.jiang@gmail.com>

---------

Signed-off-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: liwenjie200543 <liwenjie200543@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 10:48:26 +08:00
yang rui
fcff55a075
fix(frontend): preserve first-turn order during chat creation (#6074) 2026-09-30 10:45:19 +08:00
PeaceMaker-best
0ef405a77f
fix(agent): anchor compaction to runtime model profile (#5566)
* fix(agent): use runtime profile for compaction

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

* fix(summarization): honor required-thinking profile anchors

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

---------

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-09-30 10:38:19 +08:00
Totoro
d73844b997
fix(goal): show the goal evaluator the tool calls and results (#6068)
* fix(goal): show the goal evaluator the tool calls and results

The evaluator read only the text of user and assistant messages and was told
to fail closed without explicit evidence, so it stood down with
missing_evidence on most completed file tasks: the tool calls and tool
results that the web UI shows never reached it.

Include the assistant's tool calls and the tools' results inside the
existing message window, shortened (200 characters per argument value, 600
per step, line breaks kept), and tell the evaluator to treat tool results as
data, that a successful tool call alone does not meet a goal, and that a
guessed input means needs_user_input. The no-progress and final-clear
signatures are unchanged.

* fix(goal): tighten the evaluator's tool evidence after review

- Pair each tool result with the latest call of its id before it. Some
  providers reuse ids such as call_0 across turns, and the window-wide
  map labelled an earlier read with a later write's name.
- Keep model-supplied tool names on one line in calls and result labels,
  and escape U+0085, U+2028 and U+2029 in arguments and results, which
  json.dumps(ensure_ascii=False) leaves raw, so no tool step can start a
  new evidence line.
- Leave out the results of a hidden assistant message's calls. The web UI
  renders a result only under its call, so neither shows there either.
  A clarification prompt stays: the UI shows it as its own card.
- Over the 12,000-char cap, keep whole lines from the end instead of
  slicing mid-line. Keep the latest user message that the cut drops at
  the top, whole when it and the lines after it fit, else shortened to
  2,000 chars. If the next assistant message back does not fit whole,
  keep its end in the room left. A marker counts the lines left out.
- Docs: the Gateway AGENTS.md and a worker docstring no longer say the
  evaluator never sees tool messages.

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-09-30 10:36:09 +08:00
FanouZeng-TT
4006ca072f
fix(channels): keep the delimiter newline when splitting long Discord replies (#6062)
* fix(channels): preserve Discord split boundaries

* test(channels): cover Discord split at leading remainder newline

Signed-off-by: Willem Jiang <willem.jiang@gmail.com>

---------

Signed-off-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 10:17:46 +08:00
Wenchao An
9dc7bb30da
fix(capabilities): restore administrator management of shared MCP plugins (#6018)
* fix(capabilities): restore administrator management of shared MCP plugins

* fix(mcp): clarify control ownership and require mutation scope
2026-09-30 10:14:42 +08:00
lihongyuan99
f94dbf21f3
docs(backend): point the stream-mode translation anchor at stream_modes.py (#6021)
* docs(backend): point the stream-mode translation anchor at stream_modes.py

* docs(backend): use one anchor spelling within the stream-mode table row

The row named the translator by its backend-relative path and its call
site by the harness-relative short form, so the same symbol was written
two ways two rows apart.
2026-09-30 10:13:22 +08:00
Hyeonsang Cho
777961f84e
fix(events): read a non-filename run ID as an unknown run on JSONL (#6070)
* fix(events): read a non-filename run ID as an unknown run on JSONL

The run-scoped routes (events, messages, workspace-changes) pass the URL's
run_id straight to the event store. With run_events.backend: jsonl, an ID
that cannot be a filename (run.1, a%20b, %2e%2e) raised ValueError and
surfaced as a 500, while the memory and database stores return an empty
result.

JSONL reads and deletes now treat such an ID as an unknown run through one
helper, _existing_run_file(), which returns before building any path.
Writes still reject it.

* docs(changelog): reference #6070 for the JSONL run ID fix

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 10:07:58 +08:00
NanPan
48aec0bc84
fix(gateway): drain managed model saves on cancellation (#6024)
* fix(gateway): drain managed model saves on cancellation

* test(gateway): pin managed model save cancellation drain

* fix(gateway): log failed drained managed model saves

Signed-off-by: Willem Jiang <willem.jiang@gmail.com>

* fix(gateway): keep guidance within hard size limit

---------

Signed-off-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 10:06:53 +08:00
Shxiao
73fdbcfdf4
fix(scheduler): prevent fixed-hour cron tasks from firing twice on DST fall-back (#6066)
* fix(scheduler): prevent fixed-hour cron tasks from firing twice on DST fall-back

* fix(scheduler): restrict fixed-time DST skip to single-time cron and add edge tests

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 09:50:39 +08:00
Hyeonsang Cho
b37f769b67
fix(persistence): reject SQLite file: URIs instead of opening them as filenames (#6069)
* fix(persistence): reject SQLite file: URIs instead of opening them as filenames

resolve_sqlite_conn_str passed a file: connection string through
unchanged, but LangGraph's SQLite checkpointer and Store factories
(SqliteSaver, SqliteStore, AsyncSqliteSaver, AsyncSqliteStore) connect
without uri=True, so SQLite treated the whole URI as a literal filename:
file:checkpoints.db?mode=rwc created a file with that exact name in the
working directory, file::memory:?cache=shared persisted to disk, and a
file:/// URI failed to open. The readiness probe did open URIs with
uri=True, so it checked a different file than the runtime used and
reported in-memory URIs as not_configured.

Reject file: URIs in resolve_sqlite_conn_str, the one helper all four
factories go through, with an error that names
checkpointer.connection_string. The probe drops its URI parsing and
reports a rejected connection string as unreachable.

* docs(changelog): reference #6069

* test(persistence): pin the case-sensitive file: URI check
2026-09-30 09:40:02 +08:00
Bob
4394c23b66
fix(channels): decode Slack entity escapes in inbound message text (#6045)
* fix(channels): decode Slack entity escapes in inbound message text

Slack sends a user-typed &, < and > as &amp;, &lt; and &gt; in event text, so raw <...> always marks a control sequence. The Slack channel passed that text to the agent as-is, so code, comparisons and names like R&D reached the agent as 'a &lt; b', 'R&amp;D'. Decode the three entities after bot-mention stripping (amp last, so an escaped entity is decoded once) and keep mentions and links untouched.

* test(channels): cover Slack entities inside link labels

Signed-off-by: Willem Jiang <willem.jiang@gmail.com>

---------

Signed-off-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-30 09:38:05 +08:00
xiaodu55
81fa17c15a
fix(gateway): render the bundled interpreter repair path without backslash escapes (#6040)
* fix(gateway): render the bundled interpreter repair path without backslash escapes

The repair hint used {sys.executable!r}, whose repr() escapes Windows
backslashes - users saw 'D:\college\...\python.exe' in the
copy-paste guidance, and
test_stale_bundled_interpreter_can_be_repaired_without_reinstall fails
3/3 on Windows because the raw path never appears in the detail. The
!r was written for the surrounding quotes; on POSIX paths that is its
only effect, which is why CI never saw it.

Quote the raw path explicitly instead. On Windows the three test params
now pass (22/22 in the file); POSIX output is unchanged apart from the
quoting coming from the literal instead of repr().

* fix(gateway): json-escape the interpreter command in the repair hint

Review follow-up (P2): the raw Windows path in the hint cannot be
pasted into the server's JSON ('\U' is not a JSON escape), so the
prescribed repair could not be saved. Show the command value via
json.dumps(sys.executable) - pasteable JSON on every platform - and
keep the raw path in a parenthetical for terminal use, per review.
2026-09-30 09:19:39 +08:00