559 Commits

Author SHA1 Message Date
Totoro
3f9a78e02e
fix(goal,artifacts): verify completion and artifact handoff (#7961)
* fix(goal,artifacts): verify completion and artifact handoff

* fix(goal,artifacts): harden shared artifact handoff

* docs(agents): shrink inherited guidance to fit chain budget

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-11-07 18:21:49 +08:00
yetuge
a41d0fcec1
fix(parsing): keep literal think tags inside JSON string values (#7907)
* fix(parsing): keep literal think tags inside JSON string values

Structured parsers ran strip_think_blocks over the whole response before
json.loads. A model answer that quotes the tag -- "Explain <think>example
</think>" -- lost the quoted text, and an unclosed literal tag inside a
string cut the JSON document mid-string, so suggestions returned None and
goal evaluation failed to parse.

Strip reasoning only outside JSON string literals: leading blocks are still
removed whole (a JSON example inside reasoning is never taken as the
answer), and blocks after the payload starts are removed only outside
quotes. Suggestions and the goal evaluator both use the shared helper.

* fix(parsing): keep think-tag stripping complete outside the JSON payload

The string-aware walker only covered the payload itself: a reasoning block
after prose and before the payload was copied verbatim, so brackets inside
it became the parsers' find/rfind targets, and an unclosed block in
trailing prose was kept where strip_think_blocks used to truncate.

Find the payload start while stepping over think blocks, strip the head with
strip_think_blocks, keep strip/truncate semantics after the top-level
payload closes, and truncate at an unclosed block outside a string. Adds a
CHANGELOG entry for the user-visible parsing change.
2026-11-06 21:19:52 +08:00
Chau Vu
856cde25b1
fix(gateway): strip server-owned metadata from run input todos and goal (#7803 follow-up) (#7904)
* fix(gateway): strip server-owned metadata from run input todos and goal (#7803 follow-up)

Commit 459fc8c introduced shape validation for caller-written todos in
both strip_server_owned_state_metadata and normalize_input, but only
applied _strip_external_metadata_from_message_like in
strip_server_owned_state_metadata.

External callers submitting POST /runs or POST /threads/{id}/runs with
input.todos or input.goal could supply forged server-owned metadata
(such as untrusted_input: False, original_user_content, or artifact
knowledge_sources) directly into the agent graph.

Apply _strip_external_metadata_from_message_like to both normalized
todos and goal in normalize_input when trusted_internal=False.

* fix(gateway): mirror normalized goal and todos into canonical run record input

* fix(gateway): replace non-dict additional_kwargs and cover edge cases

- Ensure non-dict additional_kwargs in message-like entries is replaced with an empty dict instead of surviving the strip
- Remove redundant None-check on normalized_goal in normalize_input
- Add regression tests covering non-dict additional_kwargs replacement and untrusted_input stamping on human-like entries

* test(gateway): format todo input validation cases

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-06 21:14:28 +08:00
Totoro
593ba49458
feat(subagents): continue requested batch work and schedule explicit prerequisites (#7839)
* feat(subagents): continue native batches and honor item dependencies

* docs: link native batch follow-up changelog entries

* test(migrations): pin isolated history-seed upgrade to 0050

* fix(subagents): preserve follow-up scope, blockers and fair scheduling

* test(memory): freeze capacity evaluation time

* fix(subagents): guard follow-up admission and retain run controls

* fix(subagents): enforce runs:cancel on tool cancellation of follow-up batches

* style(tests): format test_batch_followup_host with ruff

* fix(subagents): drop the admission checkpoint check and keep SDK cancel compatible

The checkpoint check inside run admission waited on the checkpoint saver's
lock while holding the SQLite write lock, which could make another chat's
checkpoint write fail. Remove it; dispatch still checks the conversation
before admission.

A follow-up waiting for an unanswered question now keeps its claim until the
lease expires instead of rereading the conversation every poll. Cancel passes
the caller's run authority only to submitters that take it, so existing SDK
submitters keep the two-argument call. Document that retrying a prerequisite
by hand does not requeue its blocked items, and list 0052 as the migration head.

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 15:10:21 +08:00
lukyturnflow
bef8c9c2e3
fix(skills): keep disabled skills disabled when the extensions config cannot be read (#7705)
* fix(skills): fail closed when the extensions config cannot be read

* test(skills): cover a disabled skill when the extensions config cannot be read

* style(tests): format the fail-closed regression for ruff

* style(tests): fit the docstring and signature inside the 240-column limit

* fix(skills): fail closed in the user-scoped listing when the config cannot be read

* test(skills): cover the user-scoped listing with an unreadable extensions config

* fix(gateway): keep skill read surfaces loud about an unreadable extensions config

Folding the user-scoped degraded mode in (#7704) also stopped the skill
read endpoints from failing on a config that cannot be read: the storage
now answers with an all-disabled listing, which regressed the #7155
contract (a generic 500 with the host path only in the server log) into
a 200 that presents a broken deployment as a legitimately empty one.

Keep both properties: the storage still fails closed for the runtime
gate, and the read surfaces re-validate the config explicitly so the
raised error reaches each handler's generic 500 branch unchanged.
First-run behavior is untouched: no config file resolves to an empty
config without raising, so default-enabled still applies.

* fix(skills): redact config-load failures and keep the read preflight off the event loop

Review follow-ups:

- Log the failure shape instead of the exception text in both degraded
  modes: from_file resolves $VAR placeholders before validation, so its
  message can embed resolved credentials. Reuse _describe_load_failure,
  the loader's own redaction helper.
- Run the gateway read preflight through asyncio.to_thread in the
  capability adapter, matching the scan it precedes.
- Cover the user-scoped degraded branch with a CUSTOM skill that has
  per-user state on disk, so the second read's guard is directly
  regression-tested (verified red with the guard removed).

---------

Co-authored-by: Jay0130-a <309071492+Jay0130-a@users.noreply.github.com>
2026-11-05 15:07:07 +08:00
Wenchao An
b6ed47778d
feat(extensions): add versioned team file handoffs (#7817)
* feat(extensions): add versioned team file handoffs

* fix(extensions): fence team artifact execution and snapshots

* fix(agent-teams): retain resumed keys and bound artifact transfers

* ci: reclaim runner space before sandbox image smoke

* ci: budget sandbox image preparation separately

* fix(extensions): version artifact contracts and document snapshots
2026-11-05 14:50:09 +08:00
一片一片
459fc8ca43
fix(todos): keep an unusable stored todo list from failing every model call (#7803)
* fix(todos): keep an unusable stored todo list from failing every model call

`todos` is a reducer channel that `POST /threads/{id}/state` and run input can
Overwrite directly, but neither boundary validated its shape. The todo
middleware rendered it with `entry.get(...)` in both `before_model` and
`after_model`, so a non-object entry — or a value that is not a list — raised
AttributeError on every model call, permanently for that thread.

Validate caller-written todos beside `goal` in `_normalize_external_todos`: a
list of objects with string content and a known status, or HTTP 400. The
middleware now reads the channel through `_usable_todos` and ignores entries of
any other stored shape, so checkpoints and direct integrations written before
this boundary still run.

* fix(todos): normalize stored todos in token-usage attribution

TokenUsageMiddleware reads the same stored `todos` channel through
`_build_attribution` whenever the last AIMessage calls write_todos, and
`token_usage.enabled` defaults on, so a value the todo middleware now tolerates
still aborted the step here. Route the stored list through `_normalize_todos`
before reading entries.

Document that a caller-written todo without a status is accepted as pending and
cover that shape at both external boundaries.

* docs: keep backend agent guidance within size budgets

* fix(todos): keep the channel metadata strip on caller-written todos

The dedicated todos branch in strip_server_owned_state_metadata replaced the
generic list branch, which stripped server-owned metadata from every entry. A
caller could therefore persist additional_kwargs.original_user_content,
untrusted_input, or artifact.knowledge_sources on a todo entry. Re-apply the
per-entry strip after normalization, keeping None and empty lists untouched.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 07:28:33 +08:00
Wenchao An
2145c9ab1f
feat(models): discover provider catalogs and search chat models (#7871)
* feat(models): discover provider catalogs and search chat models

* docs(models): scope catalog guidance to owning directories

* test(guidance): register model catalog scoped instructions

* test(memory): pin capacity scenarios to a stable clock

* fix(models): guard catalog imports and align picker interactions

* test(memory): share the fixed clock with updater capacity tests

* fix(models): clear provider filters removed by catalog refresh

* refactor(models): share connection fields across model forms

* fix(models): keep active provider filters clearable

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 06:43:01 +08:00
aduoge
26bf78f2b9
fix(github): apply allow_authors to the webhook sender (#7868)
_author_login returned the issue/PR creator for issues and pull_request
events. On edited/labeled/reopened actions the actor is sender, so an
allow_authors user editing someone else's issue was gated on mention,
while anyone editing an allow_authors user's issue bypassed it. Prefer
sender.login and keep the object author as a fallback.

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-04 21:35:55 +08:00
golemon
5371343f69
fix(gateway): continue dispatching queued inputs after cancellation (#7746)
* fix(gateway): continue queued inputs after cancellation

* docs(runtime): clarify queued input cancellation handling

* fix(gateway): clarify cancelled-input error and log race-window skips

* docs(changelog): document queued input cancellation fix

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-04 10:39:20 +08:00
spud
21175ba46f
fix(runtime): persistently fence stale run event producers (#7619)
* fix(runtime): fence revoked run event producers

* fix(runtime): reduce off-mode producer guard locking

* docs: keep inherited guidance within size budget

* chore(runtime): remove development phase labels

* fix(persistence): normalize libpq SSL options for asyncpg connections

* docs(runtime): clarify producer fencing behavior in off mode

* test(persistence): align migration TLS probes with channel-binding rejection

* fix(runtime): authorize fenced Gateway history seeding

* fix(scheduler): preserve takeover identity in recovery publication

* fix(scheduler): report unconfirmed recovery publication

* fix(runtime): preserve history messages before the first human turn

* fix(runtime): clarify run creation failure diagnostic

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-02 15:26:42 +08:00
Willem Jiang
01e1ed778b
docs: restore agent guidance size budgets (#7810) 2026-11-02 15:20:54 +08:00
王一帆
d0ec8dcc5d
feat(knowledge): add optional ingestion and status center (#7518)
* feat(knowledge): add optional ingestion and status center

* docs(knowledge): record upstream CI budget fix and regression results

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-02 10:42:21 +08:00
Hyeonsang Cho
7fd0c0cb3e
fix(harness): keep a cancelled batch cancelled when retrying a failed item (#7792)
* fix(harness): keep a cancelled batch cancelled when retrying a failed item

retry_item only kept a paused batch as it was, so retrying a failed item
of a cancelled batch reset the batch to queued and the worker claimed and
ran the item. A cancelled batch now rejects the retry (409), re-checked
under the write lock because SQLite ignores FOR UPDATE, and the workspace
no longer offers Retry there.

* docs(changelog): reference #7792 for the cancelled batch retry fix
2026-11-02 10:05:05 +08:00
N
98b72af732
fix(auth): allow demoting disabled administrators (#7769) 2026-11-02 09:26:00 +08:00
Hyeonsang Cho
17b05718e0
fix(agents): store an explicit null description as empty (#7638)
* fix(agents): store an explicit null description as empty

PUT /api/agents/{name} accepted "description": null and wrote it into the
agent's config, but AgentConfig.description is a str. Every later load of
that agent failed validation: the PUT itself, GET and repair PUTs returned
500, the file backend dropped the agent from the list, and the db backend
failed the owner's whole list.

Store an explicit null as the empty default, and have parse_agent_config
read a stored null description (already written by this route, or a bare
YAML key) as empty so affected agents load again.

* docs(changelog): reference #7638 for the agent null description fix

* docs(agents): document that a null description resets it to empty

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-01 23:34:59 +08:00
Xiao Hou
830c8ff6f1
fix(runs): trim event_types filter entries before querying (#7758)
* fix(runs): trim event_types filter entries before querying

* style(runs): compute the trimmed event type once per entry

Addresses the review nit on #7758: use the walrus pattern already present
in this file (lines 657 and 1658) instead of stripping each entry twice.

No behavior change.

---------

Co-authored-by: gouyu-hou <gouyu-hou@users.noreply.github.com>
Co-authored-by: gouyu-hou <213296172+gouyu-hou@users.noreply.github.com>
2026-11-01 23:27:00 +08:00
Liu Hao
762b7188ab
fix(conversation): tolerate malformed event metadata (#7721)
* fix(conversation): tolerate malformed event metadata

* fix(gateway): guard malformed event metadata

* fix(gateway): guard malformed message kwargs

* fix(gateway): log malformed transcript metadata

* fix(gateway): ignore malformed message roles
2026-10-31 18:25:31 +08:00
jswSpace
bee97022b1
fix(projects): defer trash cleanup when retention config is unavailable (#7683)
* fix(projects): preserve trash when retention config is unavailable

* test(projects): address trash retention review feedback

---------

Co-authored-by: jswSpace <198684035+jswSpace@users.noreply.github.com>
2026-10-31 18:08:11 +08:00
gouyu-hou
0f2f199b3d
fix(console): tolerate malformed usage counters and pricing (#7736)
* fix(console): tolerate malformed usage counters and pricing

* docs(routers): record console counter tolerance in the router contract

* fix(console): reject stored counters beyond the float range

A JSON integer is not bounded by the float range: 10**400 parses as an
exact int, so _token_count passed it through and _token_cost then raised
OverflowError on the division back to float, taking /usage, /stats and
/runs down with a 500 - the same failure mode the rest of this PR
removes for every other junk shape.

Reject counts whose float conversion is not finite inside the existing
try, so they coerce to 0 and the run falls back to its legacy totals.
10**30 stays usable.

---------

Co-authored-by: gouyu-hou <gouyu-hou@users.noreply.github.com>
Co-authored-by: gouyu-hou <213296172+gouyu-hou@users.noreply.github.com>
2026-10-31 18:05:16 +08:00
Totoro
44e372dd7f
feat(runtime): support Queue and Steer during active Web runs (#7729)
* feat(runtime): add Queue and Steer for active Web runs

* style(frontend): format Queue and Steer controls

* fix(runtime): retain user Steer controls and order active inputs

* docs: add Chinese changelog entry for Queue and Steer

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-31 11:35:36 +08:00
Dragon ze
5d2749b05b
fix(gateway): read the live config off the loop for thread token usage (#7727)
* fix(gateway): read the live config off the loop for thread token usage

build_context_usage resolves the model capacity through deps.get_config,
which revalidates config.yaml and extensions_config.json on every call, so
each /threads/{id}/token-usage request paid that file IO on the shared
Gateway loop.

* docs(changelog): record the thread token-usage config read move

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-31 08:28:01 +08:00
Hyeonsang Cho
b4b2a68ee5
fix(memory): mask backend secrets in the memory config responses (#7665)
* fix(memory): mask backend secrets in the memory config responses

GET /api/memory/config and /api/memory/status returned
memory.backend_config as loaded. Config loading has already resolved
$VAR references by then, so a DeerMem extraction model api_key or a
Honcho api_key reached every caller with memory:read in plaintext,
which with authorization disabled (the default) is every signed-in
user.

MemoryConfigResponse now masks credential-named values (api_key,
*_token, password, authorization, ...) at any depth as "***". The
key is matched on its end, so knobs such as token_counting and
guaranteed_token_budget and the api_key_env variable name stay
readable, and unset values stay null or empty. The loaded config the
backend uses is not changed.

* docs(changelog): reference #7665 for the memory config secret masking fix

* refactor(gateway): share config secret masking between MCP and memory

The memory config masking forked the MCP router's key normalization
and recursive masker. Both now use app/gateway/secret_masking.py, and
each router keeps its own key rule: MCP matches credential terms
anywhere in the key, memory matches them at the end and keeps unset
values. MCP masking is unchanged.

The memory rule now also covers plural and cookie keys (api_keys,
secrets, passwords, cookies). Plural tokens stays out because
max_injection_tokens is a DeerMem knob. A secret-named container is
still masked whole, since its keys can be arbitrary names; the helper
docstring and a test now say so.

* docs(changelog): move the #7665 entry to Unreleased
2026-10-30 14:53:33 +08:00
SumEn
b86be89f09
fix(threads): preserve concurrent create idempotency in memory mode (#7691)
* fix(threads): preserve concurrent create idempotency in memory mode

* fix(threads): serialize detached run metadata creation

* docs(gateway): clarify metadata admission timeout budget

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-30 08:39:03 +08:00
Totoro
8d11aeb97b
feat(subagents): configure thinking and reasoning effort for native workers (#7668)
* feat(subagents): add thinking_enabled and reasoning_effort config for sub-agents (#4875)

(cherry picked from commit b1083728b23504cb0f1d1a463f8db70c5ec1c5a7)

* fix: resolve CI failures for subagent thinking config PR

- Fix UnboundLocalError in task_tool.py: initialize parent_thinking_enabled
  and parent_reasoning_effort before the if runtime block
- Remove unused imports from test_subagent_thinking_config.py
  (SimpleNamespace, pytest, resolve_subagent_model_name)
- Fix import sorting in subagents_config.py and test file
- Add reasoning_effort: None to executor test assertion

(cherry picked from commit 187851e307b871937e48bb67ae1b34b8b69bf92d)

* fix: address review comments - remove parent inheritance, supports_thinking clamp, Literal type

- Remove parent_thinking_enabled/parent_reasoning_effort from metadata
  extraction in task_tool.py (backward compatible: subagents only think
  when explicitly configured)
- Add supports_thinking clamp in _create_agent to prevent cross-model
  mismatch errors
- Change SubagentConfig.reasoning_effort to Literal type
- Fix sandbox_state assignment that was accidentally removed
- Fix syntax errors in subagents_config.py (missing closing parens)
- Update all docstrings/comments from "inherit" to "no override"
- Simplify test helper and remove parent inheritance tests

(cherry picked from commit 74f9c659ef4a98bd1756109929ab0748fab82317)

* feat(subagents): adapt reasoning settings to current native workers

* fix(subagents): retain model context on reasoning rejection

* fix(subagents): preserve model-factory reasoning diagnostics

---------

Co-authored-by: Neon549 <neon549@users.noreply.github.com>
Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-10-29 22:26:12 +08:00
Felix Shi
44b88151be
fix(skills): capture package digests without POSIX-only dir_fd (#7583)
* fix(skills): capture package digests without POSIX-only dir_fd

`capture_package` rejected every non-POSIX host, so `SkillMutationRuntime.state`
fell back to `"unsupported-" + uuid4().hex`. That digest differs on every call,
so `repository.observe()` recorded a mutation for an untouched package:
`mutation_seq` kept climbing and ABA detection could never match.

Walk the tree with `scandir` + `lstat` where `dir_fd`/`O_NOFOLLOW` are missing,
comparing the pre-open `lstat` against the opened descriptor before reading,
and leave the POSIX descriptor walk unchanged.

Windows 11 / Python 3.12: test_skill_asset_versions.py 5 failed -> 23 passed;
reverting this file alone brings the 5 failures back.

* docs(gateway): keep guidance below CI size budget

---------

Co-authored-by: Felix Shi <204130665+shi-felix@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-29 22:08:06 +08:00
Wenchao An
7feaf051a2
feat(plugins): add redacted administrator diagnostics (#7612)
* feat(plugins): add redacted administrator diagnostics

* ci: retrigger plugin diagnostics checks
2026-10-27 21:29:21 +08:00
Willem Jiang
03afd05917
fix(artifacts): authorize shared image reads and offload MIME lookup (#7604)
* fix(artifacts): authorize shared image reads and offload MIME lookup

* fix(artifacts): skip disabled blob lookups and warn on failures
2026-10-27 16:59:19 +08:00
mika
e2e8ba7aec
fix(artifacts): resolve viewed images from shared blobs (#6329)
Co-authored-by: mikamikasuki <211269698+mikamikasuki@users.noreply.github.com>
2026-10-27 14:21:42 +08:00
Totoro
c48447a4ae
fix(runtime): present outputs before a loop wrap-up and explain step-limit stops (#7588)
* fix(runtime): present outputs before a loop wrap-up and explain step-limit stops

Loop-detection warnings now ask the agent to present the files it already
created in /mnt/user-data/outputs before its final answer, when
present_files is available.

A run that exhausts recursion_limit now ends with stop_reason
recursion_capped and a plain message that lists outputs it created but
did not present. An active goal stands down without an evaluator call.
The /wait error envelope keeps the GraphRecursionError name, and IM
channels reply with the step-limit message instead of a generic failure.

* docs(changelog): note run-end delivery and step-limit stops (#7588)

* test(runtime): stop pinning LangGraph's step count in the step-limit test

The produced paths and the message already follow the observed model
calls, so the exact count only pinned LangGraph's per-step accounting.
Also note on the step-limit goal branch that its caller passes no
evaluator factory, so it has to stay ahead of the evaluator section.

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-27 10:34:34 +08:00
Totoro
ab8cd4e10f
fix(channels): keep IM binding codes on screen and report goal state in replies (#7591)
* fix(channels): keep IM binding codes on screen and report goal state in replies

Connecting Slack, Discord, Feishu, DingTalk, WeCom, Buzz or QQ now opens a
dialog that keeps the /connect command on screen with a copy button, its
expiry time and the binding status, instead of a toast that disappears.

The bots' /connect replies and the IM /goal replies now use the owner's
web UI language (else channel_connections.notification_locale), and /goal
reports the goal's state as the web goal bar does: in progress, why it
stopped, or that it was met.

* docs(changelog): note the IM binding dialog and localized IM replies (#7591)

* test(channels): check IM reply tables against every notification locale

The connect-reply check compared a fixed en-US/zh-CN pair, and the /goal
reply table had none. Both now match NOTIFICATION_LOCALES with the same
keys in each, like the existing _GOAL_REASON_TEXT check, so a new locale
fails here instead of raising KeyError at reply time.

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-10-27 09:46:58 +08:00
SumEn
f7d02a7515
fix(checkpoints): validate cached delta history before replay (#7548)
* fix(checkpoints): reject malformed Redis history write records

Validate pending-write tuples before optional Redis history entries reach
delta materialization. Treat the whole malformed entry as a miss so the
authoritative saver rebuilds it without dropping or inventing state.

Cover actual checkpoint reads, whole-entry replacement, valid tuple/list
records, neighbor preservation and warm-cache rereads.

* fix(checkpoints): bind cached history writes to their channel

* fix(checkpoints): keep cache warning failures best-effort

Guard warning handlers across decode, ingestion, serialization, Redis outages and purge. Preserve authoritative rebuilds and valid neighboring writes, with native graph regression coverage.

* docs(gateway): keep guidance below the hard size budget

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-27 00:28:03 +08:00
spud
aabfdeebc9
fix(streaming): make Redis stream END publication idempotent (#7572)
* fix(streaming): make Redis stream END publication idempotent

* fix(streaming): align Redis test fake with Lua END checks

Remove the unreachable owned missing-stream branch, mirror tail-only data checks in both fake script paths, and pin legacy END and owned missing-buffer behavior against real Redis.

* fix(streaming): gate legacy END scans after writer rollout

* fix(helm): synchronize embedded config version with schema

* test(runtime): use real stream bridge config in close coverage

Keep new configuration defaults in lifecycle fixtures and surface owner-task failures instead of masking them as event timeouts. Drain every helper task and cover context entry/close failures.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-27 00:16:53 +08:00
Willem Jiang
7a2dda1326
fix(ci): keep gateway guidance within size budget (#7584) 2026-10-26 23:01:17 +08:00
Hyeonsang Cho
0479934fe2
fix(auth): read the live config off the event loop on public auth routes (#7576)
* fix(auth): read the live config off the event loop on public auth routes

/register, /setup-status, /providers, /oauth/{provider} and
/callback/{provider} read the live auth config through get_app_config()
inline. That call stats and hashes config.yaml and extensions_config.json
on every request and reparses them after an edit, and these routes are
unauthenticated, so any client could keep that file IO on the event
loop. /login/local already reads its throttle policy in a worker.

Read the registration gate through asyncio.to_thread, and the SSO
routes' auth.oidc section through a shared _oidc_config() helper that
does the same.

* docs(changelog): reference #7576 for the off-loop auth route config reads

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-26 22:11:48 +08:00
haotianqi666
4f6ee59535
fix(gateway): preserve null input for static continuation (#7578)
* fix(gateway): preserve null input for static continuation

* fix(runtime): preserve continuation inputs during MCP projection

---------

Co-authored-by: haotianqi666 <250384991+haotianqi666@users.noreply.github.com>
2026-10-26 21:32:25 +08:00
Nan Gao
a684ae2fe3
fix(sandbox): leave SIGTERM/SIGINT teardown to the Gateway lifespan (#7560)
* fix(sandbox): leave SIGTERM/SIGINT teardown to the Gateway lifespan

AIO and E2B providers built on the event-loop thread installed SIGTERM/SIGINT
handlers that force-destroyed every sandbox synchronously, before uvicorn
started the run drain. The Gateway lifespan now holds sandbox shutdown
ownership from its first step, so providers skip those handlers and the
existing hook closes the provider after run drain and MCP teardown. SIGHUP
keeps the provider fallback. AIO acquire now refuses once shutdown began.

* docs(sandbox): document Gateway-owned sandbox shutdown and AIO admission fence

* test(gateway): inspect the lifespan body now that lifespan wraps it

* docs(changelog): reference #7560 for the sandbox SIGTERM drain-order fix

* ci: retrigger sandbox image smoke after a registry pull timeout
2026-10-26 20:38:32 +08:00
Hyeonsang Cho
9c00fd839a
fix(frontend): return SSO sign-ins to their next path (#7467)
* fix(frontend): return SSO sign-ins to their next path

The (auth) layout redirected every signed-in request to /workspace. The
Gateway sets the session cookie on the same redirect that lands on
/auth/callback?next=..., so the callback page never rendered and every SSO
sign-in landed on the workspace instead of the page that asked for it. A
signed-in visit to /login?next=... lost its target the same way.

Layouts cannot read searchParams, so the layout now renders the page for a
signed-in session and each page sends the user on: the callback and login to
the validated next path, setup to the workspace. The login page shows a
short wait message instead of the form while it redirects, and uses
router.replace so Back does not return to it.

* docs(changelog): reference #7467 for the SSO next-path fix

* fix(auth): reject control characters in the next redirect path

Browsers delete tab, LF and CR from a URL before parsing it, so a next of
"/\n//evil.example" passed both validators' "//" check and then navigated
to //evil.example. The login page's post-sign-in push already followed it,
and the signed-in login and SSO callback redirects reach it too.

validateAuthNextPath and validate_next_param now reject every C0 control
character and DEL. Percent-encoded forms are left alone: URL parsers do
not decode them, so they stay on the local origin.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-26 19:24:35 +08:00
Felix Shi
d53ec767af
fix(persistence): keep LF bytes in skill and agent text writes (#7501)
* fix(persistence): keep LF bytes in skill and agent text writes

Text mode translates a newline into os.linesep on write, so on Windows every
caller-supplied LF reached disk as CRLF while readers use universal newlines or
decode bytes explicitly. The difference stayed invisible: the API returned LF,
the file held CRLF, and a skill history record's prev_content described a
revision no API view could return (#7174).

Pass newline="" at the ten writers that persist or judge caller text: the local
and user-scoped skill writers, the skill history JSONL, the user skill-states
JSON, the SOUL.md/config.yaml staging in FileAgentStore, the managed-subagent
store, setup_agent's SOUL.md publish, the USER.md route, and the two temp copies
the frontmatter validator and the static scanner judge.

The tests emulate the Windows translation on io.open, so the regression runs on
the Linux CI shards instead of only on a Windows host: 10 failed before this
commit, 10 pass after.

* fix(skills): preserve candidate newlines during static scan

---------

Co-authored-by: Felix Shi <204130665+shi-felix@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-26 18:45:49 +08:00
Hyeonsang Cho
ddfa45eaaf
fix(gateway): read the route authorization config off the event loop (#7557)
* fix(gateway): read the route authorization config off the event loop

AuthMiddleware resolves route permissions for every authenticated
request, and resolve_route_permissions read the hot-reloaded
authorization config inline. get_app_config() stats and hashes
config.yaml and extensions_config.json on every call and reparses them
after an edit, so that file IO ran on the event loop per request.

Read it through asyncio.to_thread, like the plugin authorization path
already does. The context copy keeps runtime config overrides visible.

* docs(changelog): reference #7557 for the off-loop route authorization config read

* docs(config): note the get_app_config read cost where it is documented

Move the off-loop note from gateway/AGENTS.md, which is 12 bytes under
its hard limit, to the Config Caching section that documents what each
get_app_config() read costs.
2026-10-26 18:44:09 +08:00
Xinmin Zeng
d48ea2a9a3
refactor: remove unused proxy policy and private constants (#7554)
* refactor: remove unused proxy policy and private constants

* refactor(sandbox): share provisioner runtime mount path
2026-10-26 17:02:39 +08:00
jianyx1
4b1e06bbf9
feat(runtime): honor the web UI language in replies and DeerMem updates (#7294)
* feat(runtime): honor the web UI language in replies and DeerMem updates

Carry Settings > Appearance > Language as request-only run data: the
Gateway validates context.locale against a finite allowlist, the lead
agent prefers it for replies, and bundled DeerMem writes new facts and
summary prose in it. Explicit user language requests still take
precedence; checkpoint configurable, static prompts, and memory backend
APIs are unchanged.

Refs #7287

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

* fix(runtime): guard language context and pin locale maps

* test(runtime): pin UI locales across frontend and Gateway

Add the shared locale fixture and document the intentional language fallback for internal runs. Clarify live-locale precedence in the run context contract.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-26 13:24:30 +08:00
Totoro
c36bb699de
fix(channels): honor the configured recursion limit in IM runs (#7521)
* fix(channels): honor the configured recursion limit in IM runs

Every IM channel run asked the Gateway for recursion_limit 100, because
the channel manager seeded each run config with it. The Gateway honors a
request value, so the top-level recursion_limit never reached IM runs,
and a budget of 100 could stop a run after a handful of tool-calling
turns (six in one measurement with the default lead-agent middleware;
the count changes with the middleware chain).

IM runs now send no recursion_limit of their own and get the Gateway's
configured default, clamped by max_recursion_limit, like Gateway API
runs. A session config value still takes precedence, and GitHub channel
runs keep their floor of 250. The README and config.example.yaml IM
examples no longer pin session.config.recursion_limit to 100.

* docs(changelog): reference #7521 for the IM recursion-limit fix

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-26 12:16:26 +08:00
RongJie G
186d4f5c43
fix(gateway): admit cross-process runs when a producer lease blips (#7136)
A cross-process stream bridge (RedisStreamBridge) returns False from
start_producer_lease only after a transient backend error whose partial
lease registration was safely rolled back, leaving nothing tracked. The
admission guard treated that as fatal and raised, surfacing a 500 for an
otherwise healthy run whenever Redis had a momentary blip.

Degrade instead of rejecting: admit the run and log a warning. An
untracked producer falls back to the historical conservative recovery
path (wait for the real END or the retained-stream TTL), so the run is
no less safe than it was before producer leases existed. The lease is
never transferred or finished because none was taken; the worker
re-attempts its own lease independently.

Signed-off-by: CorgiBoyG <111257566+CorgiBoyG@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-26 12:00:41 +08:00
Luck
ce0fa4c114
feat(batch): add browser review for persisted results (#7006)
* feat(batch): add browser review for persisted results

* fix(batch): address browser review feedback

* test(batch): cover persisted result review regressions

Exercise running-to-terminal detail refresh, scoped citation dialogs, omitted evidence, translated page labels, and legacy evidence bounds. Fix component lint and formatting and synchronize the review contract documentation.

* docs(frontend): keep batch review guidance within budget

* fix(batch): refresh retried reviews and account for omitted evidence

* fix(ci): keep batch review guidance within size budget

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-25 23:27:35 +08:00
spud
33f7b43400
fix(runtime): backfill run.terminal after confirmed recovery takeover (#7500)
* fix(runtime): backfill run.terminal on confirmed recovery takeover

* fix(runtime): isolate recovery terminal publication failures

* fix(runtime): fence recovery terminal publication lifecycle

* fix(runtime): unify recovery receipt publication

* docs(runtime): document lifecycle guard lock constraints

* fix(runtime): reuse event transaction for recovery guards

Read Run lifecycle through the active event session when SQL stores share an engine, preserving deletion fencing without a second pool checkout. Keep custom lookups and different engines on their ordinary read path. Scope memory recovery guards and deletions to per-thread locks. Add SQLite/PostgreSQL pool pressure, stale identity, fallback, and independent-thread regressions, and document the contracts.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-25 23:22:02 +08:00
llb555
885062e197
fix(gateway,integrations): keep temp cleanup from replacing the caller's outcome (#7505)
* fix(gateway,integrations): keep temp cleanup from replacing the caller's outcome

* fix(gateway): adopt the family helper so a published edit stays silent
2026-10-25 22:13:26 +08:00
Hyeonsang Cho
74f3f53c66
fix(threads): reject an assistant_id that names no agent at thread creation (#7445)
* fix(threads): reject an assistant_id that names no agent at thread creation

POST /api/threads stored assistant_id unchecked. Every state read then built
a run config from it, and build_run_config's ValueError answered 500 on GET
thread, state and history until the thread was deleted.

Validate it at creation with the rule run creation already applies, now
shared as custom_agent_name(), and answer 422. Records written before this
change are read with the lead agent's schema instead of failing.

* docs(changelog): reference #7445 for the thread-creation assistant_id fix

* fix(threads): clear invalid legacy assistant ids in branches

* docs(changelog): note the branch assistant_id behavior for #7445

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-25 13:21:47 +08:00
N
481f8168d4
fix(console): include legacy run input tokens in model usage (#7411) 2026-10-25 11:01:44 +08:00
Hyeonsang Cho
ee8e6762ac
fix(mcp): let ordinary users save bundled personal connections (#7400)
* fix(mcp): let ordinary users save bundled personal connections

The personal MCP policy read `server.cwd`, which McpServerConfigResponse
does not declare. A bundled launcher definition (DingTalk, WeCom,
HubSpot) never carries `cwd`, so for a non-administrator the check
raised AttributeError and every create, edit or enable of one returned
500. The web UI installs catalog connections with scope "user", so an
ordinary user could not install any of the three.

Read a supplied `cwd` from the model's extras instead. A bundled
launcher with a working directory still requires an administrator.

* docs(changelog): reference #7400 for the bundled personal MCP fix

* docs(changelog): say #7400 makes bundled personal installs work, not work again

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-24 16:12:04 +08:00