2440 Commits

Author SHA1 Message Date
Xuehao Xu
f6a5a64c9b
feat(knowledge): add optional ingestion and processing extension (#7293)
* feat(knowledge): add optional ingestion and processing extension

* fix(knowledge): address catalog and ingestion review regressions

* fix(knowledge): add sanitized provider diagnostics and Chinese docs

* fix(knowledge): align capabilities and repair catalog e2e assertions
2026-10-24 08:19:02 +08:00
haotianqi666
868c1ff807
fix(agents): treat read_file JSON errors as document data (#7369)
Co-authored-by: haotianqi666 <250384991+haotianqi666@users.noreply.github.com>
2026-10-24 08:06:38 +08:00
lihongyuan99
8c2f066159
fix(skills): read one shell vocabulary in the curl-pipe-shell rule (#7378)
`shell-curl-pipe-shell` hard-codes `bash|zsh|dash|fish|sh` in the download-pipe
matcher while the structural `env` fallback reads `_SHELL_NAMES`. The two lists
drift: `curl ... | ksh` (and csh/tcsh/ash/mksh) pipes a fetched script in
silence, even though sandbox_audit_middleware already treats ksh as a shell.

Derive the alternation from `_SHELL_NAMES` and widen both to the same ten
names, so a shell added once is visible to both detectors.
2026-10-24 08:03:36 +08:00
wannT-1
e1a7cba2ef
fix(extensions): keep atomic-write cleanup from replacing the save error (#7164)
* fix(extensions): keep atomic-write cleanup from replacing the save error

Extension snapshot rollback, the managed plugins rewrite, and skill-archive
upload copies now use cleanup_failed_atomic_write so a failed unlink or
warning log cannot mask the original publication error.

Co-authored-by: wannT-1 <wannT-1@users.noreply.github.com>

* fix(skills): keep upload and storage cleanup from replacing the save error

Route the skill-archive upload discard, custom-skill file writes, and
per-user skill-state writes through cleanup_failed_atomic_write. Close the
extension rollback descriptor on fdopen failure, and record the plugins
temporary path before the write so a short write is still removed.

Co-authored-by: wannT-1 <wannT-1@users.noreply.github.com>

* fix(skills): keep form close and snapshot delete from masking the save error

Discard the uploaded skill archive before closing the multipart form, and
suppress close errors so a flush failure cannot replace the install outcome.
Snapshot rollback of a missing original now uses cleanup_failed_atomic_write,
and skill-state writes clean up on BaseException including KeyboardInterrupt.

Co-authored-by: wannT-1 <wannT-1@users.noreply.github.com>

* test(extensions): assert logging cleanup cannot replace the delete-branch error

The log-handler check only ran for the unlink parameter, where the handler
is never patched. Move it under the logging failure so a handler exception
must stay out of the surfaced install error.

Co-authored-by: wannT-1 <wannT-1@users.noreply.github.com>

---------

Co-authored-by: wannT-1 <200890097+wannT-1@users.noreply.github.com>
Co-authored-by: wannT-1 <wannT-1@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-24 07:38:10 +08:00
BaiHeng Peng
cc8a947c7d
feat(extensions): add JSON output for configured extension inventory (#7351)
Co-authored-by: BaiHeng Peng <191328900+Aldenshile@users.noreply.github.com>
2026-10-24 07:19:45 +08:00
Gusanity
d318a89b80
fix: honor checkpoint object in thread state updates (#7115)
* fix: honor checkpoint object in thread state updates

Prefer the checkpoint object over the deprecated checkpoint_id, validate
its thread_id, and preserve checkpoint namespace when updating state.

Add regression coverage for checkpoint-object forks and thread mismatch.

* fix: preserve client context handling in checkpoint PR

* fix: support state updates in checkpoint namespaces

* fix: reject unknown checkpoint namespaces

* fix(gateway): isolate subgraph titles from thread metadata

Only synchronize state titles into conversation metadata for the root checkpoint namespace. Cover persisted child updates with and without checkpoint IDs in full and delta modes, and retain root title synchronization and branch sequence cleanup.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-24 07:17:27 +08:00
ShiYuan Xiong
75df1be6b7
fix(benchmark): reject negative idle timeouts before provider creation (#7363)
* fix(benchmark): reject negative idle timeout before provisioning

* test(benchmark): cover registered providers for idle timeout validation
2026-10-23 23:56:44 +08:00
Xuehao Xu
cfe2bec371
feat(workflows): add multi-Gateway coordination and session-authorized failover (#7138)
* feat(workflows): add leased multi-Gateway coordination and fenced dispatch

* test(workflows): validate deployed failover and canonicalize config revisions

* fix(workflows): share namespace across registration and lease fencing

* fix(workflows): recover interrupted cancellation without admission caps

* fix(workflows): retain execution lease through child cleanup

* docs: trim guidance padding to fit merged instruction budget

* fix(workflows): release rejected admission slots and normalize gateway routing

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 23:31:43 +08:00
lihongyuan99
0a42289233
fix(sandbox): keep quarantine teardown idempotent when a peer finishes first (#7313)
* fix(sandbox): return from quarantine teardown when a peer already did the work

retire() and retire_replaced_generations() guard their final rmdir() but list
the fence directory and unlink each record unguarded, while contains() and
_exists() in the same class already report a directory removed underneath them
as "no fence". The teardown exclusion is a TTL lease, not a lock, so a peer can
finish the same teardown in those two windows and the resulting FileNotFoundError
escapes the unwrapped _retire_absent_quarantine() call at the top of creation.

Both passes now tolerate a peer that removed the directory or the record first.

* test(sandbox): correct quarantine docstring reference

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 22:55:45 +08:00
NanPan
ef54c14b4d
fix(sandbox): use saved process group id on timeout (#7353)
* fix(sandbox): retain process group id for timeout cleanup

* test(sandbox): cover exited leader during group cleanup

* fix(sandbox): capture process group id before drain threads

* style(sandbox): separate timeout regression test

* test(sandbox): cover missing process-group fallback

* style(tests): format sandbox timeout regressions

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 22:35:10 +08:00
NanPan
1700ac2c82
fix(events): honor empty event type filters in DB store (#7352)
* fix(events): treat empty event type list as an empty filter

* test(events): cover empty event-type filters across stores
2026-10-23 22:34:23 +08:00
Evan
195bf358ca
fix(acp): fail fast when an ACP agent exits and drain its stderr (#7354)
* fix(acp): fail fast when an ACP agent exits and drain its stderr

An ACP agent that exited before answering (missing credentials, a wrong
argument) left invoke_acp_agent waiting for the whole timeout_seconds
budget (30 minutes by default) and then suggested raising the timeout.
The pinned SDK never fails pending requests when the agent's stdout
reaches EOF, so nothing noticed the process was gone.

The SDK also opens the agent's stderr as a pipe and never reads it. An
agent that logged more than asyncio's pipe buffering absorbs blocked on
its next stderr write and stalled the invocation the same way.

Race the ACP exchange against the agent process: when it exits (or a
newer SDK reports the connection closed), allow a short grace for
responses still buffered in the pipe, then report the exit code. Drain
stderr continuously into a bounded tail that is logged on failure and
kept out of model-visible text.

* fix(acp): log the full stderr tail on timeout and pin tail retention

Log the timeout tail after the SDK context has terminated and reaped the
agent, so lines still in flight when it stalled are included. Make the
exit test write more stderr than the retained tail, so keeping the start
of the stream instead of its end fails the assertion on the fatal line.
2026-10-23 22:30:26 +08:00
ShiYuan Xiong
3802db3392
feat(agent): add read-only run status introspection (phase 1) (#7297)
* feat(agent): expose read-only run status tool

* refactor(tools): name the subagent dispatch tool set

Hoist the hardcoded subagent dispatch tool-name literal into _SUBAGENT_DISPATCH_TOOL_NAMES so a future dispatch-tool rename has one place to update. No behavior change: the folded-subagent context_fill guard compares the same two names.

* test(tools): account for run status in collision assembly

* docs: keep script guidance within size budget
2026-10-23 22:06:00 +08:00
Cordis798
57ceff7d50
fix(eval): distinguish booleans from numbers in snapshot grading (#7358)
Co-authored-by: Cordis798 <284918518+Cordis798@users.noreply.github.com>
2026-10-23 22:02:50 +08:00
Tsai Yuan
03c95f5278
fix(subagents): report model output length caps (#7355)
* fix(subagents): report model_length_capped when a subagent hits the output limit

ModelLengthFinishReasonMiddleware only ran on the lead chain. A subagent
whose response was cut at the provider output limit (finish_reason=length,
stop_reason=max_tokens, MAX_TOKENS, Responses max_output_tokens) reached
the lead as a clean `completed`, and a tool call truncated at that
boundary still executed with partial arguments.

Register the same middleware on the subagent chain, in the lead's
relative position (after configured extensions, before Safety). It now
also records each detected cap per context run_id and exposes
`consume_stop_reason`, so the executor's existing duck-typed guard
collection reports `stop_reason=model_length_capped`. Add the value to
the subagent status contract (Python enum, contract fixture, frontend
whitelist) with the label "model output length limit", and update the
subagent docs.

Tests cover the middleware channel, chain placement, the contract, and
an end-to-end executor run with a fake model.

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

* test(tasks): cover cap reasons on completed and failed runs

Extend the existing shared-contract round-trip matrix to preserve both terminal statuses alongside every supported stop reason, including model_length_capped and loop_capped.

---------

Co-authored-by: huangzz <110116@nd.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 21:42:11 +08:00
Daoyuan Li
6e240b4bc3
fix(sandbox): search the skills root across category mounts (#7338)
* fix(sandbox): search skills across category mounts

* fix(sandbox): address virtual search mount review

Search explicitly configured category mounts regardless of ignored names,
while retaining descendant filters and host-parent exclusion. Warn with the
virtual mount path before re-raising broken-mount errors, defer ordinary
path resolution, and cover lookahead across mount boundaries.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 21:41:24 +08:00
Aling33
48c884a533
fix(sandbox): treat a fetched process substitution as an interpreter program (#7290)
* fix(sandbox): treat a fetched process substitution as a program operand

`bash <(curl url)` hands the fetched file to bash as an *operand*, not as a
redirect: bash runs it. The audit only recognised the stdin spelling
(`bash < <(curl url)`), so the operand spelling executable-fetched content
through the sandbox audit unimpeded — the miss documented in the middlewares
AGENTS.md known-gaps list.

An interpreter in command position whose first operand is a risky process
substitution now blocks. The two exceptions mirror the stdin rule: a script
operand in front (`bash script.sh <(curl url)`) keeps the substitution an
ordinary argument, and a shell noexec flag (`bash -n <(curl url)`) only
parses it.

Verified in Git Bash:
  bash <(echo "echo FETCHED_AS_PROGRAM")            -> FETCHED_AS_PROGRAM
  bash <(echo "echo FETCHED_WITH_ARG") consumer.sh  -> FETCHED_WITH_ARG
  bash -n <(echo "echo NOEXEC")                     -> exit 0, no output

* docs(sandbox): clarify substitution audit limitations

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 21:08:17 +08:00
Tsai Yuan
9f1fd2fc05
fix(models): preserve escapes in MindIE tilde fences (#7264) 2026-10-23 20:59:38 +08:00
Hyeonsang Cho
3d2ed725f6
fix(tui): let quit skip an interrupted run's unfinished step (#7326)
* fix(tui): let quit skip an interrupted run's unfinished step

After Ctrl+C the synchronous agent stream keeps running until its next
event. The run was a Textual thread worker, i.e. a thread on asyncio's
default executor, which App.run() joins on shutdown (up to 300 s on
3.12) and interpreter exit joins again with no timeout. Quitting while
a long step was still running therefore hung silently after the
terminal had been restored, and further Ctrl+C presses only printed a
traceback.

Run workers on a daemon thread instead. Quit now waits visibly for
running workers (an interrupted step still checkpoints, a finished run
still saves its title) and refuses sends meanwhile; a second quit
request exits at once. Report unexpected worker exceptions as an error
row and end the run, since Textual no longer catches them.

* docs(changelog): reference #7326 for the TUI quit-after-interrupt fix

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 20:33:07 +08:00
Totoro
fc4f719664
fix(goal): retain supplied evidence and retry one malformed evaluator verdict (#7345)
* fix(goal): use spare evidence budget for long user input

* fix(goal): retry one malformed evaluator verdict

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-10-23 20:29:35 +08:00
Hyeonsang Cho
e7d96fe051
fix(tui): append streamed assistant deltas verbatim (#7325)
* fix(tui): append streamed assistant deltas verbatim

The reducer merged each delta by comparing it with the row text: an
equal delta was a no-op, a delta starting with the row replaced it, and
a delta the row started with was dropped. Those rules absorbed re-sends,
replayed history and cumulative snapshots, none of which
DeerFlowClient.stream emits any more, so they only deleted real tokens
("ha" + "ha" -> "ha", "1" + "10" -> "10") and every standalone Markdown
marker an answer opened with (closing **, list bullets, table pipes,
closing code fences).

Append every delta on both the id-keyed and anonymous paths, and drop
_merge_stream_text. Rewrite the tests that pinned the re-send premise
as append contracts, run the merge cases through reduce() on both
paths, and drive the real client stream into the reducer.

* docs(changelog): note TUI streamed-delta fix (#7325)

* docs(changelog): drop trailing spaces after the #7325 entries

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 18:29:08 +08:00
李刚 / Bryan Li
1c9e962b32
fix(artifacts): keep a delimited sandbox path whole (#7308)
* fix(artifacts): keep a delimited sandbox path whole

The free-text artifact scan matched `/mnt/user-data/\S+`, so a tool that
quoted a filename with spaces registered a handle for the prefix: `Created
"/mnt/user-data/outputs/quarterly report.pdf" successfully.` became
`/mnt/user-data/outputs/quarterly`, which resolves to nothing after compact or
checkpoint reload. Quotes and inline backticks had the same failure.

The scan now classifies each candidate by how it is delimited. A span opened by
a matching quote or backtick on the same line is one value and keeps its spaces
and punctuation; a span whose delimiter never closes on that line reports
nothing, because a truncated prefix names a file that does not exist; a bare
path keeps the conservative whitespace boundary it always had. Ordering is the
text order, and the shared `seen` set still deduplicates against structured
references.

Pins the three delimiters, both unchanged behaviours (bare path boundary,
punctuation outside the delimiter), the unterminated and next-line cases, two
delimited paths in one result, a mixed delimited/bare result, and the extracted
handle end to end.

* fix(artifacts): reject line breaks and match escaped quotes

* fix(artifacts): validate quoted path boundaries

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 17:59:44 +08:00
treasuresyq
6caa5addf3
feat(community): migrate You.com tools to httpx.AsyncClient (#7186)
* feat(community): migrate You.com tools to async HTTP

The You.com provider sent its search request through a sync httpx.Client. A
sync LangChain tool invoked through ainvoke runs on the default executor, so
the request held one of that pool's threads for the whole network round-trip,
and several providers queried in parallel queued behind each other.

Make the tool a coroutine over httpx.AsyncClient, resolve the API key and
the configured max_results in a single config read, and move both disk reads
off the event loop: the tool config, and the User-Agent, which reads the
installed package metadata the first time it is needed.

* refactor(community): drop the dead You.com API key wrapper

The async migration resolved the key and max_results through
_resolve_tool_settings, which left _get_api_key with no production
caller: only TestGetApiKey still exercised it. Keeping a second seam
around the same config read lets it drift from the real call path, so
drop the wrapper and point the tests at _resolve_tool_settings.

Also keep the hot path off the executor: _user_agent is cached, so only
the first call reads the installed package metadata. Later calls are
in-memory hits and now skip the to_thread round-trip.

Finally, record which httpx client factory the provider reaches for in
the runtime anchor. MockTransport runs its handler on the calling thread
without real blocking I/O, so a provider that stayed async but sent the
request through a sync client would satisfy the thread assertion; the
recorded factory is what catches that partial regression.

* test(community): align You.com URL safety stub with async tool

The URL safety matrix still patched the removed _get_api_key seam and invoked the You.com tool synchronously. Mock the awaited search with AsyncMock and call the tool through ainvoke so URL screening remains covered after the async migration.

---------

Co-authored-by: treasuresyq <309743290+treasuresyq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 17:59:14 +08:00
Cordis798
3e9a417ea6
fix(memory): apply Mem0 read policies to record conversion failures (#7307)
* fix(memory): apply Mem0 read policies to record conversion failures

* test(memory): cover Mem0 read policies through routes and tools

* fix(memory): identify malformed Mem0 records in diagnostics

---------

Co-authored-by: Cordis798 <284918518+Cordis798@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 17:57:14 +08:00
Fish
7097694abe
fix(memory): honor Mem0 failure policies for malformed JSON responses (#7202)
* fix(memory): honor Mem0 failure policies for malformed JSON responses

* fix(memory): diagnose and normalize deep Mem0 response failures

* fix(memory): include Mem0 page context in response errors

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 16:58:19 +08:00
Willem Jiang
5cdcc40582
ci: run public skill script tests in Skill Review CI (#7350) 2026-10-23 16:55:18 +08:00
Lachlan Allen
7a32a5e177
feat(exa): support a configurable Exa-compatible base URL (#7306)
Exa web_search and web_fetch accept a per-tool base_url, falling back to
EXA_BASE_URL and then the SDK default. Overrides are validated like the
Serper base URL. web_fetch screens a configured backend with the shared
delegated-backend guard.

Co-authored-by: lachy3433 <lachlanallen.co@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 16:51:36 +08:00
Kiko0301
1a5f8edc2f
docs(config): explain skills and sandbox paths across platforms (#7304)
* docs(config): explain skills and sandbox paths across platforms

* docs: clarify YAML paths and trim startup guidance

---------

Co-authored-by: Kiko0301 <191240073+Kiko0301@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 16:00:41 +08:00
EastonJiang
0d3a562c5f
fix(frontend): bound Live Browser WebSocket handshakes (#7301)
* fix(frontend): bound Live Browser WebSocket handshakes

* test: register browser-view scoped guidance

* fix(frontend): explain Browser Live connection failures

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 15:39:05 +08:00
derrickLzw
1278c5192b
fix(models): honor HTTP-date Retry-After hints in Claude retries (#7295)
* fix(models): honor HTTP-date Retry-After hints in Claude retries

* refactor(models): share bounded HTTP-date retry parsing

* test(agents): move retry date regression to middleware suite
2026-10-23 15:21:44 +08:00
Huang-404-Q
5839822aa1
fix(agents): validate the agent name in FileAgentStore.get_soul (#7265)
get_soul was the only read/write method that skipped validate_agent_name,
so a name like "../../other-user/agents/planner" reached
resolve_agent_dir and read another user's SOUL.md. Validate it like the
other methods do.

The regression test asserts ValueError for a traversal name; it reads the
other user's soul on main.

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 14:54:21 +08:00
Jiarui Li
505c33c793
fix(scheduler): reject nonexistent local times at admission (#7281)
* fix(scheduler): reject nonexistent local times at admission

* fix(scheduler): label run_at errors and clarify cron DST behavior
2026-10-23 09:13:37 +08:00
JasonH
9e5dee318b
fix(startup): make Gateway readiness timeout configurable (#7309) 2026-10-23 08:31:54 +08:00
haotianqi666
871d064f9e
fix(sandbox): preserve local files when revisions fail (#7299)
* fix(sandbox): preserve local files when revisions fail

* fix(sandbox): address revision cleanup and metadata review

---------

Co-authored-by: haotianqi666 <250384991+haotianqi666@users.noreply.github.com>
2026-10-23 08:22:45 +08:00
Hyeonsang Cho
aa978d1b32
fix(sandbox): release the E2B ownership lease when acquire finds the VM gone (#7277)
* fix(sandbox): release the E2B ownership lease when acquire finds the VM gone

The in-process reuse path (dead or unreachable cached client) and the
warm-pool reclaim path (reconnect reports "sandbox not found") dropped
their local bookkeeping but kept the VM's ownership lease. Nothing later
sees that ID: the warm sweep only walks the warm pool, reconciliation
only walks the remote inventory, and the lease renewer keeps renewing
(and re-claims a lapsed lease) until shutdown. Every reaped VM left one
entry in _owned_sandbox_ids and one renewed ownership key behind.

Call _release_ownership in both branches. Both act only on E2B's trusted
not-found signal, the store's release is a no-op for a peer's lease, and
a backend error still drops the local ID so the shared lease expires on
its TTL.

* docs(changelog): reference #7277 for the E2B dead-VM lease fix

* test(sandbox): pin that transient E2B errors keep the ownership lease

The dead-VM release is only safe because the gone signatures match
narrowly. Pin the other side: a non-gone ping error keeps reusing the
cached VM, and a non-gone warm reconnect error leaves the lease to
eviction.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 08:08:35 +08:00
N
64bfd9ffd5
fix(startup): detect extras for browser-configured IM channels (#7280)
* fix(startup): detect extras for browser-configured IM channels

* docs(startup): clarify channel extra provider source

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 07:55:51 +08:00
yetuge
859d45db7e
fix(mcp): keep host paths out of the MCP config mutation 500 details (#7275)
* fix(mcp): keep host paths out of the MCP config mutation 500 details

The five MCP config mutation routes answered an unexpected failure with
`f"Failed to …: {str(e)}"`. Each one runs a read-modify-write of
`extensions_config.json` (`_apply_mcp_config_update`,
`_apply_mcp_servers_create`, `_apply_mcp_server_config_update`,
`_apply_mcp_server_delete`, `_apply_mcp_server_state_update`), so the error the
handler catches is usually an `OSError` from `atomic_write_extensions_config`
that names the absolute host path — the `DEER_FLOW_HOME` layout of the server.

The custom-agent, user-profile and skills-read routes already return fixed text
for this reason (#7255; `routers/AGENTS.md`: "agent config parse errors and
`OSError`s carry host paths and stay in the server log"). These five were the
remaining MCP mutation 500s on the same file, so they now return the fixed
detail and keep the exception in the existing log line.

Red first, on the unmodified `main` — all five put the planted host path in
`detail`:

    $ python -m pytest tests/test_mcp_config_error_details.py -q
    5 failed in 6.37s

After the change, 5 passed; the wider MCP suites (246 tests across
`blocking_io/test_mcp_router.py`, `test_mcp_config_secrets.py`,
`test_mcp_lifecycle*.py`) stay green.

* fix(mcp): normalize personal config mutation errors

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 07:20:21 +08:00
哈基米
a1eb397141
fix(skills): treat only top-level scripts/ paths as executable in skill_manage (#7088)
* fix(skills): treat only top-level scripts/ paths as executable in skill_manage

write_file classified a support file as executable content with a substring
test ("scripts/" in path), so any support path merely containing "scripts/"
(e.g. references/scripts/overview.md or templates/my-scripts/setup.md) was
moderated as executable: that scan requires an exact "allow" decision and
hard-blocks when the moderation model is unavailable. Executable content is
now exactly a file under the skill's top-level scripts/ directory, matching
the scripts/ member rule in skills/package_files.py and the same Path
semantics the storage layer already used to validate the support path.

* refactor(skills): share the scripts/ member rule with skill_manage

The write_file executable gate re-derived the top-level scripts/ member rule
locally. Use package_files.is_scripts_member instead so the gate, installer,
and SkillScan stay in lockstep.

Extend the regression test to pin scripts/sub/run.sh and ./scripts/run.sh as
executable content, plus the shared helper's normalization.

* fix(skills): moderate nested code files as executable content in skill_manage

The write_file executable gate classified only top-level <scripts/> members as
code, so runnable support files elsewhere in the package
(references/scripts/helper.py, templates/my-scripts/run.sh,
assets/my-scripts/tool.js) and extensionless shebang files fell back to static
moderation: a warn decision, or an unavailable moderation model with
security_fail_closed=false, wrote them to disk. The deterministic package scan
does not enforce the allow-only model verdict, and this write path publishes
through write_custom_skill without running the mutations CandidateScanner, so no
later check restores executable moderation.

Classify with the complete shared code-file rule from
deerflow/skills/package_files.py (scripts members, code suffixes, and
extensionless shebang files) instead of the scripts-member rule alone, and keep
Markdown/data support files on the static tier. Extend the regression suite with
the code-suffix/ shebang cases plus the warn and unavailable-provider outcomes.
2026-10-23 00:28:20 +08:00
NanHan Qing
6c2042a043
fix(helm): give each Secret pair one precedence chain (#7291)
* refactor(helm): derive the postgres Secret name from one helper

`databaseUrlSecret` re-encoded the `postgresql.external.existingSecret` then
`postgresql.existingSecret` chain in its own `if`/`else if`. Three consumers
read it — the gateway's `DATABASE_URL`, the postgres StatefulSet's
`POSTGRES_PASSWORD`, and `postgres-secret.yaml`'s own `metadata.name` — so a
source added to one helper and not the other would make the gateway and the
StatefulSet resolve different Secrets. That is the class of bug #7149 fixed
for the redis pair.

Extract `databaseUserSecret` as the single encoding of the chain and let
`databaseUrlSecret` fall back to the chart-managed name when it is empty,
mirroring `redisUserSecret` / `redisUrlSecret`.

Pure refactor: the rendered manifest is byte-identical before and after across
the four configurations (chart-managed, `existingSecret`,
`external.existingSecret`, and both set), compared with `helm template`.

Adds `test_helm_database_secret_precedence.py`, which pins that both consumers
name the same Secret and that only the chart-managed branch renders a Secret.
Dropping either precedence source fails the two cases that depend on it.

Suggested in review of #7161.

* fix(helm): keep the postgres Secret helper's truthiness

Review follow-up on the previous commit. Two things were still wrong:

`postgres-secret.yaml` kept its own copy of the precedence chain for the
render gate, so "the only place the chain is encoded" was not yet true — a
source added to `databaseUserSecret` alone would have left that gate empty
and rendered an unreferenced `<release>-postgres` Secret. It now reads the
helper, like `redis-secret.yaml` already did.

`or` returns its last operand even when it is falsy, so
`postgresql.existingSecret: false` (how people spell "not set") named the
Secret `false` instead of falling back to the chart-managed one. Chaining
`default` keeps the old if/else-if truthiness and yields an empty string
for any falsy value; the gateway's `$pgConfigured` now reads the same
helper rather than re-listing the two keys.

Two falsy cases join the parametrize list: `false` in the lower-precedence
slot and in both slots. Both fail against the previous `or` version with
`assert [] == [False]` — the Secret literally named `false` — so they are
regressions this test can actually catch. The other four configurations
render byte-identically before and after.

* fix(helm): keep the redis user-Secret helper's truthiness

Same defect the postgres helper had, one release later: `redisUserSecret`
used `or`, which returns its last operand even when falsy, so
`redis.existingSecret: false` named both the gateway's `redis-url` ref and
the StatefulSet's `redis-password` ref after a Secret called `false`. The
render gate in `redis-secret.yaml` saw the string "false" as truthy, so it
skipped creating the chart-managed Secret — leaving two dangling refs and
a StatefulSet that cannot start.

`default` chaining restores the pre-`redisUserSecret` if/else-if behaviour:
any falsy value yields an empty string, so the chart-managed branch is
taken. `redisConfigured` now reads the helper instead of re-listing the two
keys, matching the gateway's `$pgConfigured`.

Verified against the four string-valued configurations (chart-managed,
`existingSecret`, `external.existingSecret`, both): renders are
byte-identical before and after. The two new falsy cases fail on the
previous version, and `--basetemp` makes all 14 cases in this file pass.

* docs(helm): drop a stray article in the helper comment
2026-10-23 00:26:48 +08:00
Totoro
2f0cd407fc
fix(goal): base no-progress counting on successful operations (#7271)
* fix(goal): track successful operations for no-progress checks

* fix(goal): reject failed exits and narration-only progress

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-10-23 00:14:32 +08:00
Hyeonsang Cho
66508d7522
fix(auth): map malformed OIDC provider bodies to OIDCError (#7258)
* fix(auth): map malformed OIDC provider bodies to OIDCError

The SSO login route answers OIDCError with 502 and the callback with
an sso_failed redirect, but oidc.py raised OIDCError only for transport
and HTTP status errors. A provider answering 200 with an HTML page or
any non-object JSON raised JSONDecodeError, KeyError or AttributeError
instead, so both routes failed with a 500. A malformed UserInfo body
also aborted the login, although UserInfo is best-effort.

Decode discovery, token, JWKS and UserInfo bodies through one helper
that requires a JSON object, require string endpoints and issuer, reject
a non-list JWKS keys field, skip non-object JWK entries, and wrap a
malformed ID token header as OIDCValidationError. Build discovery
metadata before caching it so a rejected document is not served from
the cache.

* docs(changelog): reference #7258 for the OIDC malformed body fix

* fix(auth): report a falsy non-string OIDC issuer as wrong-typed

Check the issuer's type before its presence so 0, false and [] get the
non-string message instead of "missing the issuer field"; an absent or
empty issuer still reports missing.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-22 19:35:43 +08:00
Huang-404-Q
c0ba948825
fix(auth): treat a signed token with wrong claims as malformed (#7266)
* fix(auth): treat a signed token with wrong claims as malformed

decode_token caught jwt.PyJWTError but not pydantic.ValidationError, so a
correctly signed token without sub/exp raised out of the function. The
OIDC state cookie shares jwt_secret and has exactly that shape, and the
OAuth start endpoint is public, so an unauthenticated caller could turn
it into a 500 (or a broken WebSocket handshake) instead of a 401.

Catch ValidationError and return TokenError.MALFORMED.

The regression test signs an OIDC-state-shaped payload and asserts
MALFORMED; it raises on main.

* fix(auth): treat a signed OIDC state cookie with bad claims as unusable

_verify_state_signed had the same latent shape as decode_token: it caught
only jwt.PyJWTError, so a state cookie signed with jwt_secret but missing
required claims raised pydantic.ValidationError straight into the public
/api/v1/auth/callback/{provider} endpoint instead of returning None and
letting the handler answer 403.

Also pin the decode_token precedence a reviewer raised: an expired token
that is missing sub reports EXPIRED because jwt.decode raises before the
payload model runs.

* docs: reduce inherited backend guidance size

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-22 19:31:37 +08:00
yetuge
b57488734d
fix(channels): recover from undecodable runtime config and WeChat state (#7273)
`store.py` already treats a state file whose bytes are not UTF-8 as corruption:
`_read_legacy_file` catches `UnicodeDecodeError` and the three call paths start
fresh or skip the import (#7233, AGENTS.md: "Store JSON decode errors follow
corrupt-file recovery"). Four readers in the same package read the same kind of
file with an explicit `encoding="utf-8"` but catch only
`json.JSONDecodeError`/`OSError`; `UnicodeDecodeError` is a `ValueError`, so it
escapes and takes the caller down instead of following that policy:

* `ChannelRuntimeConfigStore._load` — `ChannelService.start()` and the
  channel-connections router construct the store, so one undecodable
  `runtime-config.json` stops every IM channel from starting, including the
  non-fallback path where a peer's copy should be rewritten from this process's
  snapshot.
* `_read_legacy_file` — the one-time `runtime-config.json` import aborts instead
  of keeping the file and reporting it as corrupt.
* `WechatChannel._load_state` / `_load_auth_state` — `start()` loads both through
  `asyncio.to_thread`, so an undecodable cursor or auth file raises out of the
  channel's own start instead of warning and continuing unauthenticated.

Each reader now names `UnicodeDecodeError` next to the existing handlers, so the
outcome matches the sibling store: a warning, the in-memory fallback, and the
file left in place.
2026-10-22 19:26:38 +08:00
Paul Hunkin
27aa05f54e
feat(community): add Jsonify dataset rows tool (#7253)
* feat(community): add Jsonify dataset rows tool

Adds deerflow.community.jsonify.tools:dataset_rows_tool, a read-only tool
that returns rows from maintained Jsonify datasets. Jsonify pipelines run on
a schedule and repair themselves when a source changes, so the agent reads a
current table instead of scraping.

- operator configures a named map of dataset -> serve URL; the model picks
  by name and never sees the URL
- serve URLs must be https without userinfo and pass validate_public_http_url
  before any request; the URL is redacted from error text
- paging with has_more/next_offset, version pinned after the first page,
  limit clamped to the configured max_rows, system columns stripped
- config.example.yaml entry under tools; unit tests cover paging, clamping,
  unknown dataset, unconfigured tool, http/userinfo rejection, private
  address block and error redaction

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrmeK98ZKoxZNiUiz8kVeX

* fix(community): harden Jsonify tool paging, logging and error handling

- Stop paging on an empty page or a next_offset that does not advance
- Log config validation failures by field location and error type only
- Validate page shape; malformed or non-JSON pages return a clean error
- Transport errors report the exception type, never the URL
- Add truncated flag when rows were clipped or more rows remain
- Tests for each case

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrmeK98ZKoxZNiUiz8kVeX

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 19:23:56 +08:00
Aling33
e830c85adf
fix(scripts): reject a directory passed to support_bundle --out (#7256)
* fix(scripts): reject a directory passed to support_bundle --out

`--out` names the zip, but the path went straight to `zipfile.ZipFile`, so
pointing it at an existing directory ended in a raw `PermissionError` traceback
(Windows) instead of the CLI's usual `error: ...` + exit 2.

`create_support_bundle` now raises the `ValueError` the CLI already maps to a
clean message, and the regression test pins exit 2 with no traceback.

* docs(scripts): say "zip file path" for support_bundle --out

Review nit: the help text said only "Output zip path", so the
file-not-directory contract the new validation enforces was invisible until a
run failed.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-22 18:26:55 +08:00
Xuehao Xu
29ba806fc6
feat(knowledge): add independent Wiki and entity relation retrieval tools (#7171)
* feat(knowledge): add independent Wiki and entity relation retrieval tools

* fix(knowledge): address review diagnostics and CI catalog coverage

* fix(knowledge): deduplicate graph evidence and update catalog e2e

* fix(knowledge): clarify bounded evidence and pin browser CI runners

* Update e2e-tests to run on ubuntu-latest

* Update workflow to use ubuntu-latest

* test(ci): stop pinning maintainer-controlled browser runner images

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-22 16:54:58 +08:00
TAI-YUAN GUO
f5907405a0
fix(workspace): don't infer created or deleted files from truncated snapshots (#7172)
* fix(workspace): don't infer created or deleted files from truncated snapshots

A bounded workspace scan stops at max_scanned_files and marks the snapshot
truncated. Absence from such a snapshot is unknown, so creation is only
inferred from a complete before snapshot and deletion only from a complete
after snapshot. get_changed_paths() is the single filter for the summary,
text capture and output-delivery detection.

The scanner now walks subdirectories in sorted order so the truncation
boundary is reproducible across the before and after scans, and the docs
note that the budget is consumed root by root (workspace before outputs).

Supersedes #5231.

Co-authored-by: liutian <91778998+ImcLiuQian@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): record truncated delivery verification skips

Record verification_skipped=truncated_pre_run_snapshot in run.delivery
receipts when a truncated pre-run scan leaves no provable output changes.
Carry the snapshot into both normal and exceptional receipt finalization.

Keep observed-output verification and terminal statuses unchanged. Cover
success, failure, cancellation and capture failures through the real worker,
and document the per-run receipt marker.

---------

Co-authored-by: liutian <91778998+ImcLiuQian@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-22 16:52:53 +08:00
Fish
97b12b1381
fix(memory): exclude hidden framework messages from Honcho writes (#7159)
* fix(memory): exclude hidden framework messages from Honcho writes

* fix(memory): isolate hidden-message hook failures
2026-10-22 16:51:47 +08:00
Lucien
9e08568455
fix(scheduler): preserve recurring cadence after manual trials (#7209)
* fix(scheduler): preserve cadence after manual trials

* test(scheduler): cover overdue trials and share cursor rule
2026-10-22 16:06:09 +08:00
llb555
582b1f5a28
fix(uploads,integrations): keep atomic-write cleanup from replacing the caller's outcome (#7200)
* fix(uploads,integrations): keep atomic-write cleanup from replacing the caller's outcome

* fix(lark): keep the download error when archive cleanup fails

* test(lark): keep the download fixtures inside the test directory
2026-10-22 16:03:43 +08:00