diff --git a/CHANGELOG.md b/CHANGELOG.md index e4ff6f5fe..dc5435f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 This section accumulates work toward the **2.1.0** milestone ([milestone 2](https://github.com/bytedance/deer-flow/milestone/2)). +This release closes that milestone with **765 merged pull requests**. ### ⚠ Breaking changes @@ -104,9 +105,37 @@ This section accumulates work toward the **2.1.0** milestone #### Scheduler - **scheduler:** Scheduled tasks accept `interval` (`schedule_spec.every_seconds`) - in addition to `once` and `cron`. Cadence is UTC `now + N` with no missed-beat - catch-up. N is at least `scheduler.min_once_delay_seconds` (default 60s) and at - most 30 days. + in addition to `once` and `cron`. Cadence is UTC `now + N` with no + missed-beat catch-up. N is at least `scheduler.min_once_delay_seconds` + (default 60s) and at most 30 days. ([#5291]) +- **scheduled-tasks:** Upcoming cron occurrences can be inspected before a task + is saved. `POST /api/scheduled-tasks/preview-cron` takes a five-field cron + expression, a timezone, a `count` of 1–10 (default 5), and an optional aware + reference time, and returns the normalized cron, the effective UTC reference, + and the occurrences in both UTC and local time. It runs the scheduler's own + calculator in a worker thread, so DST handling matches what a saved task will + actually do, and it reserves nothing and touches no task, thread, or run + store; invalid input and date-range failures return 422. ([#5381]) +- **scheduled-tasks:** Run history can be filtered server-side by occurrence + status. Finding a rare failure used to mean downloading every history page + and filtering in the client, since a failure behind many newer successes + could sit on any page; `GET /api/scheduled-tasks/{task_id}/runs` now accepts + `?status=` with `queued`, `launching`, `running`, `success`, `failed`, + `skipped`, and `interrupted`, applying the predicate before pagination so + `?status=failed&limit=50&offset=0` returns the first page of real failures. + Unknown values, including the parent task's own `completed`, return 422, + empty results return `[]`, and the unfiltered array response is unchanged. + ([#5384]) +- **scheduled-tasks:** The scheduled-task detail pane can page backwards + through execution records. It showed only the latest 50 even though the + endpoint already supports limit and offset, so anything older was unreachable + from the UI. Newer, Older, and Latest navigation now walk the history with + localized page, loading, error, and retry states; each request fetches one + row past the page to decide whether an older page exists rather than + inventing a total count, a short final page disables Older, switching tasks + resets to the latest page and cancels the obsolete request so a late result + cannot replace the new task's history, and only the latest page polls. + ([#5363]) #### Authentication - **auth:** Personal access tokens (PAT) for programmatic API access: @@ -121,18 +150,30 @@ This section accumulates work toward the **2.1.0** milestone applies on the next login without a Gateway restart — the unblock path for deployments behind corporate proxies/NAT where many users share one egress IP. Defaults are unchanged. ([#5110]) +- **settings:** Account preferences survive a cleared browser or a move to + another device. The notification toggle, default model, conversation mode, + and reasoning effort are stored as independent `(user_id, key)` rows behind + session-authenticated `GET`/`PATCH /api/v1/auth/preferences`, hydrated from + the server before first paint, with a tab-local outbox and capped-backoff + retry so a failed write is not silently dropped; `null` resets a field. Only + those four allowlisted fields are transmitted — device notification + permission, display settings, and thread-specific model overrides stay local, + and an automatic composer fallback is never uploaded as an explicit account + preference. Existing unscoped local settings are not migrated, since they + carry no known owner, so users reselect these four once after upgrading. + ([#5397]) #### Agents & runtime - **scheduler:** Scheduled tasks can pin `assistant_id` to `lead_agent` (the default) or a custom agent the owner already has. Unknown or malformed names return 422. The workspace create/edit form exposes the same choice. - ([#5286]) + ([#5286], [#5288]) - **gateway:** `GET /api/threads/{thread_id}/runs/page` walks thread run history with a `(created_at, run_id)` keyset cursor (`{data, has_more, next_before_created_at, next_before_run_id}`). `GET /api/threads/{thread_id}/runs` still returns a bare array of the newest 100 runs so LangGraph SDK clients keep - working. ([#5282]) + working. ([#5282], [#5283]) - **middleware:** New `TokenBudgetMiddleware` enforces a per-run token budget, shared additively across the lead agent and subagents. ([#3412]) - **middleware:** Structured tool-result metadata and a tool-progress state @@ -217,24 +258,154 @@ This section accumulates work toward the **2.1.0** milestone - **agents:** The read-before-write gate now elides the dead payload of a blocked `write_file` / `str_replace` call (`content`, `old_str`, `new_str`) from model-bound requests. A blocked call never ran and must be re-issued - after a re-read, so the original arguments only cost context; stored - history, receipts, and the run journal keep them. Blocked results are - paired with call occurrences (tool-call ids may repeat across turns), and - a request whose history was rewritten drops OpenAI `resp_` response ids so + after a re-read, so the original arguments only cost context; stored history, + receipts, and the run journal keep them. Blocked results are paired with call + occurrences (tool-call ids may repeat across turns), and a request whose + history was rewritten drops OpenAI `resp_` response ids so `use_previous_response_id` chaining cannot resume the original server-side history. Controlled by `read_before_write.elide_blocked_payloads` (default - on) and `read_before_write.elide_min_chars` (default 2000). + on) and `read_before_write.elide_min_chars` (default 2000). ([#5329]) - **agents:** `ToolOutputBudgetMiddleware` now also elides the `content` of a - successful `write_file` call from model-bound requests once the same path - was read or modified again later in the conversation. After a successful - write the file on disk is the source of truth, and the read-before-write - gate forces a `read_file` before the next modification, so the historical - copy was redundant with that read and long report-writing runs carried every - section twice. The newest `tool_output.keep_recent_writes` successful writes - (default 1) always stay visible, `str_replace` payloads are never touched, - and stored history, receipts, and the run journal keep the original - arguments. Controlled by `tool_output.elide_superseded_writes` (default on) - and `tool_output.superseded_write_min_chars` (default 2000). + successful `write_file` call from model-bound requests once the same path was + read or modified again later in the conversation. After a successful write + the file on disk is the source of truth, and the read-before-write gate + forces a `read_file` before the next modification, so the historical copy was + redundant with that read and long report-writing runs carried every section + twice. The newest `tool_output.keep_recent_writes` successful writes (default + 1) always stay visible, `str_replace` payloads are never touched, and stored + history, receipts, and the run journal keep the original arguments. + Controlled by `tool_output.elide_superseded_writes` (default on) and + `tool_output.superseded_write_min_chars` (default 2000). ([#5374]) +- **agents:** Custom Agent `config.yaml` accepts `memory_enabled: false` to run + an agent as a stateless execution worker; omission defaults to `true`. The + opt-out removes only that agent from the memory lifecycle — recalled-memory + injection, passive capture, the `memory_search` / `memory_add` / + `memory_update` / `memory_delete` tools, and tool-mode memory guidance are + all suppressed, and both automatic summarization and manual `/compact` skip + the durable memory flush. Switching an existing agent off clears only the + frozen, server-tagged memory reminders from checkpoint state: date reminders, + real user messages, and untagged lookalikes survive, and the global memory + setting is unchanged for every other agent. ([#5167]) +- **runtime:** `ToolProgressMiddleware`'s interventions become auditable in the + durable run-event stream. A new `middleware:tool_progress` event is persisted + for each effective `warn`, `block`, `recover`, or later-invocation `reset` + phase transition; previously those replanning hints, tool-state promotions, + and short-circuits disappeared into process logs, so a persisted run could + not show whether the model recovered on its own or the runtime guard + intervened. Only bounded decision metadata is recorded — status and recovery + vocabularies derive from the canonical `tool_result_meta` schema, and tool + arguments, result content, prompts, and content-derived hashes never enter + the event. Subagent and other recorder attribution keys are server-owned at + both the Gateway and embedded-worker boundaries, so a caller cannot forge + durable attribution. Events appear only when ToolProgress is enabled, which + remains off by default. ([#5214]) +- **subagents:** Durable `batch_task` items accept the same optional + `acceptance_criteria` that ordinary `task` delegations already take, so a + batch worker can no longer claim a missing deliverable exists and still land + as a succeeded item with no recorded check. Criteria are normalized before + persistence (20 usable entries, 500 characters after neutralization; empty + becomes `null`) and preserved across lease recovery, and a completed + execution reuses the existing deterministic checker against owner-scoped + thread files and recorded test-command evidence. Item queries and JSONL + exports gain nullable `acceptance_criteria` and a separately validated + `acceptance_verdict` (`holds`, `does not hold`, or `UNVERIFIED`): a completed + item with a report, a missing CSV, and an unsupported quality claim stays + `succeeded` with those three leaves and is not retried automatically. Items + without criteria and legacy rows report no verdict. ([#5289]) +- **subagents:** `task(context_mode="snapshot")` lets a delegation carry a + dispatch-time copy of the retained parent conversation, instead of the + delegated prompt alone. Snapshot mode captures conversation content and + `summary_text` after delegation validation and before child setup, so a + rejected delegation serializes nothing and later parent edits never reach the + child. It is rendered as a separate historical `HumanMessage` ahead of the + task, keeping plain text, text blocks, historical tool-call descriptions with + their matching results, and serializable media input; unserializable media + becomes an explicit omission notice, while parent system messages, hidden + framework state (injected memory and todo state included), reasoning blocks, + execution metadata, and pending tool calls are excluded. The child keeps its + own role, model, tools, and skill restrictions, and parent tool frames never + enter its execution history, so parent actions cannot populate child + receipts, execution steps, or bash evidence. `context_mode="isolated"` + remains the default, and `batch_task` items still require self-contained + prompts. ([#5367]) +- **context:** Opt-in task notes and compacted-history recall, gated by + `task_continuity.enabled` (default `false`; the config schema gains the + section with disabled defaults). Enabling it exposes `task_note`, + `history_search`, and `history_read` to standard lead agents and + `DeerFlowClient` under the existing authorization and skill policies, with + both sync and async tool execution. `task_note` keeps short checkpointed + notes of constraints, decisions, failed attempts, and next steps in a shared + state channel that enforces notebook limits and report shape on every write, + rendered as escaped historical data in the durable-context human message. + Automatic and manual compaction additionally preserve bounded message and + tool text — including genuine hidden clarification-card answers — into a + user/thread-local SQLite FTS5 archive outside the sandbox mount, which + `history_search` and `history_read` then query; malformed checkpoint metadata + is reported as unavailable instead of aborting a model call or compaction, + and a storage failure leaves ordinary compaction working. This is lexical + recall within one task: it does not resume runs, replicate archives across + hosts, or create cross-thread memory, and it adds no embedding dependency. + ([#5382]) +- **gateway:** Gateway runs no longer hardcode a recursion limit of 100. A + deployment whose normal tasks need longer agent loops had to make every API + caller supply a request-level override, and a caller that did not hit + `GraphRecursionError` on otherwise valid work. A hot-reloaded top-level + `recursion_limit` now supplies the default when a request omits it (100, for + backward compatibility), an explicit request value still wins, an invalid + request value falls back to the configured default, and `max_recursion_limit` + caps both configured and client-supplied values. ([#5390]) +- **gateway:** A Gateway run can be asked to read a specified earlier + conversation. Opt-in `read_conversation` plus a `conversation_references` run + field (up to three thread ids or same-origin chat URLs) grant the lead agent + a model-facing read of a previous conversation while it continues the current + task; ordinary message text never confers access, and the grant is bound to + that run's references, the run's `runs:read` permission, and ownership of + each source. Reads reuse the existing transcript pagination and visibility + rules, returning bounded user and assistant text with source ids, + continuation cursors, and truncation or unavailability notices while + excluding hidden context, reasoning blocks, and raw tool results. The reader + is never persisted in configuration and is released when the run ends, so + resume and replay need the references again; bootstrap, subagent, and + ordinary embedded paths do not receive it. Text is capped at 4,000 characters + per message and 20,000 per page. ([#5399]) +- **gateway:** Conversation references can be sent by SDK clients and detected + by them. The LangGraph JS SDK's `RunsClient.stream` drops unknown top-level + fields, so a browser client could not send the `conversation_references` + field at all, and it had no way to tell whether the reader was enabled — + leaving no way to hide an entry point where it was off. Run create, stream, + and wait requests now accept `context.conversation_references` with the same + bounds (at most three, each 1–2048 characters) and the same error locations, + lifted into the canonical field before validation and removed from `context` + so it never reaches the run context or the checkpointed configurable; sending + both spellings returns 422, and a copy left in `body.config` still grants + nothing. `GET /api/features` reports `conversation_references: {enabled, + max_references}`, where `enabled` follows the configured tool list so a + `config.yaml` change takes effect without a restart. ([#5463]) +- **tools:** `list_uploaded_files` accepts optional `query` (a case-insensitive + filename substring) and `extensions` (`"pdf"` or `".PDF"`, both meaning + `.pdf`), and both filters run **before** the 20-file truncation that + lazy-loading history already applied. Previously the cap hit the unfiltered + mtime-sorted list, so "analyze those PDFs I uploaded before" in a thread full + of newer screenshots pushed the PDFs into `omitted_summary` and left the + agent with no path to hand `read_file`; `total_count`, `truncated`, and + `omitted_summary` now describe the filtered set. Omitting both parameters + keeps today's behavior, the two filters combine with AND, blank or invalid + values count as no filter, and a filtered miss returns `No uploaded files + matched the given filters.`. ([#5341]) +- **agents:** Custom Agents showed their ASCII-only storage identifier in the + gallery, chat header and welcome page, so an owner could not label an agent + in Chinese or any other non-ASCII script. Each agent can now carry an + optional `display_name` — whitespace-trimmed, at most 100 characters — on its + config document and through the create/update/read/list APIs; omitting the + field on an update preserves it, and `null` or a blank value clears it. The + label is edited from the agent gallery's settings button, counts Unicode code + points against a visible budget, and is rejected when it holds control + characters, invisible formatting characters, or nothing but marks, separators + or format characters — ordinary multilingual text and ZWJ emoji stay allowed. + Paths, URLs, the runtime `agent_name`, ownership and React identity keep + using the stable identifier, so nothing needs migrating, and an invalid + stored value is ignored on read rather than breaking list/detail/bootstrap. + ([#5324]) #### Memory @@ -259,12 +430,27 @@ This section accumulates work toward the **2.1.0** milestone plus Honcho as a user-model memory provider. ([#4509], [#4528], [#4730]) - **memory:** A hybrid fact eviction policy blends multiple signals when deciding which stored facts to drop as memory fills. ([#4789]) +- **memory:** Opt-in write-side guard against storing paraphrases of a fact + that is already remembered, so repeated extraction no longer consumes memory + capacity and prompt tokens with duplicates. The write-side gate is + `memory.backend_config.fact_dedup_enabled` (default `false`), with + `memory.backend_config.fact_dedup_similarity_threshold` (default `0.7`, range + `0.5`–`1.0`). Within the current user/agent scope a new same-category fact is + compared against existing ones with bounded token-Jaccard similarity over + Latin words and Chinese bigrams — a lexical heuristic, not semantic + equivalence detection. A merge keeps the existing fact's id, content, and + creation time, raises `confidence` to the higher of the two, and refreshes + `source` only when confidence increased; the `facts_merged_dedup` counter + records how many merges happened. Paired correction replacements bypass + near-dedup, removal proposals exclude their own targets from matching, and no + confirmation signal is fabricated — reinforcement still requires a genuine + human message. ([#5254]) #### Skills - **skills:** The built-in image-generation skill can use OpenAI-compatible Images APIs for generation and reference-image editing, with configurable - endpoint, model, size, and output format. + endpoint, model, size, and output format. ([#5389]) - **skills:** Native SkillScan (phase 1) statically analyzes skill packages at load, and `describe_skill` enables deferred discovery so the model fetches a skill's schema on demand instead of loading all skills up front. ([#3033], @@ -277,6 +463,33 @@ This section accumulates work toward the **2.1.0** milestone ([#5039]) - **skills:** Podcast-generation Volcengine voices are configurable per speaker gender, with trimmed blank-safe defaults. ([#5156]) +- **skills:** Custom skills can be exported from Settings, disabled ones + included, so what you back up, move between installations, or send a teammate + is the version actually installed rather than the original upload. A + bilingual preview lists package size, paginated file paths, declared + requirements, and actionable blockers, and the two new admin-only manifest + and download endpoints bind that preview to a revision digest — a package + changed in between returns `409` instead of a stale ZIP. Supporting files, + empty directories, and normalized executable permissions survive the round + trip, while links, special files, nonportable paths, nested skill roots, and + executable binaries are rejected. Export copies raw saved files and is not a + redaction step: it neither scans for secrets nor runs skill scripts, so + credentials and account settings must still be configured separately. + ([#5332]) +- **skills:** Deferred skill discovery ranks candidates instead of matching the + model's whole free-text query as one regular expression. With + `skills.deferred_discovery: true` the agent sees only skill names and calls + `describe_skill` to choose what to load, but a multi-term intent had to match + contiguously: `chart visualization` never found `chart-visualization`, and + `analyze Python` never found a skill described as "Analyze data with Python, + pandas, jupyter" — so the agent could not load the matching workflow or its + active tool policy. Lookup now normalizes Unicode, case, and name separators + and scores candidates by literal intent-term coverage, with name matches + outranking description-only matches at equal coverage and catalog order + breaking ties so results stay reproducible; model-generated queries are + bounded to 256 characters and 16 unique terms. `select:` exact selection and + `+required` name filtering are unchanged, and no embeddings, model calls, or + telemetry are involved. ([#5369]) #### Models & integrations @@ -322,6 +535,36 @@ This section accumulates work toward the **2.1.0** milestone the ranked chunks as citation-numbered text, redacts the optional `X-API-Key` on every model-visible path, and maps server error messages to actionable tool errors. ([#5209]) +- **models:** A per-user favorites layer keeps frequently used models reachable + without changing the selected or default one. The compact two-line model list + is now an anchored dropdown in both the main chat and Side Chat, with an + inline star per row; favorites sort into the first group without selecting a + model or closing the picker, and persist per signed-in user with cross-tab + synchronization and restoration of temporarily unavailable entries. The + existing search field is removed as a deliberate simplification, with no + separate management mode. ([#5441]) +- **models:** Bounded concurrency does not bound request rate: several + concurrent runs could still exhaust a provider's requests-per-minute + allowance, and nothing paced the demand before dispatch, so a 429 was the + first signal anything was wrong. A new optional `models[].request_admission` + block admits model calls through a process-local queue, taking + `requests_per_minute` (required), an optional `group` (defaults to the model + config name), `max_wait_seconds` (default `300`) and `max_queue_size` + (default `256`); at 60 RPM admissions land at least a second apart, and idle + periods accumulate no burst credit. One bounded FIFO is shared across + synchronous callers, independent asyncio loops and every model instance in an + explicit group, whose policies must be identical — a conflicting setting + fails construction and needs a restart. Cancellation and timeout drop a + waiter without consuming an admission, and a full queue fails before + dispatch. The limiter attaches through the LangChain `BaseChatModel` hook, so + agent models, the invoke and stream paths and factory-created auxiliary + models are all covered, and the factory strips the policy from provider + kwargs and sets the exposed SDK `max_retries` to zero so retries cannot + silently bypass the hook — existing agent middleware retries re-enter + admission, while non-middleware callers lose SDK retries. Budgets are per + process, so an operator must partition an account allowance across workers, + replicas and other clients; the limiter counts requests, not tokens, and + unconfigured models behave exactly as before. ([#5432]) #### MCP @@ -347,7 +590,7 @@ This section accumulates work toward the **2.1.0** milestone (`https://search.parallel.ai/mcp`, HTTP, no auth by default) ships in `extensions_config.example.json`, disabled by default; enabling it exposes `parallel-search_web_search` and `parallel-search_web_fetch`, with optional - Bearer authentication documented. ([#5028]) + Bearer authentication documented. ([#5028], [#5501]) #### Channels @@ -393,6 +636,17 @@ This section accumulates work toward the **2.1.0** milestone middleware already stamps on every authenticated request — no extra provider evaluations — so the frontend can hide actions the caller's role cannot perform. ([#5228]) +- **authz:** A role denied `threads:delete` or `runs:cancel` was still shown + the thread-row Delete menu item, the sidecar delete button and an enabled + composer stop button, so the UI offered an action the Gateway's + `@require_permission` guard would only reject. The frontend now consumes the + effective permissions `GET /auth/me` reports and hides the delete + affordances, and disables the stop button while streaming with an + `aria-label`/`title` naming the permission boundary. Enforcement is unchanged + — the Gateway guards remain the single decision point — and an absent, `null` + or not-yet-loaded permission list is treated as permissive, so a mixed + old-backend/new-frontend deploy can never hide an action the caller can still + perform. ([#5294]) #### Sandbox & provisioner @@ -433,6 +687,41 @@ This section accumulates work toward the **2.1.0** milestone context-compaction records, guardrail decisions, and the MCP origin of a tool. `deerflow-extension-api` moves to 0.2.0; extensions written against 0.1 are refused at startup with an install hint. ([#4863]) +- **extensions:** An `extensions.middlewares` entry may be `{class, kwargs}` in + addition to the existing `module.path:ClassName` string, and `kwargs` is + passed to the constructor. Operator-managed middleware that needs a + threshold, a header name, or any other argument no longer has to ship a + hardcoded subclass just to set one value. String entries still construct with + no arguments, blank class paths are rejected at config validation instead of + failing later at agent creation, and unknown fields such as `apply_to` are + refused. ([#5312]) +- **extensions:** `deerflow extensions upgrade SOURCE` (also exposed as `make + extension-upgrade SOURCE=...`) replaces a managed local snapshot, or re-pins + a requirement already in the `extensions` group, and adopts the existing + `plugins:` record so its private `config` and `required` survive. Moving to a + newer pin used to mean `remove` then `install`, and `remove` deletes the + whole record — secrets included — while an already-snapshotted local + directory could not be reinstalled at all without editing the managed copy by + hand. Plain `install` is unchanged and still refuses an existing local + snapshot, upgrading a source that is not installed fails closed with an + install hint, and a failed upgrade restores the previous snapshot even when a + concurrent `pyproject.toml` / `uv.lock` edit blocks dependency-file rollback. + ([#5347]) +- **extensions:** A new optional `RunEvidenceReader` contract lets a Gateway + extension learn what changed since it last looked, instead of reconciling + whole threads: a host-owned monotonic cursor drives durable changed-run + discovery, alongside scoped per-run event paging and authoritative status + reads. Cursors are opaque, versioned, scope-bound, replay-safe, and durable + for database-backed stores; paging orders by `(change_seq, run_id)` rather + than inferring global order from thread-scoped event sequences or timestamps; + progress snapshots and lease heartbeats do not advance the clock; and + deletions emit no tombstone, so consumers poll status and treat a missing run + as absent. Existing extensions are unaffected because + `ExtensionRuntimeDeps.run_evidence_reader` defaults to `None`, and a store + that cannot serve a page fails explicitly rather than reporting a + misleadingly empty one. The production reader is app-scoped with global + cross-user visibility for trusted operator extensions; it redacts event + metadata but leaves content unchanged. ([#5405]) #### Persistence @@ -499,6 +788,65 @@ This section accumulates work toward the **2.1.0** milestone range loader — literal strings, leading zeros, and multiline quoted cells survive, long cells open in a copyable dialog, and source view remains one toggle away. ([#5284]) +- **title:** Attachment-only first turns get a real title instead of the + generic `New Conversation`, so file-first conversations are distinguishable + and searchable. When the first turn carries one validated attachment, the + cleaned filename becomes the local title; several attachments produce a count + such as `2 files uploaded`; user-authored text still wins as the title + source, and `New Conversation` remains the fallback when no valid filename is + available. The local title is returned before the configured title-model + path, so an attachment-only turn no longer pays for an LLM call. Filenames + are read only from the `uploaded_files` state the upload middleware populated + — client metadata is not trusted — and are sanitized: control characters and + excess whitespace stripped, ordinary Unicode and percent signs preserved, + long names truncated while retaining the extension. This supersedes the `New + Conversation` fallback the earlier `` title fix deliberately + kept. ([#5304]) +- **projects:** Projects MVP Phase 2 makes a project do its job rather than + just name a folder. Project instructions — previously stored, editable, and + read by nothing — now reach the model on every member-thread run as a + bounded, user-role `` block in that run's request only, never the + system prompt or persisted history, with renames taking effect on the next + run and oversized text rejected at write time rather than truncated. A + per-project document shelf gains a Documents tab (upload, list with + provenance, preview, download, move-to-trash, content-hash dedup) that the + agent reads on demand through `list_project_documents` / + `read_project_document` — registered only in project runs, text only — plus + promotion in both directions: *Save to project* copies any thread file onto + the shelf with provenance, and *Attach to thread* ingests a shelf document + back into a conversation through the normal upload pipeline. A read-only + conversation-files view aggregates member threads' uploads and outputs, and + project deletion finally has a trash tier. ([#5443]) +- **frontend:** Tool details for generic and MCP calls are inspectable in debug + mode. Those steps previously showed only a label even though the browser + already receives their inputs and results, making it hard to see what was + passed between tools. Token Usage → Debug now carries a collapsed Tool + details panel — including for calls with no token statistics — showing tool + name, call ID, input, and either the original result or an explicit error, + with copy actions and English/Chinese labels. Content is formatted only when + expanded, and text length, nesting, and visited values are bounded so a large + payload cannot stall the panel. ([#5309]) +- **frontend:** Conversations can be referenced from the composer. A "Reference + a conversation" button sits beside the attachment button, rendered only while + `/api/features` reports the capability, and opens a picker over the same + recent-conversation list the sidebar uses — never offering the current + conversation, and capped at the reported `max_references` (3 today), where + rows past the cap are disabled and selected ones stay clickable to remove. + Attached conversations appear as removable chips in the composer and as + read-only chips linking back to the source in the transcript. References are + per message: not saved with a draft, cleared on send or thread change, and + regenerating or editing a turn runs without them unless re-attached. + ([#5465]) +- **frontend:** Capability management moves out of Settings into a dedicated + Capability Center in the workspace sidebar. MCP servers, connected-app + authorization, and skills sat alongside account and appearance preferences, + which made them hard to find and made the settings dialog hard to scan; they + now live on one page, with Plugins combining MCP management and Lark/Feishu + install and authorization, and Skills providing Built-in, Community, My + skills, and All skills views with searchable cards, enable switches, file + import, creation, and custom-skill export. Settings keeps its seven remaining + sections, API contracts and permissions are unchanged, and the old + `?settings=tools|integrations|skills` entry points are removed. ([#5468]) #### Observability & tooling @@ -523,6 +871,17 @@ This section accumulates work toward the **2.1.0** milestone attribution; no queries, schemas, or results), observed only after skill-policy filtering so denied schemas are never reported as effective promotions. ([#5183]) +- **client:** Context compaction stores its result in + `ThreadState.summary_text`, outside `messages`, but the embedded client's + `values` event selected only the title, messages and artifacts — so a + consumer outside the Gateway, such as a benchmark runner, had no way to + observe the summary through the public event stream. Every embedded `values` + event now carries `summary_text`, `None` when absent, forwarding updates, + repeats and clears as state snapshots. Message serialization, AI-delta + deduplication, tool artifacts and usage accounting are unchanged, and no + checkpoint internals are exposed. An initial snapshot can already contain a + restored summary, so a changed value alone is not a causal compaction event. + ([#5249]) ### Changed @@ -587,11 +946,12 @@ This section accumulates work toward the **2.1.0** milestone read shorter than a bucket collapsed onto its neighbours: five sequential 40-line reads hashed identically and tripped the hard stop, ending the run with a forced final answer and `stop_reason=loop_capped` — on exactly the - ranged reads `read_file`'s own truncation notice tells the model to make. - The key now uses the exact line window, with an omitted `end_line` kept + ranged reads `read_file`'s own truncation notice tells the model to make. The + key now uses the exact line window, with an omitted `end_line` kept open-ended so a bare read and an explicit `start_line=1` still share one key. Repeating a single range is still caught at the same threshold, and a read loop that varies its bounds remains covered by the per-tool frequency layer. + ([#5486]) - **subagents:** Give `max_turns` the meaning operators read it as. It was handed to LangGraph as `recursion_limit`, which counts super-steps — one per graph node — while `create_agent` compiles a node for every middleware @@ -603,7 +963,7 @@ This section accumulates work toward the **2.1.0** milestone assembled, so raising `max_turns` buys the turns it names. No config keys changed; existing `max_turns` values now grant their full budget, which can make a previously truncated subagent run longer, bounded as before by - `subagents.timeout_seconds` and `subagents.token_budget`. + `subagents.timeout_seconds` and `subagents.token_budget`. ([#5485]) - **scheduler:** Enforce the global `max_concurrent_runs` budget on SQLite, which previously only held on Postgres. Claiming a queued occurrence counts the executing rows and then promotes one row to `launching`, and Postgres @@ -621,7 +981,7 @@ This section accumulates work toward the **2.1.0** milestone branch, which has always decided from the full list. This concerns the filtered-match cap only: the raw-output cap `parse_remote_search_output` owns is a separate limit with its own one-line-past accounting, and the other - providers' filtered-match cap is unchanged. + providers' filtered-match cap is unchanged. ([#5449]) - **middleware:** Stop a guard that removes tool calls from breaking every later turn of a Claude or OpenAI Responses thread. Token-budget and loop-detection hard stops, subagent-limit truncation, and safety suppression cleared @@ -933,6 +1293,17 @@ This section accumulates work toward the **2.1.0** milestone ([#3563]) - **skills:** Don't treat a lazily evaluated PEP 695 type alias as a network sink in SkillScan. ([#4315]) +- **skills:** Bound skill-archive extraction by member count, not only by + uncompressed size. `safe_extract_skill_archive()` is the always-on path every + `.skill` install goes through; it capped total uncompressed bytes as a + zip-bomb-by-size defence but had no member limit, so a small archive holding + tens of thousands of tiny entries extracted cleanly. + `scan_archive_preflight()` already capped entries at 4096, but only ran when + the optional `skill_scan.enabled` kill switch was on, leaving the default path + uncapped. + Extraction now enforces the same 4096-member cap unconditionally, raising a + plain `ValueError`; where SkillScan is enabled the structured + `package-too-many-members` finding still surfaces first. ([#4241]) - **tracing:** Resolve the Langfuse trace user from runtime context. ([#3794]) - **guardrails:** Propagate internal owner attribution into the guardrail context. ([#3839]) @@ -1153,6 +1524,743 @@ This section accumulates work toward the **2.1.0** milestone `extra_token_params` during OAuth token exchange, so extra parameters can no longer silently switch the configured flow and be rejected by the token endpoint. ([#4860]) +- **runtime:** A slow or hung stdio MCP server no longer stalls the whole + Gateway. Agent construction assembled MCP tools synchronously on the event + loop, so a wait for an in-flight MCP initialization blocked SSE delivery, run + cancellation, and timers for every other request, not just the caller waiting + for its tools. Tool assembly is now dispatched to a worker thread at each + async entry point: the subagent spawn path in `task_tool`, + `SubagentBatchService._execute_item` for durable batches, `run_agent`'s agent + factory (which covers both `get_available_tools` call sites in lead-agent + assembly), and the checkpoint state-accessor build, whose cold-cache reads + pay the MCP-init wait in the worker thread instead of stopping the loop. The + four offloads ride a dedicated bounded pool (`run_assembly()` in + `utils/assembly_io.py`, 8 workers, overridable with + `DEER_FLOW_ASSEMBLY_WORKERS`) rather than the loop's default executor, so a + worker parked for the full MCP timeout cannot queue every other `to_thread` + caller behind it; contextvars are copied across the hop so the extension + build-context snapshot still propagates, and pending assemblies are logged + with a throttled warning once they exceed the worker count. Because assembly + can now be parked, a durable batch re-checks its item's durable state + immediately before launching, so a batch cancelled during assembly no longer + starts a model call. ([#5217], [#5224]) +- **agents:** `LoopDetectionMiddleware` no longer spends one turn's budget on + another turn's legal work. It scoped its identical-call hashes and per-tool + frequency windows only by `thread_id`, so a compiled agent reused across + turns — `DeerFlowClient` retains the graph and assigns a fresh `run_id` per + turn — counted earlier turns' ordinary calls toward the later turn's limit, + producing a false loop warning on the third separate turn and stripping a + legitimate tool call on the fifth at the default identical-call thresholds. + State is now scoped by `(thread_id, run_id)`, with evidence still accumulated + across repeated graph entries inside one run, including hidden goal + continuations, and independent warning queues for concurrent sibling runs. + LRU eviction drops a whole run scope, `reset(thread_id)` still clears every + scope that thread owns, and thresholds and warning/hard-stop behavior are + unchanged. ([#5344]) +- **agents:** A run's `token_budget.max_tokens` cap now holds across the hidden + continuations of an active `/goal`. The worker re-enters the graph under the + same `run_id`, and the middleware cleared that run's usage in `after_agent` + and then marked every existing message as already seen, so each continuation + started counting from zero: with `max_tokens: 10000`, a turn that hit the + hard stop at 12k was followed by a continuation that kept calling tools, and + the run reported `stop_reason: token_capped` while spending 20k tokens and + running two more tool calls after the cap. Usage and warning state now + survive graph entries with the same `run_id` — as loop detection already did + — while a later user run still gets a fresh `run_id` and a fresh budget. + After a cap, a continuation still makes one model call before its tool calls + are stripped. ([#5410]) +- **middleware:** A Human Input Card reply is now recognized as the user's + current request. The card answer arrives as a hidden `HumanMessage` carrying + a valid `human_input_response`, and the turn-detection helpers used + `is_real_user_message`, which rejects every hidden message with no carve-out + — so a run started from a card reply kept the older visible request as + "current" while the answer itself was treated as a framework injection. In + summarization, the current-request rescue therefore locked onto the stale + message: after compaction the model saw the original request word for word + and the user's region and year constraints only inside the summary. In + `McpRoutingMiddleware`, a routing keyword that appeared only in the card + answer matched nothing, so the deferred MCP tool was never auto-promoted and + the model had to call `tool_search` by hand — the identical answer sent as a + visible message promoted it, so behavior depended only on how the answer was + transported. Both now use `is_genuine_user_message`, which skips hidden + messages unless they carry a valid `human_input_response`; hidden messages + without one still stay skipped. ([#5416], [#5426]) +- **goal:** A goal that a token-capped run already satisfied is no longer + followed by a pointless continuation. Now that continuations share the run's + token budget, a continuation queued after the hard stop made one model call + and then had its tool calls stripped, so it could not make progress — yet the + worker still queued it, paying for an evaluator call and a model call each + time until the continuation or no-progress limit stopped the goal. + `run_agent` now passes the run's `stop_reason` into goal-continuation + preparation, and when it is `token_capped` the goal stands down with + `stand_down_reason: "token_capped"` instead of queueing another continuation. + The evaluator still runs first, so a goal the capped run did satisfy is still + cleared, and other stop reasons behave as before. ([#5424]) +- **agents:** A retried model call no longer reaches the provider without the + warning a guard middleware had already queued for it. + `LoopDetectionMiddleware`, `TokenBudgetMiddleware`, and + `ToolProgressMiddleware` drain their queued warning or hint inside + `wrap_model_call` before calling the handler; because + `LLMErrorHandlingMiddleware` wraps them and retries by calling its handler + again, the second attempt ran with an empty queue while the warning had + already been marked sent. A model that looped on one tool call and failed + once with a 503 on the request carrying the warning never saw it and ran on + to the forced stop. All three now put the drained warnings back at the front + of the queue when the handler raises, so the retry picks them up; successful + calls are unchanged, per-run caps still apply, and warnings are still dropped + at `after_agent` if the run ends without another model call. ([#5433]) +- **agents:** The token budget works again for runs that have no `run_id` — + LangGraph Server, `langgraph dev`, and direct `create_deerflow_agent` + callers. Two defects rode the runtime-local fallback key. A subagent's hard + stop was stored under the id string but read back with + `consume_stop_reason(None)`, because `SubagentExecutor` propagates the + parent's `run_id` and the parent has none, so a token-capped subagent was + reported to the parent as a clean `Task Succeeded` instead of a capped + failure. And LangGraph gives each node its own `Runtime` wrapper, so + `id(runtime)` differed between `after_model`, the next `wrap_model_call`, and + `before_agent`: the queued budget warning was never delivered, `after_model` + missed the `before_agent` baseline and counted every `AIMessage` in the + thread, and a second invocation on the same thread was charged with the first + invocation's tokens. Invocations without a non-empty string context `run_id` + are now keyed by LangGraph's run-scoped `Runtime.control` object, the same + anchor loop detection uses, and the stop reason is stored under the context + `run_id` exactly as given, `None` included. ([#5436]) +- **agents:** Cancelling a `read_file` or `write_file` call can no longer + strand or release the read-before-write gate's lock. `asyncio.to_thread()` + cancellation only cancels the asyncio waiter, not an already-running worker, + so a cancellation could leave a queued `threading.Lock.acquire()` hanging or + free the gate while an off-thread probe was still running. Dispatched gate + operations are now drained under `asyncio.shield()` before the cancellation + propagates, the first `CancelledError` is preserved and re-raised across + repeated cancellations (including when the drained worker task is itself + cancelled), a lock that succeeds after cancellation is released exactly once, + and the same-path gate is held until the write-check and read-mark work + finishes. ([#5395]) +- **agents:** Three `create_deerflow_agent` features now do what they + advertise. Factory graphs were built without `DurableContextMiddleware`, + which is what writes the `delegations` ledger — so `SubagentLimitMiddleware` + always counted zero earlier delegations and only the per-response limit + applied — and which is the only thing that puts `summary_text` back into a + model request, so after the first compaction the model saw only the kept tail + and the summary was lost. Separately, `RuntimeFeatures(token_budget=True)` + built a `TokenBudgetConfig()` whose `enabled` defaults to `false`, so every + hook returned early and there was no warning, no hard stop, and no + `token_capped`. `_assemble_from_features` now always adds + `DurableContextMiddleware` in the same position as the lead-agent chain, + factory model requests carry the hidden durable-context block when the graph + has a summary, delegations, or loaded skill files, and `token_budget=True` + builds `TokenBudgetConfig(enabled=True)`. ([#5488]) +- **subagents:** Subagent acceptance checks no longer let an out-of-scope + command count as evidence on Windows. `_cd_target_in_scope()` normalized bash + `cd` targets with the host platform's path module, so on Windows an absolute + POSIX path or a `..` escape could be judged a safe relative target and a test + run outside the checked scope could satisfy an acceptance criterion. Bash + targets and configured roots are now normalized with POSIX semantics on every + host, drive-qualified Windows paths are recognized and compared + case-insensitively, and an unmatched drive or directory fails closed. + ([#5162]) +- **sandbox:** Concurrent subagents stopped working once their count passed the + AIO image's shell-session ceiling. Each concurrent subagent gets its own + persistent scoped shell, but the AIO image caps `MAX_SHELL_SESSIONS` at 10, + so the eleventh shell evicted the oldest idle session while DeerFlow still + held its scoped id — the subagent's next command then failed with `404 + Session not found`, and recreating sessions without raising the capacity only + evicted another subagent and lost its shell state. New local containers now + receive `subagent_runtime.max_running + 1` (the extra slot leaves room for + the lead shell) as `MAX_SHELL_SESSIONS`, provisioner mode forwards the same + value to the sandbox Pod, and an explicit + `sandbox.environment.MAX_SHELL_SESSIONS` set below the required capacity now + fails at provider startup naming both values. When a scoped session is lost + anyway — to a timeout or external cleanup — DeerFlow recreates it once, and + only for the structured `404 Session not found` response. ([#5178]) +- **view-image:** `view_image` no longer serves a stale or missing picture for + a remote sandbox image. It resolved `/mnt/user-data/...` to a Gateway host + path, so bytes only became available after synchronization and an older host + copy could stand in for the current one. Bytes are now read from the current + live sandbox when one is available, without acquiring a replacement merely + because a persisted sandbox ID exists, and lightweight provenance — the exact + SHA-256 plus the source sandbox ID — is recorded in `viewed_images` instead + of checkpointing image bytes or base64. A replacement sandbox that reports + the image missing falls back to the synchronized host copy only when it + matches both the recorded size and SHA-256, so same-size stale content is + rejected; missing-file classification is provider-neutral over an explicit + `__cause__` chain and deliberately ignores implicit exception context. Async + tool invocation and model injection now run the blocking sandbox read through + `run_sync_lifecycle_operation()`, so a cancellation cannot tear down sandbox + lease cleanup before the read has drained. ([#5306]) +- **subagents:** A subagent keeps its instructions after its context is + compacted. The executor builds the agent with `system_prompt=None` and puts + the assembled prompt into state as the first message, so it holds the role + prompt, the `` citation rules, the acceptance-criteria note, + the skills index, and the deferred MCP tools and routing hints — and + compaction cuts by index, so index 0 always fell in the summarized part. + Every model call after the first compaction ran with no system prompt at all, + and the summary did not stand in for it because it is injected as hidden data + the authority contract tells the model not to follow. Dynamic-context + preservation now rescues `SystemMessage`s as well as tagged reminders and the + latest user message, keeping their order so the prompt stays first and is not + sent to the summarizer; when the prompt plus the current request are all that + remains, compaction is skipped instead of summarizing the prompt away. The + lead agent is unaffected — its prompt is the request's system message, not a + message in state. ([#5454]) +- **subagents:** A repeatedly cancelled subagent no longer leaks its execution + slot permanently. `SubagentExecutionCapacity.slot()` increments the + process-wide running count before yielding and releases it in the async + context manager's `finally`, but a second `Task.cancel()` arriving while that + release was blocked acquiring the capacity lock interrupted the cleanup: the + task exited cancelled with `_running` still incremented. With + `max_running=1`, every later native subagent then queued until timeout or was + rejected even though nothing was running, violating the documented invariant + that cancellation and timeout release queue and slot ownership. The final + release now runs in its own task, is shielded from caller cancellation, and + is drained across repeated cancellation before the cancellation propagates. + ([#5477]) +- **worker:** A delegated subagent's error no longer fails the parent run. When + a subagent's model call ended in an error after its retries, the executor + reported `task_failed` and the lead agent still answered, but the worker also + saw the `deerflow_error_fallback` marker inside the root-level `task_running` + custom event — each carries a subagent message with `additional_kwargs` — and + marked the parent run `error` with the subagent's error text. Goal + continuation stopped, and an edit-and-rerun rolled the thread back, + discarding the edited question and the new answer. Custom frames no longer + feed parent error-fallback detection, while the lead's own error fallback + still arrives through `values`, `messages`, and `updates` frames; the parent + run now ends `success` when the lead finishes. ([#5407]) +- **sandbox:** Normalize separators in masked output tails so virtual paths are + spelled POSIX-style on Windows hosts too. Both output maskers searched for + the host base in a forward-slash-normalized copy of the output but sliced the + matched tail out of the original text, so nested backslashes survived into + the spliced result and `glob` results and masked skill reads came back as + mixed spellings such as `/mnt/user-data/workspace/pkg\util.py`. Depth-1 tails + happened to splice cleanly, which is why Linux CI never caught it; the two + splice sites now normalize the tail before joining it to the virtual prefix. + ([#5247]) +- **sandbox:** Reverse-resolve forward-slash spellings of Windows host paths. + Forward resolution spells resolved paths with `/`, since backslashes would + break bash escape sequences like `\U`, but the reverse scanner that maps host + paths in output back to their `/mnt/...` form still anchored its matches on + the native backslash base — so every forward-resolved path that came back in + command output or in an agent-written file failed to match and leaked the raw + host path, real username and full directory tree, to the model instead of the + container path the agent is meant to reference. The scanner now matches + separator-agnostically, the same contract the sandbox tools already use; + POSIX hosts, where the two spellings coincide, are unaffected. ([#5373]) +- **sandbox:** Stop remote `list_dir` from reporting failures as an empty + directory. `find ... 2>/dev/null` on a missing path produced empty stdout and + client errors were swallowed as `[]`, so `ls` told the agent the directory + was `(empty)` — a dead sandbox, a closed client, and a nonexistent path all + looked like a writable empty tree, letting the agent write over existing + files or skip recovery. AIO, E2B, BoxLite, OpenSandbox, and Tenki now raise + `OSError` on command or client failure and `FileNotFoundError` when nothing + is listed, matching what the local sandbox already did for a path that is not + a directory; a genuinely empty directory still reports `(empty)`, and `find + -H` keeps a symlinked root listable. ([#5264]) +- **sandbox:** Reject a partial remote `list_dir` traversal instead of + presenting it as complete. `find` exits `1` both for a missing start path and + for a file or subdirectory it could not read after printing some entries, and + the parser accepted `1` in both cases — so a traversal that failed partway + returned the visible entries as a successful complete listing and `ls` handed + the agent a silently incomplete tree. Status `1` with no entries still raises + `FileNotFoundError`, while status `1` with any entries now raises `OSError` + naming the incomplete traversal and suggesting a narrower path, the same + contract remote `glob` already applied. Every provider sharing the remote + `list_dir` helper inherits it without provider-specific changes. ([#5422]) +- **sandbox:** Stop remote `grep` and `glob` from reporting failures as "no + matches". The searches ran with stderr discarded and took the pipeline status + from `head`, so a missing search root, a missing `grep`/`find` binary, or an + unreadable tree printed nothing and exited 0 — and the tools told the agent + "No matches found" where the local sandbox reports `Error: Directory not + found`. A shared wrapper now records the search command's own status: a + missing root raises `FileNotFoundError`, and any other failure — including + `grep` 2 or `find` 1 after some results were already printed, since a caller + cannot tell a partial result from a complete one — raises `OSError` telling + the agent that some paths could not be read. A genuine no-match still returns + `[]`. E2B, OpenSandbox, BoxLite, and Tenki also raise on a closed client + instead of returning empty. ([#5380]) +- **sandbox:** Report `read_file` truncation in lines and name the line to + resume from. The tool head-truncates at `sandbox.read_file_output_max_chars` + (default 50,000) and its marker told the model to continue with + `start_line`/`end_line`, but the cut was made at a character offset and the + marker reported only character counts — so it almost always landed mid-line + and the last line the model saw was a fragment that read as complete, with + nothing saying which line to continue from. Asked for the next read of a + truncated file, three models picked the right `start_line` in 0 of 15 + attempts, landing tens or hundreds of lines off. The cut now lands on the + last line boundary the budget allows, and the marker states the position in + lines and names the exact continuation, as in `[truncated: showing first 743 + of 1828 lines (49746 of 155704 chars). Continue with start_line=744]`; a + ranged read reports file line numbers rather than the provider's + slice-relative ones. A single line longer than the budget still cuts at the + character limit, since dropping it would discard most of the budget, and the + marker then points at `bash` rather than at a `read_file` call that cannot + return it. ([#5474], [#5478]) +- **sandbox:** Force a UTF-8 console in PowerShell so CJK tool output is no + longer garbled on Windows. The command runner captures stdout through a UTF-8 + pipe reader, but Windows PowerShell 5.1 writes console output in the legacy + OEM codepage — GBK on zh-CN Windows — unless explicitly switched, so every + CJK character arrived as mojibake, and because the reader replaces + undecodable bytes rather than raising, the corruption was silent. Every + PowerShell `-Command` payload now sets `InputEncoding`, `OutputEncoding`, and + `$OutputEncoding` to UTF-8 before the user command runs. ([#5440]) +- **sandbox:** Validate the managed Lark CLI sandbox runtime in a + platform-aware way on Windows hosts. The check required a POSIX executable + bit, which NTFS does not preserve, so every candidate reported `st_mode & + 0o111 == 0` and the Gateway raised `ValueError: Managed Lark CLI sandbox + runtime file is not executable` — leaving the managed Lark runtime unusable + on a Windows development host, and on a Windows Docker host that mounts the + managed Linux CLI runtime into the sandbox. POSIX keeps the strict + executable-bit contract unchanged; on Windows the Linux-only artifacts are + validated by content instead, requiring executable image magic on the + per-arch binaries and a shebang on the launcher. ([#5442]) +- **sandbox:** Default the Docker-outside-of-Docker sandbox port bind to + loopback on Docker Desktop. `DEER_FLOW_SANDBOX_HOST` defaults to + `host.docker.internal` in DooD mode, which resolves inside containers to the + Docker Desktop VM gateway, and publishing the sandbox port to that address + made the host socket layer reject the bind with `WSAEADDRNOTAVAIL` — so the + first sandbox shell action failed with `ports are not available` even though + startup had succeeded. When the Docker server is Docker Desktop and no + `DEER_FLOW_SANDBOX_BIND_HOST` override is configured, the bind host is now + `127.0.0.1`, which Docker Desktop forwards; an explicit override still wins, + and native Linux DooD is unaffected. ([#5446]) +- **sandbox:** Drain the skill-sync worker before a cancellation is treated as + failure cleanup. The default `sync_agent_skills_async()` wrapper used a bare + `asyncio.to_thread()`, so cancelling the awaiting task returned before the + synchronous sandbox mutation had finished, and `SandboxMiddleware` then + released the execution holder while provider work was still in flight — + breaking the lifecycle contract that blocking work started on behalf of a + holder must drain before that holder leaves the boundary. The wrapper now + runs through the existing cancellation fence, so the ordering holds for + third-party and upload-based providers that do not serialize sync and release + themselves. ([#5350]) +- **frontend:** Keep a human-input card with the turn that requested it. An + existing `needYourHelp` card could drift below the next user message during a + multi-turn conversation, so the chronology became misleading and an answered + request could read as part of the new turn. A pre-submit baseline message + could be woven in after the newly persisted human message, a card confirmed + only through REST history might never enter the checkpoint baseline, and + steps already persisted for the current run could be lifted into the previous + turn after an interrupt. Baseline messages are restored before the pending + human message, REST-confirmed cards count as established history, and the + current run's own steps stay below the message that started them. ([#4892]) +- **frontend:** Preserve server-assigned message positions through a live + content merge. Streaming updates that replaced a message's content also + dropped its `deerflow_seq`, so ordering the server had already settled was + recomputed client-side and came out wrong — a loaded history window of + `1,3,5` merged with a live tail of `2,5` rendered as `1,3,2,5`, and long + threads (history pagination, or a conversation resumed after compaction) + showed their steps out of order until a refresh. Content and position are now + handled separately, the merge, compaction bridge, and render ledger share one + position priority so an already-sorted upstream is not re-sorted downstream, + and anomalous sequence values (null, string, NaN, non-integer, + non-safe-integer) never overwrite a known position. ([#5293]) +- **frontend:** Restore the user's input after an incremental stream reconnect. + Refreshing during a later turn could let the reconnect stream replay AI and + tool chunks before the current human input had entered the durable history + feed, so the user's message briefly vanished and its reasoning steps were + grouped under the previous turn. The active run's input is now hydrated from + `kwargs.input` and merged with the latest durable thread state before the + incremental stream is joined, deduplicated by id; if the metadata or state + read is unavailable, the previous reconnect path is used unchanged. ([#5428]) +- **frontend:** Show an agent's skill badge even when its tool groups are + explicitly empty. The badge container's nullish-coalescing chain stopped at + the tool-group count `0`, so an agent declared as `tool_groups: []` with + `skills: ["data-analysis"]` lost its badge in the Agents gallery — even + though an explicit empty tool-group list is valid and supported by the Agent + API and the `update_agent` tool. The container now shows when either list has + at least one entry. ([#5326]) +- **frontend:** Scope composer slash-skill suggestions to the active agent. + Custom-agent chats can restrict which skills they may activate, including to + an explicit empty list, but the composer still offered the global + enabled-skill catalog — so an agent could suggest a skill it was not + permitted to activate. Suggestions now pass through the active agent's + allowlist, with an explicit empty list treated as no skills available rather + than briefly leaking the global catalog while the agent loads, and the same + scoping applied when a saved composer draft with a selected skill chip is + restored. ([#5451]) +- **frontend:** Confirm before a sidebar chat is deleted. Choosing Delete in a + recent chat's menu removed the conversation and its files immediately, with + no confirmation step despite deletion being irreversible. A dialog now names + the conversation and warns that deletion is irreversible, focuses Cancel, and + leaves the chat intact on Cancel, Escape, or close; actions are disabled and + dismissal blocked while deletion is pending, and a failed deletion keeps the + dialog open with the underlying error so it can be retried by keyboard. The + dialog is hosted outside the virtualized sidebar rows, so a list refresh + cannot remove the retry UI after a partial deletion. ([#5406]) +- **agents:** Keep the Custom Agent settings dialog within the viewport. + Choosing **Selected subagents** could make the dialog taller than the window, + pushing the title, close control, and Save/Cancel buttons off-screen while a + long worker description filled the nested list. The header and action buttons + now sit outside a single scrolling form area, subagent descriptions show a + two-line preview with keyboard-accessible expansion that scrolls into view + without moving focus or changing checkbox state, and long names and + descriptions wrap without shrinking the checkboxes. ([#5458]) +- **frontend:** Keep the MCP configuration dialog within the viewport. Opening + a long MCP server definition in the Capability Center let the JSON textarea + grow past the window, so the centered dialog clipped its title, close + control, and Save/Cancel buttons and became hard to edit or dismiss on + smaller windows. The dialog is now viewport-contained with more horizontal + room on desktop, long JSON scrolls inside the editor, and the title, Close, + Save, and Cancel stay outside the scrolling region even at very small + viewports or with a long server name or validation message. ([#5492]) +- **frontend:** Show a pointer cursor over interactive controls that were + clickable while still displaying the default cursor, making their affordance + unclear. One global selector now covers native buttons, `role="button"` + elements, dropdown menu items, and command items, excluding native disabled + controls and anything marked `aria-disabled="true"`; generated `ui/` and + `ai-elements/` components are untouched. ([#4921]) +- **frontend:** Localize Chinese documentation links. MDX links and Nextra + cards in the localized docs were rewritten without knowing the active docs + language, so a Chinese reader following a quick-start link was sent to the + English page. The localized docs layout now provides the language through + context and link rewriting honors it. ([#5275]) +- **runtime:** Memory-only deployments keep their run history through scheduled + cleanup. `cleanup()` evicted unconditionally, so an embedded consumer that + built `RunManager()` with the documented default `store=None` lost completed + runs from history when cleanup fired — `get(run_id)` returned `None` and + `list_by_thread()` dropped the record, because there is no durable copy for + those reads to fall back to. Eviction is now gated on a backing store, + keeping the retain-forever behavior memory-only mode had before; store-backed + managers still evict after the grace period. ([#5453]) +- **events:** Read JSONL event records that contain U+0085, U+2028, or U+2029. + All three readers split on those Unicode separators as if they were record + boundaries, so a record carrying one was skipped on read — which reused event + sequence numbers after a reopen and turned idempotent inserts into + duplicates. Thread reads, run reads, and sequence recovery now split on + physical line feeds; existing valid files need no rewrite. ([#5429]) +- **events:** Keep the per-thread JSONL write lock until filesystem work + settles. Cancelling a store call during its worker release dropped the lock + while the append was still in flight, so a later deletion could complete + before the cancelled append recreated the record, and a cancelled mixed-run + batch could roll back over a write it had already acknowledged. Admitted + mutations now retain their lock through file I/O, rollback, and bookkeeping + before propagating the cancellation, covering `put`, `put_if_absent`, batched + writes, and both deletion methods; queued callers can still cancel before + admission, and unrelated threads stay independent. ([#5439]) +- **events:** Stop JSONL thread mutations from splitting onto two lock + generations across a deletion. `delete_by_thread()` removed the per-thread + lock from its registry while still holding it, so a mutation already queued + on the old lock could run concurrently with a later mutation that resolved a + freshly created lock for the same thread — overlapping sequence assignment + and file mutation right after a delete boundary. The registry is now a + `WeakValueDictionary` and the entry is no longer popped, so holders and + queued waiters keep the same generation alive until they drain, after which + the entry disappears on its own. ([#5455]) +- **channels:** Keep one lock generation for a channel's first-use thread + creation. The cleanup removed the per-conversation lock unconditionally when + the current creator exited, so a creator that failed or was cancelled + released and unregistered its lock while a queued creator entered through it, + and a late arrival could install a new lock and enter concurrently — two + callers creating Gateway threads for the same conversation, with the later + mapping write winning and adjacent messages split across the orphaned thread. + The hand-managed lifecycle is replaced by the existing waiter-aware keyed + lock table, so a conversation key keeps one discoverable generation through + exceptions and cancellation and the idle key is reclaimed only after the last + participant leaves. ([#5480]) +- **gateway:** Read a thread's runs as the data owner, not as the authorization + identity. The list, keyset-page, and single-run read endpoints filtered run + rows by the caller's authorization identity, while `start_run` stamps them + with the data identity — the two only coincide for already-safe owner values, + so trusted internal callers always saw an empty runs list and 404s even on + threads they were authorized to act on, and the symptom was easily misread as + run loss. Internal callers now skip the per-user store filter, which thread + visibility on these endpoints already authorizes, while browser and API + sessions keep their exact per-user filter; the two message-read endpoints had + the same conflation and are corrected the same way. ([#5448]) +- **gateway:** Apply the same data-identity scoping to the message edit and + regenerate helper paths. Those three helpers still resolved the authorization + identity and passed it as the data filter, so `regenerate/prepare` and + `edit-regenerate/prepare` failed with 409 for internal callers on threads + they were authorized on. They now resolve their filter id the same way the + read endpoints do, and browser and API sessions keep the prior per-user + filter and the 409 on cross-user runs. ([#5483]) +- **conversation:** Keep conversation-reader pages inline and say when text was + dropped. A page was filled to its 20,000-character cap by cutting the last + message that did not fit, and a cut suffix could never be paged back — the + oldest of six 3,500-character messages came back as about 2,500 characters + with `has_more: false`. Worse, a page over the default 12,000-character + tool-output budget was externalized by `ToolOutputBudgetMiddleware`, so the + model saw only a synopsis plus a file copy of the source text in the + destination thread. Pages are now sized by the budget that actually applies + to `read_conversation` (per-tool override, else `externalize_min_chars`, and + `fallback_max_chars`), still capped at 20,000 text characters, and a message + that does not fit starts the next page intact — so one operator setting stays + in charge, and `tool_output.tool_overrides.read_conversation` tunes this tool + alone. Truncated results now carry a notice asking the agent to acknowledge + the omission and ask for the missing material before claiming full coverage. + ([#5421]) +- **goal:** Stop a goal loop while a clarification card is still open. The goal + evaluator only reads human and AI messages, so a question asked through + `ask_clarification` — which arrives as a tool result — was invisible to it; + it judged the goal unmet, and the worker queued a hidden continuation telling + the agent to keep working and not to ask the user unless genuinely blocked. + The agent then acted on its own guess, including on a `risk_confirmation` + question, while the card sat waiting in the UI. A trailing human-input + request is now detected before the evaluator runs and stands the goal down + with the existing reason `blocked:needs_user_input`; the user's answer is a + new human message, so the next run evaluates normally, and a card with no + assistant text before it reports why it stopped instead of `run_failed`. + ([#5467]) +- **client:** Emit text a later node appends to an AI message already sent. + `LoopDetectionMiddleware`, `TokenBudgetMiddleware`, + `SafetyFinishReasonMiddleware`, `SubagentLimitMiddleware`, and + `TerminalResponseMiddleware` each rewrite the last AI message under the same + id from their own node, but the stream marks an id seen once and then skips + it, so the rewrite never reached `messages-tuple` consumers: `chat()` and + headless `--print` returned the text from before the rewrite instead of the + terminal error or the forced-stop notice, the TUI never showed the stop or + safety notice, and `token_usage_attribution` added after the model node never + arrived. The values path now remembers the message object last seen per id + and re-examines it when a snapshot holds a different one, emitting only the + added text as one more delta and routing new `additional_kwargs` through the + existing metadata-only follow-up; unchanged messages are still skipped + without re-extracting text. A replacement that does not extend the sent text + is not re-emitted. ([#5479]) +- **mcp:** Hold the MCP session pool to its capacity limit under concurrent + initialization. `MCPSessionPool` checked LRU capacity only before session + creation started, and because initialization awaits, several distinct keys + could each observe spare capacity, enter `_inflight`, and then promote into + `_entries` with no second check — leaving the persistent-session registry + above `MAX_SESSIONS` and retaining the extra subprocesses and connections it + implied. Capacity is now rechecked and enforced atomically when a session is + promoted, and promotion-time eviction victims are closed outside the registry + lock through their owner-task lifecycle. ([#4962]) +- **mcp:** Stop parallel synchronous MCP calls from cancelling each other's + connection. When an embedded client dispatched two synchronous calls to the + same stdio server, each wrapper ran on its own event loop and the pool + treated the sibling loop's live session and in-flight creation as stale — + cancelling or closing them and aborting a tool step with `CancelledError` + instead of returning both results. Established sessions and in-flight + creation are now keyed by `(server_name, scope_key, owning_loop)`, so + same-loop callers still share creation and state while separate loops get + their own, and each owner retires only its own records, including after a + normal `asyncio.run()` shutdown. Synchronous calls still use independent + subprocess sessions and gain no shared server-side state. ([#5396]) +- **web-fetch:** Resolve relative destinations in extracted Markdown against + the page they came from. Jina, Browserless, and InfoQuest returned links + exactly as written in the HTML, so a page at + `https://example.com/docs/current` yielded `[Next](../next)` and left the + agent with no URL it could follow up on. The three providers now pass the + requested URL to the shared extractor, which resolves anchor and image + destinations against that URL or against the first usable document ``; + only destination attribute values are rewritten rather than serializing a + different parse tree, so malformed markup, comments, scripts, and attribute + formatting survive untouched and still reach Readability.js unchanged. Fetch + validation, the 4096-character output limit, and off-thread extraction are + unchanged, and the Python fallback keeps its text-only behavior. ([#5310]) +- **community:** The Firecrawl `web_fetch` and `web_search` tools honor the + tool's `base_url`. `_get_firecrawl_client` read only `api_key`, so + `FirecrawlApp` always fell back to `https://api.firecrawl.dev` — and because + the SDK raises `Error: No API key provided` whenever the target is the cloud + API and no key is set, a self-hosted Firecrawl was unreachable no matter how + the config was written. `base_url` is now forwarded as `api_url`, letting a + local Firecrawl serve both tools with no cloud key, and configurations + without `base_url` construct the client exactly as before. ([#5392]) +- **community:** Tavily extraction reads its credentials from the `web_fetch` + entry instead of the search configuration. With `web_search` on Serper and + `web_fetch` on Tavily, fetching built its client from the Serper key and + ignored a separate Tavily fetch key entirely. Each tool now reads `api_key` + from its own entry and falls back to the SDK's `TAVILY_API_KEY` when omitted, + matching the existing Exa helper. Configurations that put a shared Tavily key + only under `web_search` must also set it under `web_fetch`, or rely on + `TAVILY_API_KEY` for both. ([#5496]) +- **client:** `DeerFlowClient.stream()` emits a streamed tool call once, with + its complete arguments. OpenAI-style models put the tool name and id in the + first chunk and argument fragments with no id in the rest, and the client + emitted a `messages-tuple` tool_calls event for every chunk, each parsed from + that chunk alone — so the complete call, which does arrive in the values + snapshot, was skipped because its message id was already in `streamed_ids`. + The tool itself ran with the right arguments; only the event stream was + wrong, and in the TUI the tool card showed `bash` with an empty detail + instead of the command. One event is now emitted per message from the values + snapshot, while whole AI messages from non-streaming models and streamed text + are unchanged. ([#5408]) +- **browser:** Keep browser session teardown alive across caller cancellation. + `BrowserSessionManager.close_session()` removed a session from the registry + before awaiting its close, and that close awaited the private Playwright loop + directly, so a cancellation at that point propagated through + `asyncio.wrap_future()` into the cleanup itself — leaving the browser process + and context orphaned, with the session already out of the registry and no + owner left to retry. `close_all_sessions()` had the same ownership problem at + a larger scale: it cleared the whole registry first, so a cancellation while + closing the first session stranded every later session with no teardown + scheduled at all. Teardown is now submitted to the private loop before any + cancellable await and awaited behind `asyncio.shield()`, and close-all + schedules every session before its first await and shields the group wait. + The caller still receives `CancelledError`; only ownership of the + already-started cleanup is isolated from it. ([#5444]) +- **browser:** Detect the browser dependency regardless of tool field order. + Startup dependency detection recognized a tool name only when `name` was the + first key of its YAML list item, so moving `name` below `use` or `group` kept + the tool configuration working but silently omitted the browser extra from + `uv sync`, leaving browser tools without their required dependencies. The + direct `name` field is now recognized anywhere in a tool entry, tracked + indentation keeps names inside nested options from enabling the extra, and + the detector stays standard-library-only because it runs before dependencies + are installed. ([#5456]) +- **setup:** The same pre-sync extras detector now reads `config.yaml` as + `utf-8-sig`. PyYAML accepts a leading UTF-8 BOM but the detector read the + file as plain `utf-8`, leaving the BOM attached to the first line, so its + anchored section patterns failed to recognize a valid config's opening + section and omitted the extras it declared — `postgres`, `browser`, and + `ollama` detection all failed the same way when their section came first, + while a plain-UTF-8 copy of the same file worked. ([#5504]) +- **video:** Forward `--aspect-ratio` into the Gemini Veo request. The skill + CLI accepted the flag and passed it to `generate_video()`, but the Gemini + branch built its `predictLongRunning` body from `instances` alone and dropped + the value, so every Veo request rendered at the provider default ratio + regardless of the argument. It is now sent as `parameters.aspectRatio`. + ([#5388]) +- **channels:** Both WeCom outbound paths sent unbounded text while the bot + protocol caps content at 20480 UTF-8 bytes, a bar a deep-research report + clears easily. Over the cap, `_send_with_retry` retried three times and gave + up: the stream path left the reply stuck mid-way without ever sending + `finish=true`, and the push path dropped the reply entirely. Stream replies + now clip on a UTF-8 character boundary behind a visible truncation marker — + one stream carries the whole reply, so it cannot split mid-way — while + proactive pushes with no replyable frame, such as scheduled-task + notifications, split into sequential markdown messages at newline boundaries + so the full content still arrives. Both paths now measure the payload in + UTF-8 bytes rather than characters. ([#5148]) +- **channels:** Inbound WeChat (iLink) and WeCom media was downloaded in full + before any size limit applied, and the download target was a URL taken + straight from the message payload with no destination validation, so a + legitimately oversized attachment spiked Gateway memory before being + rejected. Both channels now stream the transfer and abort in flight once it + exceeds `max_inbound_image_bytes` / `max_inbound_file_bytes`, with the exact + post-decrypt size checks kept as a second line. Inbound `full_url`s are gated + to http/https plus a dot-boundary host-suffix allowlist — the `qq.com` family + and the configured CDN host by default, extendable with + `channels.wechat.allowed_media_hosts` and + `channels.wecom.allowed_media_hosts` — with the WeCom COS gate pinned to the + verified Tencent Cloud APPID shape, and the WeCom manager reader bounded by a + 50 MB in-flight cap. Readers ask for `Accept-Encoding: identity` and refuse + any residual `Content-Encoding`, so httpx cannot inflate a compressed body + past the cap before it is measured. Well-formed media under the limits is + unaffected; oversized or non-allowlisted media is now skipped with a warning + naming the reason. ([#5225]) +- **channels:** Keep one undecodable WeChat message from dropping the rest of + its batch. `_poll_loop` persisted the `get_updates_buf` cursor for the entire + batch immediately on receipt, then iterated `data["msgs"]` with no per-message + error isolation, so a single message whose processing raised — a corrupt or + undecryptable attachment, such as a truncated encrypted image payload, is the + realistic trigger — aborted the loop with the cursor already advanced past the + whole batch. Every message after the failing one was permanently dropped and + the next poll never re-fetched it. `_handle_update` now runs inside a + per-message `try`/`except`, so siblings still reach the bus, the failure is + logged with its message id, and the cursor still advances once the batch has + been fully attempted. ([#4231]) +- **channels:** When the Discord client thread died — an invalidated token, an + unrecoverable close — `Client.start()` returned and left its event loop + stopped but not closed, so `call_soon_threadsafe` queued a callback that + would never run and the unbounded `await asyncio.wrap_future(...)` in `send`, + `send_file` and `_get_channel_or_thread` hung forever. Each outbound to the + dead channel permanently consumed one `ChannelManager` worker from the + default pool of five, so a handful of messages froze inbound processing for + every IM channel, not just Discord. Those cross-loop calls now route through + a helper that bounds the wait at `DISCORD_OUTBOUND_TIMEOUT_SECONDS` (30 s), + with file uploads given `DISCORD_UPLOAD_TIMEOUT_SECONDS` (120 s) so a slow + uplink plus a large 429 retry-after cannot cancel a healthy transfer, and a + missing or stopped loop closes the coroutine and raises immediately. + `DiscordChannel.is_running` also requires the client thread to be alive, as + `FeishuChannel` already did, so readiness polling restarts the channel + instead of treating a dead one as healthy; and a half-started instance is now + stopped and discarded when `start()` fails, so repeated readiness retries + cannot accumulate stale outbound listeners on the bus. ([#5227]) +- **channels:** Discord thread mappings are persisted off the event loop after + a thread is created, so a process killed between the creation and its + background write could still lose the newest mapping and leave a Discord + thread unreachable after the restart. `stop()` now flushes the in-memory + mappings to disk, wrapped so a shutdown path is never blocked — a best-effort + net over the hard-kill window rather than a new write path. ([#5461]) +- **channels:** Enabling `rich_messages` in the Telegram channel config made + every final outbound message render as a mangled single line: command menus + such as `/help` and plain error replies were sent through `sendRichMessage` + with `rich_message.markdown`, which collapses single newlines and strips the + `` / `` / `` angle-bracket tokens the menus rely on. + A message is now sent as rich only when `rich_messages` is enabled *and* the + text actually contains a rich construct — a fenced code block, a table row or + separator, a task list, a `[text](url)` link, bold, italic, a `
` + block, or `$$` math. The detector is deliberately conservative, requiring a + table to lead its line with `|`, so a line like `/goal [condition|clear]` in + `/help` never trips it. ([#5470]) +- **community:** The InfoQuest reader, web search and image search called + `requests` with no transport timeout, so a stalled endpoint left the + synchronous worker waiting indefinitely even when the remote crawl `timeout` + field was configured. All three call sites now share a 30-second connect/read + inactivity bound; crawl timeout and navigation payload fields, successful + parsing and the existing `Error:` returns are unchanged. This bounds + inactivity, not total wall-clock time. ([#5315]) +- **models:** The request-admission limiter decided immediate admission and + queue insertion under two separate lock acquisitions, so a blocking caller + could observe that the next permit was not yet due, be descheduled before it + enqueued, and then be overtaken by a newer caller once the permit came due + while the waiter queue was still empty — the newcomer took the permit and the + older caller waited a further interval, or under a short `max_wait_seconds` + timed out while the later request succeeded. Immediate admission and FIFO + insertion are now a single lock-protected decision. Non-blocking calls still + fail fast and never enter the queue, and queued pacing, cancellation, + timeout, queue-capacity and no-burst behavior are unchanged. ([#5459]) +- **uploads:** Uploaded-document outlines counted any line starting with `#` as + a heading, so a document with 51 `#tag` lines before `# Real section` + consumed every outline slot and hid the real section; four-space and + tab-indented comments became false headings, and optional closing hashes + stayed in the titles. The extractor now matches root-level ATX syntax on the + original line — one to six hashes, a space or tab separator or end of line, + at most three leading spaces — and strips valid closing hashes before the + existing bold cleanup. Physical line numbers, PDF structural headings, + fenced-code exclusion and the heading-count limit are unchanged, and the + scanner stays bounded rather than becoming a Markdown parser. ([#5316]) +- **uploads:** A single 200,000-character paragraph produced a + 199,999-character upload preview despite the five-line limit, and long valid + headings bypassed the 50-entry limit as a practical context-size bound. + Outline titles are now capped at 200 characters and fallback preview text at + 2,000 characters across all lines, with the omission markers counted inside + those budgets. Physical line numbers, short text, the existing heading and + preview counts, and the original file bytes are preserved; this bounds + returned summary text only, not file-scan memory or a budget across uploads. + ([#5323]) +- **doctor:** On a fresh clone, `make config` followed by `make doctor` + reported four errors where only one was real: `config.example.yaml` ships a + `models:` key with every entry commented out, which parses as `None`, so the + `[]` default in `.get("models", [])` never applied and a broad `except` + rendered the resulting `TypeError` as three `'NoneType' object is not + iterable` check results. A new user saw what looked like a broken install at + the exact moment `make doctor` is meant to reduce confusion, when they had + simply not configured a model yet. The LLM checks now skip when no models are + configured, leaving the actionable `models configured` failure and its `make + setup` hint; nothing changes once a model is configured. ([#5296]) +- **scripts:** `detect_uv_extras.py` resolves uv extras from `config.yaml` so + `make dev` does not wipe optional dependencies on every restart, but it had + no rule for Ollama — so with an Ollama model configured it returned nothing, + `serve.sh` ran `uv sync` without `--extra ollama`, and `langchain-ollama` was + uninstalled from a working setup. The configured model then failed with + `ModuleNotFoundError: No module named 'langchain_ollama'` with nothing + pointing back at `make dev` as the cause, even though `make doctor` had just + reported the package installed. Configuring an Ollama model now makes `make + dev` pass `--extra ollama`, the same way `database.backend: postgres` passes + `--extra postgres`; a config without an Ollama model is unaffected, and the + commented-out Ollama examples in `config.example.yaml` are correctly ignored. + ([#5318]) +- **docker:** On Windows Git Bash, `make docker-start` and `make up` aborted + unconditionally with `Docker socket not found at /var/run/docker.sock — + AioSandboxProvider (DooD) will not work.` MSYS2/Git Bash has no physical Unix + socket at that path — Docker Desktop uses named pipes — even though the + daemon natively handles mounting `/var/run/docker.sock` into Linux + containers, so the check could never pass. Under `MINGW*`, `MSYS*` and + `CYGWIN*` the preflight now verifies `docker info` connectivity instead of + requiring a socket file; POSIX hosts still fail fast on a missing socket. + ([#5371]) +- **deploy:** `make up` failed on Windows Git Bash during container startup + with `mkdir C:\Program Files\Git\var: Access is denied.` `scripts/deploy.sh` + exported `DEER_FLOW_DOCKER_SOCKET=/var/run/docker.sock`, and MSYS converted + that exported value into a Windows host path when invoking native `docker + compose`, so `docker-compose.dood.yaml` mounted `C:\Program + Files\Git\var\run\docker.sock` and the daemon tried to create a directory + that does not exist on the host. The socket is now kept in an unexported + local variable, as `scripts/docker.sh` already did, and on Windows Git Bash + the variable is unset when it holds the default so Compose expands its own + literal `/var/run/docker.sock`; an operator-set custom socket path is still + passed through. ([#5402]) - **mcp:** Exclude the internal stdio MCP temp directory (`.mcp/tmp`) from workspace changes, so MCP temporary and debug files no longer appear alongside user deliverables or crowd real changes out of the file budget. @@ -1324,6 +2432,18 @@ This section accumulates work toward the **2.1.0** milestone - **agents:** Normalize `ToolMessage`s returned inside `Command` results, so error payloads no longer earn a default success receipt and tool-progress tracking sees them. ([#4977]) +- **config:** `use_previous_response_id` works in a `config.yaml` model entry — + it is not a `ModelConfig` field and reaches `ChatOpenAI` only through the + model factory's `extra="allow"` passthrough — but was never documented, and + reads like a synonym for `use_responses_api`. It is not: `use_responses_api` + picks the endpoint, while `use_previous_response_id` switches the Responses + API from replaying the full history each turn to chaining on server-side + state. The OpenAI Responses API example in `config.example.yaml` gains a + commented `use_previous_response_id: false` line noting that it is forwarded + to `ChatOpenAI` as-is, that chained context is still billed as input tokens, + and that client-side history rewrites only apply when the history is + replayed. Comment only — no schema change, no new `ModelConfig` field, no + `config_version` bump. ([#5359]) - **mcp:** Tear down the in-flight session owner when `get_session` is cancelled during eviction, so a cancelled caller no longer leaks the owner task or parks past its timeout. ([#5008]) @@ -1390,6 +2510,78 @@ This section accumulates work toward the **2.1.0** milestone policy, and the 5-second injection deadline honors the same policy (fail-open continues without the context; strict raises with the timeout as its cause). ([#4726]) +- **persistence:** Add the storage foundation for a per-thread incarnation + token without activating it. New nullable `threads_meta.incarnation` and + `mcp_tasks.thread_incarnation` columns (a new Alembic head) let a task be + tied to the specific thread a reused thread ID belongs to, since a deleted + thread's ID can be taken by a later thread while durable MCP work outlives + the deletion. A random 32-character incarnation is assigned to newly created + thread records and copied into new MCP task rows only when the thread is + owned by the task user or is an unowned legacy row, with the lookup and + insert kept atomic on SQLite through a scalar subquery and a share lock on + PostgreSQL. Nothing reads the fields yet, they are kept out of the thread and + MCP task API responses, and both columns are nullable with no default so + older binaries keep writing during a rolling deployment. ([#5216]) +- **scripts:** On native Windows, `shutil.which("pnpm")` follows + `PATH`/`PATHEXT` resolution and can select a generic `pnpm` match — an `.exe` + or `.bat`, or an earlier-path wrapper — ahead of the npm-installed `pnpm.cmd` + wrapper. The shared host runner now checks `pnpm.cmd` and `corepack.cmd` + before the generic names, matching the Windows command-selection order the + repo already documents; POSIX resolution order is unchanged. ([#5305]) +- **tests:** The backend suite failed 68 shell-script tests across seven files + on a Windows contributor host, none of them for anything wrong with the code + under test. CreateProcess searches `System32` before `PATH`, so the WSL + `bash.exe` launcher won every `["bash", ...]` spawn and could not run the + repo scripts against Windows checkout paths — on non-English Windows its + localized UTF-16LE warning crashed the harness's pipe decoding and buried the + real failure behind a `TypeError`; `sh` does not exist outside MSYS2; and + `dev-entrypoint.sh`'s `command -v python3` probe selected the Microsoft Store + alias stubs, which exit 49 when executed. The suites now resolve a shell + through a shared helper that mirrors the repo's Git Bash wrapper discovery + and explicitly rejects the WSL launcher and the Store stubs, and skip cleanly + where no Git Bash exists. POSIX and CI behavior is unchanged. ([#5404]) +- **tests:** Six tests across three suites failed on a Windows host purely on + path-separator spelling. The provisioner's `join_host_path` deliberately + preserves host-native style and even has an explicit `PureWindowsPath` + branch, and `os.path.normpath` re-spells POSIX inputs with backslashes there, + so the `hostPath` strings it builds, the docker `--mount` `src=` spelling, + and the review CLI's `PYTHONPATH` never matched POSIX-style literals. The + assertions now normalize the host-native side before comparing, which is a + no-op on POSIX so CI expectations stay byte-identical; production behavior is + unchanged. ([#5413]) +- **tests:** Five tests in the skill request-scoped-secrets suite failed on a + Windows host and two negative checks passed vacuously, because the probes + embedded POSIX syntax: `LocalSandbox._get_shell()` resolves to PowerShell + there, where `$VAR` is an undefined PowerShell variable and expands to + nothing, so the positive checks could never see an injected value and the + `"secret" not in out` assertions were comparing against empty output. Probes + are now rendered in the syntax of the shell production actually resolved — + `$env:NAME` under PowerShell, `%NAME%` under cmd.exe, `$NAME` under POSIX + shells — so the negative checks genuinely verify scrubbing on Windows, and + the per-call-scoping case also proves the injected value reached the first + subprocess. POSIX output is unchanged in form. ([#5415]) +- **tests:** `test_run_on_isolated_subagent_loop_survives_caller_loop_teardown` + failed intermittently under CI load, on unrelated PRs and on `main` itself. + The race was in the test, not in `run_on_isolated_subagent_loop`: that helper + is `asyncio.run_coroutine_threadsafe`, whose `concurrent.futures.Future` is + only marked done after the coroutine returns, but the test signalled from + inside the coroutine and asserted `done()` immediately, so the main thread + could wake while the future was still `pending`. The assertions now block on + `result(timeout=10)` before checking `done()`; no `sleep` is introduced and + the behavior under test is unchanged. ([#5299]) +- **tests:** Checkpoint retention needs an executable statement of what a + deletion may never break — a newest-N proposal had to be withdrawn once + review showed it would silently break branch and time-travel parent-chain + semantics. The new suite pins six retention scenarios across the memory, + SQLite and Postgres checkpointers: growth baselines per step for the full and + delta schemas; deleting a branch ancestor fails loudly with + `CheckpointLineageError` instead of silently corrupting branch/regenerate; + deleting an explicit resume target removes the resume surface; pending writes + are retained state rather than garbage; and a trailing duration-only leaf and + a leaf sibling branch — the real fork path — can be deleted safely. A + companion draft documents the protected set, the provably safe deletion + shapes, and joint-table mechanics with orphan accounting, for any future + `max_checkpoints_per_thread` or TTL work to be validated against. ([#5255]) - **memory:** Buffered memory extraction is cancelled when a custom agent is deleted or cleared, so pending debounce timers can no longer resurrect the deleted per-agent memory scope or overwrite a fresh clear with a stale @@ -1568,8 +2760,24 @@ This section accumulates work toward the **2.1.0** milestone and the conversation block in the memory-update prompt - and neutralize prompt-injection tags in `web_capture` tool results. ([#4028], [#4119], [#4137], [#4157], [#4162], [#4099], [#4060], [#4097], [#4128]) -- **prompt-injection:** Close two input-sanitization bypasses. `hide_from_ui` and - a human `name="summary"` tell `is_genuine_user_message` that the framework +- **prompt-injection:** Escape MindIE tool-response framing. The provider's + `_fix_messages` escaped tool-call names and arguments before rendering them + into `` / `` tags, but the sibling path that wraps a + `ToolMessage` in `` passed its text through unescaped — and + that output arrives largely unsanitized, because the tool-result sanitizer + covers only the remote-content tools on its allowlist (`web_fetch`, + `web_search`, `image_search`, `web_capture`) and deliberately leaves local + `bash` / `read_file` output and differently named MCP tools alone. A literal + `` in a `read_file` result therefore closed the framing early, + and everything after it was presented to the MindIE model as though it sat + outside the tool response, so a payload planted in an untrusted file could + forge a ``. Content is now HTML-escaped with `quote=False`, + matching the tool-call path; the model still decodes the entities back, so + only the framing is protected and legitimate results are understood exactly + as before. ([#4253]) +- **prompt-injection:** Close two input-sanitization bypasses. + `hide_from_ui` and a human `name="summary"` tell `is_genuine_user_message` + that the framework authored a message, which skips sanitization entirely. Untrusted run input and thread-state writes carrying either marker are now marked server-side and sanitized regardless, so a caller can no longer land a raw `` @@ -1715,8 +2923,12 @@ This section accumulates work toward the **2.1.0** milestone and `delta` across latency, storage, and replay metrics. ([#4395]) - **deps:** Bump `cryptography` 49.0.0 -> 50.0.0, `postcss` 8.4.31 -> 8.5.25, `h2` 4.3.0 -> 4.4.1, `langgraph-checkpoint-sqlite` and - `langgraph-checkpoint-postgres` 3.1.0 -> 3.1.1, and `nanoid` 5.1.6 -> 5.1.16. - ([#4681], [#4683], [#4737], [#4738], [#4747], [#4748]) + `langgraph-checkpoint-postgres` 3.1.0 -> 3.1.1, `nanoid` 5.1.6 -> 5.1.16, + `h3` 1.15.6 -> 1.15.9, and `next` 16.2.11 -> 16.3.3. The `next` bump closes + two unauthenticated remote code execution advisories — one in the Image + Optimization API's AVIF path, one on Windows-hosted servers — while `h3` + stops a double-encoded dot segment from traversing out of the static route. + ([#4211], [#4681], [#4683], [#4737], [#4738], [#4747], [#4748], [#5377]) - **bench:** Add a reproducible hybrid memory-eviction evaluation under `backend/scripts/benchmark/deermem_eviction/` with a deterministic, blind-by-construction grader for the #4789 policy. ([#4810]) @@ -2151,6 +3363,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#3153]: https://github.com/bytedance/deer-flow/pull/3153 [#3174]: https://github.com/bytedance/deer-flow/pull/3174 [#3176]: https://github.com/bytedance/deer-flow/pull/3176 +[#3183]: https://github.com/bytedance/deer-flow/pull/3183 [#3191]: https://github.com/bytedance/deer-flow/pull/3191 [#3200]: https://github.com/bytedance/deer-flow/pull/3200 [#3228]: https://github.com/bytedance/deer-flow/pull/3228 @@ -2169,6 +3382,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#3342]: https://github.com/bytedance/deer-flow/pull/3342 [#3377]: https://github.com/bytedance/deer-flow/pull/3377 [#3393]: https://github.com/bytedance/deer-flow/pull/3393 +[#3396]: https://github.com/bytedance/deer-flow/pull/3396 [#3397]: https://github.com/bytedance/deer-flow/pull/3397 [#3398]: https://github.com/bytedance/deer-flow/pull/3398 [#3408]: https://github.com/bytedance/deer-flow/pull/3408 @@ -2188,6 +3402,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#3435]: https://github.com/bytedance/deer-flow/pull/3435 [#3436]: https://github.com/bytedance/deer-flow/pull/3436 [#3437]: https://github.com/bytedance/deer-flow/pull/3437 +[#3442]: https://github.com/bytedance/deer-flow/pull/3442 [#3451]: https://github.com/bytedance/deer-flow/pull/3451 [#3453]: https://github.com/bytedance/deer-flow/pull/3453 [#3455]: https://github.com/bytedance/deer-flow/pull/3455 @@ -2238,6 +3453,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#3561]: https://github.com/bytedance/deer-flow/pull/3561 [#3562]: https://github.com/bytedance/deer-flow/pull/3562 [#3563]: https://github.com/bytedance/deer-flow/pull/3563 +[#3565]: https://github.com/bytedance/deer-flow/pull/3565 [#3566]: https://github.com/bytedance/deer-flow/pull/3566 [#3569]: https://github.com/bytedance/deer-flow/pull/3569 [#3570]: https://github.com/bytedance/deer-flow/pull/3570 @@ -2289,6 +3505,8 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#3686]: https://github.com/bytedance/deer-flow/pull/3686 [#3687]: https://github.com/bytedance/deer-flow/pull/3687 [#3698]: https://github.com/bytedance/deer-flow/pull/3698 +[#3703]: https://github.com/bytedance/deer-flow/pull/3703 +[#3708]: https://github.com/bytedance/deer-flow/pull/3708 [#3709]: https://github.com/bytedance/deer-flow/pull/3709 [#3711]: https://github.com/bytedance/deer-flow/pull/3711 [#3713]: https://github.com/bytedance/deer-flow/pull/3713 @@ -2307,6 +3525,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#3770]: https://github.com/bytedance/deer-flow/pull/3770 [#3772]: https://github.com/bytedance/deer-flow/pull/3772 [#3775]: https://github.com/bytedance/deer-flow/pull/3775 +[#3783]: https://github.com/bytedance/deer-flow/pull/3783 [#3786]: https://github.com/bytedance/deer-flow/pull/3786 [#3790]: https://github.com/bytedance/deer-flow/pull/3790 [#3791]: https://github.com/bytedance/deer-flow/pull/3791 @@ -2317,8 +3536,10 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#3810]: https://github.com/bytedance/deer-flow/pull/3810 [#3812]: https://github.com/bytedance/deer-flow/pull/3812 [#3821]: https://github.com/bytedance/deer-flow/pull/3821 +[#3824]: https://github.com/bytedance/deer-flow/pull/3824 [#3826]: https://github.com/bytedance/deer-flow/pull/3826 [#3828]: https://github.com/bytedance/deer-flow/pull/3828 +[#3833]: https://github.com/bytedance/deer-flow/pull/3833 [#3837]: https://github.com/bytedance/deer-flow/pull/3837 [#3839]: https://github.com/bytedance/deer-flow/pull/3839 [#3843]: https://github.com/bytedance/deer-flow/pull/3843 @@ -2401,6 +3622,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4018]: https://github.com/bytedance/deer-flow/pull/4018 [#4023]: https://github.com/bytedance/deer-flow/pull/4023 [#4024]: https://github.com/bytedance/deer-flow/pull/4024 +[#4025]: https://github.com/bytedance/deer-flow/pull/4025 [#4026]: https://github.com/bytedance/deer-flow/pull/4026 [#4028]: https://github.com/bytedance/deer-flow/pull/4028 [#4033]: https://github.com/bytedance/deer-flow/pull/4033 @@ -2434,7 +3656,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4085]: https://github.com/bytedance/deer-flow/pull/4085 [#4090]: https://github.com/bytedance/deer-flow/pull/4090 [#4094]: https://github.com/bytedance/deer-flow/pull/4094 -[#4095]: https://github.com/bytedance/deer-flow/issues/4095 +[#4095]: https://github.com/bytedance/deer-flow/pull/4095 [#4096]: https://github.com/bytedance/deer-flow/pull/4096 [#4097]: https://github.com/bytedance/deer-flow/pull/4097 [#4098]: https://github.com/bytedance/deer-flow/pull/4098 @@ -2481,14 +3703,17 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4187]: https://github.com/bytedance/deer-flow/pull/4187 [#4188]: https://github.com/bytedance/deer-flow/pull/4188 [#4190]: https://github.com/bytedance/deer-flow/pull/4190 -[#4192]: https://github.com/bytedance/deer-flow/issues/4192 +[#4192]: https://github.com/bytedance/deer-flow/pull/4192 [#4193]: https://github.com/bytedance/deer-flow/pull/4193 [#4197]: https://github.com/bytedance/deer-flow/pull/4197 [#4199]: https://github.com/bytedance/deer-flow/pull/4199 +[#4200]: https://github.com/bytedance/deer-flow/pull/4200 [#4202]: https://github.com/bytedance/deer-flow/pull/4202 [#4203]: https://github.com/bytedance/deer-flow/pull/4203 [#4208]: https://github.com/bytedance/deer-flow/pull/4208 [#4209]: https://github.com/bytedance/deer-flow/pull/4209 +[#4210]: https://github.com/bytedance/deer-flow/pull/4210 +[#4211]: https://github.com/bytedance/deer-flow/pull/4211 [#4215]: https://github.com/bytedance/deer-flow/pull/4215 [#4217]: https://github.com/bytedance/deer-flow/pull/4217 [#4218]: https://github.com/bytedance/deer-flow/pull/4218 @@ -2497,32 +3722,48 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4225]: https://github.com/bytedance/deer-flow/pull/4225 [#4229]: https://github.com/bytedance/deer-flow/pull/4229 [#4230]: https://github.com/bytedance/deer-flow/pull/4230 +[#4231]: https://github.com/bytedance/deer-flow/pull/4231 [#4234]: https://github.com/bytedance/deer-flow/pull/4234 [#4235]: https://github.com/bytedance/deer-flow/pull/4235 [#4238]: https://github.com/bytedance/deer-flow/pull/4238 [#4239]: https://github.com/bytedance/deer-flow/pull/4239 +[#4241]: https://github.com/bytedance/deer-flow/pull/4241 +[#4242]: https://github.com/bytedance/deer-flow/pull/4242 [#4245]: https://github.com/bytedance/deer-flow/pull/4245 [#4246]: https://github.com/bytedance/deer-flow/pull/4246 +[#4247]: https://github.com/bytedance/deer-flow/pull/4247 +[#4250]: https://github.com/bytedance/deer-flow/pull/4250 [#4251]: https://github.com/bytedance/deer-flow/pull/4251 +[#4253]: https://github.com/bytedance/deer-flow/pull/4253 [#4255]: https://github.com/bytedance/deer-flow/pull/4255 [#4256]: https://github.com/bytedance/deer-flow/pull/4256 [#4260]: https://github.com/bytedance/deer-flow/pull/4260 +[#4262]: https://github.com/bytedance/deer-flow/pull/4262 [#4264]: https://github.com/bytedance/deer-flow/pull/4264 +[#4266]: https://github.com/bytedance/deer-flow/pull/4266 [#4267]: https://github.com/bytedance/deer-flow/pull/4267 [#4268]: https://github.com/bytedance/deer-flow/pull/4268 +[#4274]: https://github.com/bytedance/deer-flow/pull/4274 +[#4275]: https://github.com/bytedance/deer-flow/pull/4275 [#4277]: https://github.com/bytedance/deer-flow/pull/4277 [#4278]: https://github.com/bytedance/deer-flow/pull/4278 [#4279]: https://github.com/bytedance/deer-flow/pull/4279 [#4283]: https://github.com/bytedance/deer-flow/pull/4283 +[#4284]: https://github.com/bytedance/deer-flow/pull/4284 [#4287]: https://github.com/bytedance/deer-flow/pull/4287 [#4288]: https://github.com/bytedance/deer-flow/pull/4288 [#4292]: https://github.com/bytedance/deer-flow/pull/4292 +[#4293]: https://github.com/bytedance/deer-flow/pull/4293 +[#4298]: https://github.com/bytedance/deer-flow/pull/4298 +[#4301]: https://github.com/bytedance/deer-flow/pull/4301 +[#4302]: https://github.com/bytedance/deer-flow/pull/4302 [#4306]: https://github.com/bytedance/deer-flow/pull/4306 [#4309]: https://github.com/bytedance/deer-flow/pull/4309 [#4311]: https://github.com/bytedance/deer-flow/pull/4311 +[#4314]: https://github.com/bytedance/deer-flow/pull/4314 [#4315]: https://github.com/bytedance/deer-flow/pull/4315 [#4316]: https://github.com/bytedance/deer-flow/pull/4316 -[#4324]: https://github.com/bytedance/deer-flow/issues/4324 +[#4324]: https://github.com/bytedance/deer-flow/pull/4324 [#4326]: https://github.com/bytedance/deer-flow/pull/4326 [#4337]: https://github.com/bytedance/deer-flow/pull/4337 [#4347]: https://github.com/bytedance/deer-flow/pull/4347 @@ -2531,6 +3772,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4355]: https://github.com/bytedance/deer-flow/pull/4355 [#4356]: https://github.com/bytedance/deer-flow/pull/4356 [#4358]: https://github.com/bytedance/deer-flow/pull/4358 +[#4360]: https://github.com/bytedance/deer-flow/pull/4360 [#4361]: https://github.com/bytedance/deer-flow/pull/4361 [#4364]: https://github.com/bytedance/deer-flow/pull/4364 [#4365]: https://github.com/bytedance/deer-flow/pull/4365 @@ -2539,86 +3781,51 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4373]: https://github.com/bytedance/deer-flow/pull/4373 [#4374]: https://github.com/bytedance/deer-flow/pull/4374 [#4376]: https://github.com/bytedance/deer-flow/pull/4376 +[#4377]: https://github.com/bytedance/deer-flow/pull/4377 [#4381]: https://github.com/bytedance/deer-flow/pull/4381 +[#4382]: https://github.com/bytedance/deer-flow/pull/4382 [#4383]: https://github.com/bytedance/deer-flow/pull/4383 +[#4384]: https://github.com/bytedance/deer-flow/pull/4384 [#4385]: https://github.com/bytedance/deer-flow/pull/4385 [#4391]: https://github.com/bytedance/deer-flow/pull/4391 [#4392]: https://github.com/bytedance/deer-flow/pull/4392 [#4394]: https://github.com/bytedance/deer-flow/pull/4394 +[#4395]: https://github.com/bytedance/deer-flow/pull/4395 [#4402]: https://github.com/bytedance/deer-flow/pull/4402 [#4403]: https://github.com/bytedance/deer-flow/pull/4403 +[#4405]: https://github.com/bytedance/deer-flow/pull/4405 +[#4406]: https://github.com/bytedance/deer-flow/pull/4406 [#4407]: https://github.com/bytedance/deer-flow/pull/4407 [#4408]: https://github.com/bytedance/deer-flow/pull/4408 [#4411]: https://github.com/bytedance/deer-flow/pull/4411 -[#4414]: https://github.com/bytedance/deer-flow/issues/4414 -[#4424]: https://github.com/bytedance/deer-flow/issues/4424 +[#4414]: https://github.com/bytedance/deer-flow/pull/4414 +[#4423]: https://github.com/bytedance/deer-flow/pull/4423 +[#4424]: https://github.com/bytedance/deer-flow/pull/4424 [#4425]: https://github.com/bytedance/deer-flow/pull/4425 [#4426]: https://github.com/bytedance/deer-flow/pull/4426 +[#4427]: https://github.com/bytedance/deer-flow/pull/4427 +[#4429]: https://github.com/bytedance/deer-flow/pull/4429 [#4430]: https://github.com/bytedance/deer-flow/pull/4430 [#4431]: https://github.com/bytedance/deer-flow/pull/4431 [#4432]: https://github.com/bytedance/deer-flow/pull/4432 [#4434]: https://github.com/bytedance/deer-flow/pull/4434 [#4437]: https://github.com/bytedance/deer-flow/pull/4437 +[#4439]: https://github.com/bytedance/deer-flow/pull/4439 [#4441]: https://github.com/bytedance/deer-flow/pull/4441 [#4442]: https://github.com/bytedance/deer-flow/pull/4442 +[#4443]: https://github.com/bytedance/deer-flow/pull/4443 [#4444]: https://github.com/bytedance/deer-flow/pull/4444 [#4446]: https://github.com/bytedance/deer-flow/pull/4446 [#4447]: https://github.com/bytedance/deer-flow/pull/4447 +[#4448]: https://github.com/bytedance/deer-flow/pull/4448 [#4450]: https://github.com/bytedance/deer-flow/pull/4450 +[#4453]: https://github.com/bytedance/deer-flow/pull/4453 [#4456]: https://github.com/bytedance/deer-flow/pull/4456 [#4459]: https://github.com/bytedance/deer-flow/pull/4459 [#4460]: https://github.com/bytedance/deer-flow/pull/4460 [#4468]: https://github.com/bytedance/deer-flow/pull/4468 [#4469]: https://github.com/bytedance/deer-flow/pull/4469 [#4471]: https://github.com/bytedance/deer-flow/pull/4471 -[#4516]: https://github.com/bytedance/deer-flow/pull/4516 -[#4611]: https://github.com/bytedance/deer-flow/issues/4611 -[#4745]: https://github.com/bytedance/deer-flow/pull/4745 -[#4574]: https://github.com/bytedance/deer-flow/issues/4574 -[#4577]: https://github.com/bytedance/deer-flow/pull/4577 -[#4623]: https://github.com/bytedance/deer-flow/pull/4623 -[#4634]: https://github.com/bytedance/deer-flow/pull/4634 -[#4638]: https://github.com/bytedance/deer-flow/pull/4638 -[#4848]: https://github.com/bytedance/deer-flow/pull/4848 -[#3183]: https://github.com/bytedance/deer-flow/pull/3183 -[#3396]: https://github.com/bytedance/deer-flow/pull/3396 -[#3442]: https://github.com/bytedance/deer-flow/pull/3442 -[#3565]: https://github.com/bytedance/deer-flow/pull/3565 -[#3703]: https://github.com/bytedance/deer-flow/pull/3703 -[#3708]: https://github.com/bytedance/deer-flow/pull/3708 -[#3783]: https://github.com/bytedance/deer-flow/pull/3783 -[#3824]: https://github.com/bytedance/deer-flow/pull/3824 -[#3833]: https://github.com/bytedance/deer-flow/pull/3833 -[#4025]: https://github.com/bytedance/deer-flow/pull/4025 -[#4200]: https://github.com/bytedance/deer-flow/pull/4200 -[#4210]: https://github.com/bytedance/deer-flow/pull/4210 -[#4242]: https://github.com/bytedance/deer-flow/pull/4242 -[#4247]: https://github.com/bytedance/deer-flow/pull/4247 -[#4250]: https://github.com/bytedance/deer-flow/pull/4250 -[#4262]: https://github.com/bytedance/deer-flow/pull/4262 -[#4266]: https://github.com/bytedance/deer-flow/pull/4266 -[#4274]: https://github.com/bytedance/deer-flow/pull/4274 -[#4275]: https://github.com/bytedance/deer-flow/pull/4275 -[#4284]: https://github.com/bytedance/deer-flow/pull/4284 -[#4293]: https://github.com/bytedance/deer-flow/pull/4293 -[#4298]: https://github.com/bytedance/deer-flow/pull/4298 -[#4301]: https://github.com/bytedance/deer-flow/pull/4301 -[#4302]: https://github.com/bytedance/deer-flow/pull/4302 -[#4314]: https://github.com/bytedance/deer-flow/pull/4314 -[#4360]: https://github.com/bytedance/deer-flow/pull/4360 -[#4377]: https://github.com/bytedance/deer-flow/pull/4377 -[#4382]: https://github.com/bytedance/deer-flow/pull/4382 -[#4384]: https://github.com/bytedance/deer-flow/pull/4384 -[#4395]: https://github.com/bytedance/deer-flow/pull/4395 -[#4405]: https://github.com/bytedance/deer-flow/pull/4405 -[#4406]: https://github.com/bytedance/deer-flow/pull/4406 -[#4423]: https://github.com/bytedance/deer-flow/pull/4423 -[#4427]: https://github.com/bytedance/deer-flow/pull/4427 -[#4429]: https://github.com/bytedance/deer-flow/pull/4429 -[#4439]: https://github.com/bytedance/deer-flow/pull/4439 -[#4443]: https://github.com/bytedance/deer-flow/pull/4443 -[#4448]: https://github.com/bytedance/deer-flow/pull/4448 -[#4453]: https://github.com/bytedance/deer-flow/pull/4453 [#4472]: https://github.com/bytedance/deer-flow/pull/4472 [#4480]: https://github.com/bytedance/deer-flow/pull/4480 [#4482]: https://github.com/bytedance/deer-flow/pull/4482 @@ -2636,6 +3843,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4510]: https://github.com/bytedance/deer-flow/pull/4510 [#4512]: https://github.com/bytedance/deer-flow/pull/4512 [#4513]: https://github.com/bytedance/deer-flow/pull/4513 +[#4516]: https://github.com/bytedance/deer-flow/pull/4516 [#4518]: https://github.com/bytedance/deer-flow/pull/4518 [#4519]: https://github.com/bytedance/deer-flow/pull/4519 [#4524]: https://github.com/bytedance/deer-flow/pull/4524 @@ -2653,7 +3861,9 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4559]: https://github.com/bytedance/deer-flow/pull/4559 [#4564]: https://github.com/bytedance/deer-flow/pull/4564 [#4570]: https://github.com/bytedance/deer-flow/pull/4570 +[#4574]: https://github.com/bytedance/deer-flow/pull/4574 [#4575]: https://github.com/bytedance/deer-flow/pull/4575 +[#4577]: https://github.com/bytedance/deer-flow/pull/4577 [#4578]: https://github.com/bytedance/deer-flow/pull/4578 [#4582]: https://github.com/bytedance/deer-flow/pull/4582 [#4584]: https://github.com/bytedance/deer-flow/pull/4584 @@ -2664,18 +3874,22 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4599]: https://github.com/bytedance/deer-flow/pull/4599 [#4600]: https://github.com/bytedance/deer-flow/pull/4600 [#4604]: https://github.com/bytedance/deer-flow/pull/4604 +[#4611]: https://github.com/bytedance/deer-flow/pull/4611 [#4615]: https://github.com/bytedance/deer-flow/pull/4615 [#4617]: https://github.com/bytedance/deer-flow/pull/4617 [#4618]: https://github.com/bytedance/deer-flow/pull/4618 [#4620]: https://github.com/bytedance/deer-flow/pull/4620 +[#4623]: https://github.com/bytedance/deer-flow/pull/4623 [#4624]: https://github.com/bytedance/deer-flow/pull/4624 [#4625]: https://github.com/bytedance/deer-flow/pull/4625 [#4627]: https://github.com/bytedance/deer-flow/pull/4627 [#4629]: https://github.com/bytedance/deer-flow/pull/4629 [#4631]: https://github.com/bytedance/deer-flow/pull/4631 [#4633]: https://github.com/bytedance/deer-flow/pull/4633 +[#4634]: https://github.com/bytedance/deer-flow/pull/4634 [#4635]: https://github.com/bytedance/deer-flow/pull/4635 [#4636]: https://github.com/bytedance/deer-flow/pull/4636 +[#4638]: https://github.com/bytedance/deer-flow/pull/4638 [#4639]: https://github.com/bytedance/deer-flow/pull/4639 [#4643]: https://github.com/bytedance/deer-flow/pull/4643 [#4644]: https://github.com/bytedance/deer-flow/pull/4644 @@ -2694,6 +3908,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4684]: https://github.com/bytedance/deer-flow/pull/4684 [#4690]: https://github.com/bytedance/deer-flow/pull/4690 [#4693]: https://github.com/bytedance/deer-flow/pull/4693 +[#4696]: https://github.com/bytedance/deer-flow/pull/4696 [#4701]: https://github.com/bytedance/deer-flow/pull/4701 [#4703]: https://github.com/bytedance/deer-flow/pull/4703 [#4707]: https://github.com/bytedance/deer-flow/pull/4707 @@ -2702,13 +3917,16 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4719]: https://github.com/bytedance/deer-flow/pull/4719 [#4722]: https://github.com/bytedance/deer-flow/pull/4722 [#4724]: https://github.com/bytedance/deer-flow/pull/4724 +[#4726]: https://github.com/bytedance/deer-flow/pull/4726 [#4727]: https://github.com/bytedance/deer-flow/pull/4727 +[#4729]: https://github.com/bytedance/deer-flow/pull/4729 [#4730]: https://github.com/bytedance/deer-flow/pull/4730 [#4735]: https://github.com/bytedance/deer-flow/pull/4735 [#4736]: https://github.com/bytedance/deer-flow/pull/4736 [#4737]: https://github.com/bytedance/deer-flow/pull/4737 [#4738]: https://github.com/bytedance/deer-flow/pull/4738 [#4744]: https://github.com/bytedance/deer-flow/pull/4744 +[#4745]: https://github.com/bytedance/deer-flow/pull/4745 [#4747]: https://github.com/bytedance/deer-flow/pull/4747 [#4748]: https://github.com/bytedance/deer-flow/pull/4748 [#4750]: https://github.com/bytedance/deer-flow/pull/4750 @@ -2731,67 +3949,76 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4800]: https://github.com/bytedance/deer-flow/pull/4800 [#4804]: https://github.com/bytedance/deer-flow/pull/4804 [#4806]: https://github.com/bytedance/deer-flow/pull/4806 +[#4810]: https://github.com/bytedance/deer-flow/pull/4810 [#4812]: https://github.com/bytedance/deer-flow/pull/4812 [#4815]: https://github.com/bytedance/deer-flow/pull/4815 [#4816]: https://github.com/bytedance/deer-flow/pull/4816 [#4817]: https://github.com/bytedance/deer-flow/pull/4817 +[#4820]: https://github.com/bytedance/deer-flow/pull/4820 [#4822]: https://github.com/bytedance/deer-flow/pull/4822 [#4823]: https://github.com/bytedance/deer-flow/pull/4823 [#4825]: https://github.com/bytedance/deer-flow/pull/4825 +[#4826]: https://github.com/bytedance/deer-flow/pull/4826 [#4827]: https://github.com/bytedance/deer-flow/pull/4827 [#4830]: https://github.com/bytedance/deer-flow/pull/4830 [#4833]: https://github.com/bytedance/deer-flow/pull/4833 +[#4834]: https://github.com/bytedance/deer-flow/pull/4834 [#4836]: https://github.com/bytedance/deer-flow/pull/4836 [#4838]: https://github.com/bytedance/deer-flow/pull/4838 +[#4839]: https://github.com/bytedance/deer-flow/pull/4839 [#4840]: https://github.com/bytedance/deer-flow/pull/4840 [#4842]: https://github.com/bytedance/deer-flow/pull/4842 [#4844]: https://github.com/bytedance/deer-flow/pull/4844 [#4846]: https://github.com/bytedance/deer-flow/pull/4846 +[#4848]: https://github.com/bytedance/deer-flow/pull/4848 [#4852]: https://github.com/bytedance/deer-flow/pull/4852 [#4853]: https://github.com/bytedance/deer-flow/pull/4853 [#4860]: https://github.com/bytedance/deer-flow/pull/4860 [#4861]: https://github.com/bytedance/deer-flow/pull/4861 [#4863]: https://github.com/bytedance/deer-flow/pull/4863 [#4865]: https://github.com/bytedance/deer-flow/pull/4865 +[#4867]: https://github.com/bytedance/deer-flow/pull/4867 [#4868]: https://github.com/bytedance/deer-flow/pull/4868 +[#4876]: https://github.com/bytedance/deer-flow/pull/4876 [#4877]: https://github.com/bytedance/deer-flow/pull/4877 +[#4878]: https://github.com/bytedance/deer-flow/pull/4878 [#4882]: https://github.com/bytedance/deer-flow/pull/4882 +[#4884]: https://github.com/bytedance/deer-flow/pull/4884 [#4887]: https://github.com/bytedance/deer-flow/pull/4887 [#4888]: https://github.com/bytedance/deer-flow/pull/4888 +[#4892]: https://github.com/bytedance/deer-flow/pull/4892 [#4898]: https://github.com/bytedance/deer-flow/pull/4898 +[#4901]: https://github.com/bytedance/deer-flow/pull/4901 [#4903]: https://github.com/bytedance/deer-flow/pull/4903 [#4911]: https://github.com/bytedance/deer-flow/pull/4911 [#4918]: https://github.com/bytedance/deer-flow/pull/4918 +[#4921]: https://github.com/bytedance/deer-flow/pull/4921 [#4928]: https://github.com/bytedance/deer-flow/pull/4928 [#4933]: https://github.com/bytedance/deer-flow/pull/4933 [#4936]: https://github.com/bytedance/deer-flow/pull/4936 [#4938]: https://github.com/bytedance/deer-flow/pull/4938 +[#4944]: https://github.com/bytedance/deer-flow/pull/4944 +[#4946]: https://github.com/bytedance/deer-flow/pull/4946 [#4951]: https://github.com/bytedance/deer-flow/pull/4951 +[#4952]: https://github.com/bytedance/deer-flow/pull/4952 [#4953]: https://github.com/bytedance/deer-flow/pull/4953 +[#4955]: https://github.com/bytedance/deer-flow/pull/4955 [#4956]: https://github.com/bytedance/deer-flow/pull/4956 [#4959]: https://github.com/bytedance/deer-flow/pull/4959 [#4960]: https://github.com/bytedance/deer-flow/pull/4960 +[#4962]: https://github.com/bytedance/deer-flow/pull/4962 [#4963]: https://github.com/bytedance/deer-flow/pull/4963 [#4965]: https://github.com/bytedance/deer-flow/pull/4965 [#4970]: https://github.com/bytedance/deer-flow/pull/4970 -[#4983]: https://github.com/bytedance/deer-flow/pull/4983 -[#4987]: https://github.com/bytedance/deer-flow/pull/4987 -[#4998]: https://github.com/bytedance/deer-flow/pull/4998 -[#4696]: https://github.com/bytedance/deer-flow/pull/4696 -[#4810]: https://github.com/bytedance/deer-flow/pull/4810 -[#4820]: https://github.com/bytedance/deer-flow/pull/4820 -[#4826]: https://github.com/bytedance/deer-flow/pull/4826 -[#4834]: https://github.com/bytedance/deer-flow/pull/4834 -[#4839]: https://github.com/bytedance/deer-flow/pull/4839 -[#4867]: https://github.com/bytedance/deer-flow/pull/4867 -[#4876]: https://github.com/bytedance/deer-flow/pull/4876 -[#4878]: https://github.com/bytedance/deer-flow/pull/4878 -[#4946]: https://github.com/bytedance/deer-flow/pull/4946 -[#4955]: https://github.com/bytedance/deer-flow/pull/4955 [#4972]: https://github.com/bytedance/deer-flow/pull/4972 [#4977]: https://github.com/bytedance/deer-flow/pull/4977 +[#4980]: https://github.com/bytedance/deer-flow/pull/4980 +[#4983]: https://github.com/bytedance/deer-flow/pull/4983 [#4984]: https://github.com/bytedance/deer-flow/pull/4984 [#4986]: https://github.com/bytedance/deer-flow/pull/4986 +[#4987]: https://github.com/bytedance/deer-flow/pull/4987 +[#4995]: https://github.com/bytedance/deer-flow/pull/4995 +[#4998]: https://github.com/bytedance/deer-flow/pull/4998 [#5003]: https://github.com/bytedance/deer-flow/pull/5003 [#5006]: https://github.com/bytedance/deer-flow/pull/5006 [#5008]: https://github.com/bytedance/deer-flow/pull/5008 @@ -2803,7 +4030,9 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#5022]: https://github.com/bytedance/deer-flow/pull/5022 [#5023]: https://github.com/bytedance/deer-flow/pull/5023 [#5025]: https://github.com/bytedance/deer-flow/pull/5025 +[#5026]: https://github.com/bytedance/deer-flow/pull/5026 [#5027]: https://github.com/bytedance/deer-flow/pull/5027 +[#5028]: https://github.com/bytedance/deer-flow/pull/5028 [#5030]: https://github.com/bytedance/deer-flow/pull/5030 [#5031]: https://github.com/bytedance/deer-flow/pull/5031 [#5036]: https://github.com/bytedance/deer-flow/pull/5036 @@ -2817,6 +4046,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#5056]: https://github.com/bytedance/deer-flow/pull/5056 [#5057]: https://github.com/bytedance/deer-flow/pull/5057 [#5059]: https://github.com/bytedance/deer-flow/pull/5059 +[#5062]: https://github.com/bytedance/deer-flow/pull/5062 [#5064]: https://github.com/bytedance/deer-flow/pull/5064 [#5066]: https://github.com/bytedance/deer-flow/pull/5066 [#5069]: https://github.com/bytedance/deer-flow/pull/5069 @@ -2835,30 +4065,19 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#5103]: https://github.com/bytedance/deer-flow/pull/5103 [#5105]: https://github.com/bytedance/deer-flow/pull/5105 [#5109]: https://github.com/bytedance/deer-flow/pull/5109 -[#5112]: https://github.com/bytedance/deer-flow/pull/5112 -[#5117]: https://github.com/bytedance/deer-flow/pull/5117 -[#5133]: https://github.com/bytedance/deer-flow/pull/5133 -[#5136]: https://github.com/bytedance/deer-flow/pull/5136 -[#5239]: https://github.com/bytedance/deer-flow/pull/5239 -[#5119]: https://github.com/bytedance/deer-flow/pull/5119 -[#4726]: https://github.com/bytedance/deer-flow/pull/4726 -[#4729]: https://github.com/bytedance/deer-flow/pull/4729 -[#4884]: https://github.com/bytedance/deer-flow/pull/4884 -[#4901]: https://github.com/bytedance/deer-flow/pull/4901 -[#4944]: https://github.com/bytedance/deer-flow/pull/4944 -[#4952]: https://github.com/bytedance/deer-flow/pull/4952 -[#4980]: https://github.com/bytedance/deer-flow/pull/4980 -[#4995]: https://github.com/bytedance/deer-flow/pull/4995 -[#5026]: https://github.com/bytedance/deer-flow/pull/5026 -[#5028]: https://github.com/bytedance/deer-flow/pull/5028 -[#5062]: https://github.com/bytedance/deer-flow/pull/5062 [#5110]: https://github.com/bytedance/deer-flow/pull/5110 [#5111]: https://github.com/bytedance/deer-flow/pull/5111 +[#5112]: https://github.com/bytedance/deer-flow/pull/5112 +[#5117]: https://github.com/bytedance/deer-flow/pull/5117 +[#5119]: https://github.com/bytedance/deer-flow/pull/5119 [#5123]: https://github.com/bytedance/deer-flow/pull/5123 +[#5133]: https://github.com/bytedance/deer-flow/pull/5133 [#5134]: https://github.com/bytedance/deer-flow/pull/5134 +[#5136]: https://github.com/bytedance/deer-flow/pull/5136 [#5137]: https://github.com/bytedance/deer-flow/pull/5137 [#5141]: https://github.com/bytedance/deer-flow/pull/5141 [#5145]: https://github.com/bytedance/deer-flow/pull/5145 +[#5148]: https://github.com/bytedance/deer-flow/pull/5148 [#5149]: https://github.com/bytedance/deer-flow/pull/5149 [#5152]: https://github.com/bytedance/deer-flow/pull/5152 [#5153]: https://github.com/bytedance/deer-flow/pull/5153 @@ -2866,11 +4085,14 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#5155]: https://github.com/bytedance/deer-flow/pull/5155 [#5156]: https://github.com/bytedance/deer-flow/pull/5156 [#5159]: https://github.com/bytedance/deer-flow/pull/5159 +[#5162]: https://github.com/bytedance/deer-flow/pull/5162 [#5163]: https://github.com/bytedance/deer-flow/pull/5163 [#5164]: https://github.com/bytedance/deer-flow/pull/5164 [#5166]: https://github.com/bytedance/deer-flow/pull/5166 +[#5167]: https://github.com/bytedance/deer-flow/pull/5167 [#5168]: https://github.com/bytedance/deer-flow/pull/5168 [#5170]: https://github.com/bytedance/deer-flow/pull/5170 +[#5178]: https://github.com/bytedance/deer-flow/pull/5178 [#5181]: https://github.com/bytedance/deer-flow/pull/5181 [#5183]: https://github.com/bytedance/deer-flow/pull/5183 [#5185]: https://github.com/bytedance/deer-flow/pull/5185 @@ -2879,34 +4101,147 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#5197]: https://github.com/bytedance/deer-flow/pull/5197 [#5206]: https://github.com/bytedance/deer-flow/pull/5206 [#5209]: https://github.com/bytedance/deer-flow/pull/5209 +[#5214]: https://github.com/bytedance/deer-flow/pull/5214 +[#5216]: https://github.com/bytedance/deer-flow/pull/5216 +[#5217]: https://github.com/bytedance/deer-flow/pull/5217 [#5219]: https://github.com/bytedance/deer-flow/pull/5219 +[#5224]: https://github.com/bytedance/deer-flow/pull/5224 +[#5225]: https://github.com/bytedance/deer-flow/pull/5225 +[#5227]: https://github.com/bytedance/deer-flow/pull/5227 [#5228]: https://github.com/bytedance/deer-flow/pull/5228 [#5232]: https://github.com/bytedance/deer-flow/pull/5232 [#5234]: https://github.com/bytedance/deer-flow/pull/5234 [#5236]: https://github.com/bytedance/deer-flow/pull/5236 +[#5239]: https://github.com/bytedance/deer-flow/pull/5239 [#5244]: https://github.com/bytedance/deer-flow/pull/5244 [#5245]: https://github.com/bytedance/deer-flow/pull/5245 +[#5247]: https://github.com/bytedance/deer-flow/pull/5247 +[#5249]: https://github.com/bytedance/deer-flow/pull/5249 +[#5254]: https://github.com/bytedance/deer-flow/pull/5254 +[#5255]: https://github.com/bytedance/deer-flow/pull/5255 [#5261]: https://github.com/bytedance/deer-flow/pull/5261 +[#5264]: https://github.com/bytedance/deer-flow/pull/5264 [#5265]: https://github.com/bytedance/deer-flow/pull/5265 +[#5275]: https://github.com/bytedance/deer-flow/pull/5275 [#5278]: https://github.com/bytedance/deer-flow/pull/5278 [#5280]: https://github.com/bytedance/deer-flow/pull/5280 [#5281]: https://github.com/bytedance/deer-flow/pull/5281 [#5282]: https://github.com/bytedance/deer-flow/pull/5282 +[#5283]: https://github.com/bytedance/deer-flow/pull/5283 [#5284]: https://github.com/bytedance/deer-flow/pull/5284 +[#5286]: https://github.com/bytedance/deer-flow/pull/5286 [#5287]: https://github.com/bytedance/deer-flow/pull/5287 +[#5288]: https://github.com/bytedance/deer-flow/pull/5288 +[#5289]: https://github.com/bytedance/deer-flow/pull/5289 +[#5291]: https://github.com/bytedance/deer-flow/pull/5291 +[#5293]: https://github.com/bytedance/deer-flow/pull/5293 +[#5294]: https://github.com/bytedance/deer-flow/pull/5294 +[#5296]: https://github.com/bytedance/deer-flow/pull/5296 +[#5299]: https://github.com/bytedance/deer-flow/pull/5299 +[#5304]: https://github.com/bytedance/deer-flow/pull/5304 +[#5305]: https://github.com/bytedance/deer-flow/pull/5305 +[#5306]: https://github.com/bytedance/deer-flow/pull/5306 +[#5309]: https://github.com/bytedance/deer-flow/pull/5309 +[#5310]: https://github.com/bytedance/deer-flow/pull/5310 +[#5312]: https://github.com/bytedance/deer-flow/pull/5312 +[#5315]: https://github.com/bytedance/deer-flow/pull/5315 +[#5316]: https://github.com/bytedance/deer-flow/pull/5316 +[#5318]: https://github.com/bytedance/deer-flow/pull/5318 [#5321]: https://github.com/bytedance/deer-flow/pull/5321 +[#5323]: https://github.com/bytedance/deer-flow/pull/5323 +[#5324]: https://github.com/bytedance/deer-flow/pull/5324 +[#5326]: https://github.com/bytedance/deer-flow/pull/5326 +[#5329]: https://github.com/bytedance/deer-flow/pull/5329 +[#5332]: https://github.com/bytedance/deer-flow/pull/5332 [#5338]: https://github.com/bytedance/deer-flow/pull/5338 +[#5341]: https://github.com/bytedance/deer-flow/pull/5341 +[#5344]: https://github.com/bytedance/deer-flow/pull/5344 +[#5347]: https://github.com/bytedance/deer-flow/pull/5347 +[#5350]: https://github.com/bytedance/deer-flow/pull/5350 [#5353]: https://github.com/bytedance/deer-flow/pull/5353 [#5357]: https://github.com/bytedance/deer-flow/pull/5357 +[#5359]: https://github.com/bytedance/deer-flow/pull/5359 +[#5363]: https://github.com/bytedance/deer-flow/pull/5363 +[#5367]: https://github.com/bytedance/deer-flow/pull/5367 +[#5369]: https://github.com/bytedance/deer-flow/pull/5369 +[#5371]: https://github.com/bytedance/deer-flow/pull/5371 +[#5373]: https://github.com/bytedance/deer-flow/pull/5373 +[#5374]: https://github.com/bytedance/deer-flow/pull/5374 [#5375]: https://github.com/bytedance/deer-flow/pull/5375 +[#5377]: https://github.com/bytedance/deer-flow/pull/5377 +[#5380]: https://github.com/bytedance/deer-flow/pull/5380 +[#5381]: https://github.com/bytedance/deer-flow/pull/5381 +[#5382]: https://github.com/bytedance/deer-flow/pull/5382 +[#5384]: https://github.com/bytedance/deer-flow/pull/5384 +[#5388]: https://github.com/bytedance/deer-flow/pull/5388 +[#5389]: https://github.com/bytedance/deer-flow/pull/5389 +[#5390]: https://github.com/bytedance/deer-flow/pull/5390 +[#5392]: https://github.com/bytedance/deer-flow/pull/5392 [#5393]: https://github.com/bytedance/deer-flow/pull/5393 +[#5395]: https://github.com/bytedance/deer-flow/pull/5395 +[#5396]: https://github.com/bytedance/deer-flow/pull/5396 +[#5397]: https://github.com/bytedance/deer-flow/pull/5397 +[#5399]: https://github.com/bytedance/deer-flow/pull/5399 [#5401]: https://github.com/bytedance/deer-flow/pull/5401 +[#5402]: https://github.com/bytedance/deer-flow/pull/5402 [#5403]: https://github.com/bytedance/deer-flow/pull/5403 +[#5404]: https://github.com/bytedance/deer-flow/pull/5404 +[#5405]: https://github.com/bytedance/deer-flow/pull/5405 +[#5406]: https://github.com/bytedance/deer-flow/pull/5406 +[#5407]: https://github.com/bytedance/deer-flow/pull/5407 +[#5408]: https://github.com/bytedance/deer-flow/pull/5408 +[#5410]: https://github.com/bytedance/deer-flow/pull/5410 [#5411]: https://github.com/bytedance/deer-flow/pull/5411 +[#5413]: https://github.com/bytedance/deer-flow/pull/5413 +[#5415]: https://github.com/bytedance/deer-flow/pull/5415 +[#5416]: https://github.com/bytedance/deer-flow/pull/5416 [#5418]: https://github.com/bytedance/deer-flow/pull/5418 [#5419]: https://github.com/bytedance/deer-flow/pull/5419 +[#5421]: https://github.com/bytedance/deer-flow/pull/5421 +[#5422]: https://github.com/bytedance/deer-flow/pull/5422 +[#5424]: https://github.com/bytedance/deer-flow/pull/5424 +[#5426]: https://github.com/bytedance/deer-flow/pull/5426 [#5427]: https://github.com/bytedance/deer-flow/pull/5427 +[#5428]: https://github.com/bytedance/deer-flow/pull/5428 +[#5429]: https://github.com/bytedance/deer-flow/pull/5429 [#5431]: https://github.com/bytedance/deer-flow/pull/5431 +[#5432]: https://github.com/bytedance/deer-flow/pull/5432 +[#5433]: https://github.com/bytedance/deer-flow/pull/5433 +[#5436]: https://github.com/bytedance/deer-flow/pull/5436 +[#5439]: https://github.com/bytedance/deer-flow/pull/5439 +[#5440]: https://github.com/bytedance/deer-flow/pull/5440 +[#5441]: https://github.com/bytedance/deer-flow/pull/5441 +[#5442]: https://github.com/bytedance/deer-flow/pull/5442 +[#5443]: https://github.com/bytedance/deer-flow/pull/5443 +[#5444]: https://github.com/bytedance/deer-flow/pull/5444 +[#5446]: https://github.com/bytedance/deer-flow/pull/5446 [#5447]: https://github.com/bytedance/deer-flow/pull/5447 +[#5448]: https://github.com/bytedance/deer-flow/pull/5448 +[#5449]: https://github.com/bytedance/deer-flow/pull/5449 +[#5451]: https://github.com/bytedance/deer-flow/pull/5451 +[#5453]: https://github.com/bytedance/deer-flow/pull/5453 +[#5454]: https://github.com/bytedance/deer-flow/pull/5454 +[#5455]: https://github.com/bytedance/deer-flow/pull/5455 +[#5456]: https://github.com/bytedance/deer-flow/pull/5456 +[#5458]: https://github.com/bytedance/deer-flow/pull/5458 +[#5459]: https://github.com/bytedance/deer-flow/pull/5459 +[#5461]: https://github.com/bytedance/deer-flow/pull/5461 +[#5463]: https://github.com/bytedance/deer-flow/pull/5463 +[#5465]: https://github.com/bytedance/deer-flow/pull/5465 +[#5467]: https://github.com/bytedance/deer-flow/pull/5467 +[#5468]: https://github.com/bytedance/deer-flow/pull/5468 [#5469]: https://github.com/bytedance/deer-flow/pull/5469 - +[#5470]: https://github.com/bytedance/deer-flow/pull/5470 +[#5474]: https://github.com/bytedance/deer-flow/pull/5474 +[#5477]: https://github.com/bytedance/deer-flow/pull/5477 +[#5478]: https://github.com/bytedance/deer-flow/pull/5478 +[#5479]: https://github.com/bytedance/deer-flow/pull/5479 +[#5480]: https://github.com/bytedance/deer-flow/pull/5480 +[#5483]: https://github.com/bytedance/deer-flow/pull/5483 +[#5485]: https://github.com/bytedance/deer-flow/pull/5485 +[#5486]: https://github.com/bytedance/deer-flow/pull/5486 +[#5488]: https://github.com/bytedance/deer-flow/pull/5488 +[#5492]: https://github.com/bytedance/deer-flow/pull/5492 +[#5496]: https://github.com/bytedance/deer-flow/pull/5496 +[#5501]: https://github.com/bytedance/deer-flow/pull/5501 +[#5504]: https://github.com/bytedance/deer-flow/pull/5504 diff --git a/CHANGELOG_zh.md b/CHANGELOG_zh.md index 7826f5223..fe74d954b 100644 --- a/CHANGELOG_zh.md +++ b/CHANGELOG_zh.md @@ -10,6 +10,7 @@ ## [未发布] 本节累积面向 **2.1.0** 里程碑([里程碑 2](https://github.com/bytedance/deer-flow/milestone/2))的工作。 +该里程碑随本次发布收尾,共合并 **765 个 pull request**。 ### ⚠ 不兼容变更(Breaking Changes) @@ -77,9 +78,35 @@ ### 新增 #### 调度器 -- **调度器:** 定时任务在 `once` 和 `cron` 之外新增 `interval` - (`schedule_spec.every_seconds`)。节奏为 UTC 的 `now + N`,不补跑错过的节拍。 - N 不小于 `scheduler.min_once_delay_seconds`(默认 60 秒),不大于 30 天。 +- **scheduler:** 定时任务在 `once` 和 `cron` 之外新增 `interval` + (`schedule_spec.every_seconds`)。节奏为 UTC 的 `now + N`, + 不补跑错过的节拍。N 不小于 `scheduler.min_once_delay_seconds` + (默认 60 秒),不大于 30 天。([#5291]) + +- **scheduled-tasks:** 保存任务前即可查看即将到来的 cron 触发时刻。 + `POST /api/scheduled-tasks/preview-cron` 接受五段式 cron 表达式、时区、1–10 + 的 `count`(默认 5)与一个可选的带时区(aware)参考时间,返回规范化后的 + cron、生效的 UTC 参考时间,以及 UTC 与本地时间下的触发时刻。 + 它在工作线程中运行调度器自己的计算器,因此 DST 处理与保存后的任务实际 + 行为一致;它不预留任何资源,也不触碰任何 task、thread 或 run 存储;输入 + 非法与日期范围失败都会返回 422。([#5381]) + +- **scheduled-tasks:** 可以在服务端按触发状态过滤 run 历史。过去要找一个罕见 + 的失败,只能把每一页历史都下载下来在客户端过滤,因为埋在大量较新成功记录 + 后面的失败可能出现在任何一页;`GET /api/scheduled-tasks/{task_id}/runs` + 现在接受 `?status=`,取值有 `queued`、`launching`、`running`、`success`、 + `failed`、`skipped` 与 `interrupted`,并在分页之前应用该谓词,因此 + `?status=failed&limit=50&offset=0` 返回的就是第一页真正的失败记录。未知 + 取值(包括父任务自身的 `completed`)返回 422,空结果返回 `[]`, + 未过滤时的数组响应保持不变。([#5384]) + +- **scheduled-tasks:** 定时任务详情面板可以向后翻页浏览执行记录。此前它只显示 + 最新 50 条,尽管端点早已支持 limit 与 offset,更早的记录在 UI 上无法访问。 + “较新记录”、“更早记录”与“最新记录”导航现在会在历史中翻页, + 并带有本地化的页面、加载、错误与重试状态;每次请求都会多取一行来判断 + 是否存在更早的页面,而不是凭空编造一个总数;最后一页不满时会禁用“更早 + 记录”,切换任务会重置到最新页并取消过期的请求,使迟到的结果不会替换新 + 任务的历史;且只有最新页会轮询。([#5363]) #### 认证 - **认证:** 新增用于程序化 API 访问的个人访问令牌(PAT): @@ -92,7 +119,26 @@ ,无需重启 Gateway——这对多名用户共享同一出口 IP 的企业代理 / NAT 部署是解锁 通道。默认值不变。([#5110]) +- **settings:**账户偏好可以跨浏览器清理或设备更换保留。通知开关、默认模型、对 + 话模式与推理力度以独立的 `(user_id, key)` 行存储,位于需会话认证的 + `GET`/`PATCH /api/v1/auth/preferences` 之后,并在首帧渲染前从服务端拉取,同 + 时配备标签页本地的发件箱与有上限的退避重试,因此失败的写入不会被静默丢弃; + `null` 会重置某个字段。只有这四个白名单字段会被传输——设备通知权限、显示设置 + 与线程级模型覆盖都保留在本地,自动的输入框回退也不会作为显式账户偏好上传。现 + 有的无作用域本地设置不会被迁移,因为它们没有已知归属,因此用户升级后需要重新 + 选择这四项。([#5397]) + #### 智能体与运行时 +- **scheduler:**定时任务可以把 `assistant_id` 固定为 `lead_agent`(默认)或创 + 建者已有的自定义智能体。未知或格式错误的名称返回 422。工作区的创建/编辑表单 + 提供同样的选择。([#5286], [#5288]) + +- **gateway:**`GET /api/threads/{thread_id}/runs/page` 以 + `(created_at, run_id)` 键集游标翻页遍历线程 run 历史( + `{data, has_more, next_before_created_at, next_before_run_id}`)。 + `GET /api/threads/{thread_id}/runs` 仍返回仅含最新 100 条 run 的裸数组,以便 + LangGraph SDK 客户端继续工作。([#5282], [#5283]) + - **中间件:** 新增 `TokenBudgetMiddleware`,强制单个 run 的 token 预算,在主智 能体与子智能体之间累加共享。([#3412]) - **中间件:** 结构化的工具结果元数据与工具进度状态机,让运行时对多步工具流拥有 @@ -151,6 +197,127 @@ `uploaded_files` 边界会注入子智能体的图状态,使 `list_uploaded_files` 可参与 正常的工具策略过滤(持久 `batch_task` worker 保持禁用)。([#5170]) +- **agents:**读取先于写入的拦截现在会把被阻止的 `write_file` / `str_replace` + 调用中无用的负载(`content`、`old_str`、`new_str`)从发往模型的请求中剔除。 + 被阻止的调用从未执行,必须在重新读取后重新发出,因此原始参数只会占用上下文; + 已存储的历史、回执与 run 日志都会保留它们。被阻止的结果会与调用出现位置配对 + (工具调用 id 可能跨回合重复),而历史被重写的请求会丢弃 OpenAI 的 `resp_` + 响应 id,使 `use_previous_response_id` 链无法恢复原始的服务端历史。由 + `read_before_write.elide_blocked_payloads`(默认开启)与 + `read_before_write.elide_min_chars`(默认 2000)控制。([#5329]) + +- **agents:**`ToolOutputBudgetMiddleware` 现在还会在对话稍后又读取或修改了同 + 一路径时,把成功的 `write_file` 调用的 `content` 从发往模型的请求中剔除。写 + 入成功后磁盘上的文件才是事实来源,而读取先于写入的拦截会强制在下一次修改前 + `read_file`,因此历史副本是冗余的,长篇的报告撰写 run 会把每个章节携带两遍。 + 最新的 `tool_output.keep_recent_writes` 次成功写入(默认 1)始终可见, + `str_replace` 的负载从不改动,已存储的历史、回执与 run 日志都会保留原始参数。 + 由 `tool_output.elide_superseded_writes`(默认开启)与 + `tool_output.superseded_write_min_chars`(默认 2000)控制。([#5374]) + +- **agents:**自定义智能体的 `config.yaml` 接受 `memory_enabled: false`,以把 + 该智能体作为无状态的执行 worker 运行;省略时默认为 `true`。该退出选项只把该 + 智能体从记忆生命周期中移除——召回记忆注入、被动捕获、 + `memory_search` / `memory_add` / `memory_update` / `memory_delete` 工具以及 + 工具模式的记忆指引全部被抑制,自动摘要与手动 `/compact` 也会跳过持久记忆刷写。 + 关闭一个已有智能体只清除 checkpoint 状态中被冻结的、由服务端标记的记忆提醒: + 日期提醒、真实的用户消息与未标记的相似内容都会保留,其他每个智能体的全局记忆 + 设置都不受影响。([#5167]) + +- **runtime:**`ToolProgressMiddleware` 的干预现在可在持久的 run 事件流中审计。 + 每次生效的 `warn`、`block`、`recover` 或更晚调用的 `reset` 阶段转换都会持久 + 化一条新的 `middleware:tool_progress` 事件;此前这些重规划提示、工具状态提升 + 与短路都消失在进程日志中,因此持久化的 run 无法显示模型是自行恢复的还是由运 + 行时护栏干预的。只记录有界的决策元数据——状态与恢复词汇表来自规范的 + `tool_result_meta` schema,工具参数、结果内容、提示词以及由内容派生的哈希从 + 不进入事件。子智能体与其他 recorder 归属键在 Gateway 与嵌入式 worker 边界处 + 都由服务端持有,因此调用方无法伪造持久归属。事件只在 ToolProgress 启用时出现, + 而它默认仍处于关闭状态。([#5214]) + +- **subagents:**持久化的 `batch_task` 条目现在接受与普通 `task` 委派相同的可 + 选 `acceptance_criteria`,因此批处理 worker 无法再声称某个缺失的交付物存在却 + 仍作为成功条目落地、且没有任何记录在案的检查。判据在持久化前会被规范化(20 + 条可用条目、中性化后 500 个字符;空值变为 `null`),并在租约恢复后保留;已完 + 成的执行会复用现有的确定性检查器,对照归属方作用域内的线程文件与已记录的测试 + 命令证据。条目查询与 JSONL 导出新增可空的 `acceptance_criteria` 和单独校验的 + `acceptance_verdict`(`holds`、`does not hold` 或 `UNVERIFIED`):一个已完 + 成、带有报告、缺少 CSV 且含不受支持的质量声明的条目会保持 `succeeded`,并带 + 上这三个叶子,也不会被自动重试。没有判据的条目与遗留行不报告任何结论。 + ([#5289]) + +- **subagents:**`task(context_mode="snapshot")` 让委派可以携带派发时刻所保留 + 的父对话副本,而不只是委派的提示词。快照模式在委派校验之后、子项设置之前捕获 + 对话内容与 `summary_text`,因此被拒绝的委派不会序列化任何内容,之后父对话的 + 编辑也不会传到子项。它被渲染为任务之前一条独立的历史 `HumanMessage`,保留纯 + 文本、文本块、带匹配结果的历史工具调用描述以及可序列化的媒体输入;不可序列化 + 的媒体会变成一条显式的省略声明,而父级系统消息、隐藏的框架状态(包括注入的记 + 忆与 todo 状态)、推理块、执行元数据与待处理的工具调用都被排除。子项保留自己 + 的角色、模型、工具与技能限制,父级工具帧从不进入它的执行历史,因此父级操作无 + 法填充子项的回执、执行步骤或 bash 证据。`context_mode="isolated"` 仍是默认值, + `batch_task` 条目仍要求自包含的提示词。([#5367]) + +- **context:**可选的任务笔记与压缩历史召回,由 `task_continuity.enabled`(默 + 认 `false`;配置 schema 新增该小节并带禁用默认值)控制。启用后,`task_note`、 + `history_search` 与 `history_read` 会在现有授权与技能策略下对标准主智能体和 + `DeerFlowClient` 暴露,并同时支持同步与异步工具执行。`task_note` 在共享状态 + 通道中保存关于约束、决策、失败尝试与下一步的简短检查点笔记,每次写入都强制执 + 行笔记本容量限制与报告形态,并在持久化上下文的人类消息中渲染为已转义的历史数 + 据。自动与手动压缩还会把有界的消息和工具文本——包括真正的隐藏澄清卡片答案——保 + 留到沙箱挂载之外的 user/thread 本地 SQLite FTS5 归档中,随后由 + `history_search` 与 `history_read` 查询;格式错误的 checkpoint 元数据会报告 + 为不可用,而不会中断模型调用或压缩,存储故障也不会影响普通压缩。这是单一任务 + 内的字面召回:它不会恢复 run、跨主机复制归档,也不会创建跨线程记忆,且不引入 + 任何 embedding 依赖。([#5382]) + +- **gateway:**Gateway 的 run 不再硬编码 100 的递归上限。那些常规任务需要更长 + 智能体循环的部署此前必须让每个 API 调用方都提供请求级覆盖,而未这样做的调用 + 方会在本来合法的工作上撞到 `GraphRecursionError`。现在一个可热重载的顶层 + `recursion_limit` 会在请求未指定时提供默认值(100,以保持向后兼容),显式的 + 请求值仍然优先,无效的请求值回退到配置的默认值,而 `max_recursion_limit` 会 + 同时限制已配置与客户端提供的值。([#5390]) + +- **gateway:**Gateway run 可以被要求读取一段指定的更早对话。可选的 + `read_conversation` 加上 `conversation_references` run 字段(最多三个线程 + id 或同源的聊天 URL)会授予主智能体对上一段对话的面向模型读取权限,同时继续 + 当前任务;普通消息文本从不授予访问权,该授权绑定到该次 run 的引用、该 run 的 + `runs:read` 权限以及每个来源的归属。读取复用现有的转录分页与可见性规则,返 + 回有界的用户与助手文本,并带上来源 id、续接游标以及截断或不可用声明,同时排 + 除隐藏上下文、推理块与原始工具结果。读取器从不持久化到配置中,并在 run 结束 + 时释放,因此恢复与重放需要再次提供引用;bootstrap、子智能体与普通嵌入式路径 + 都不会获得它。文本上限为每条消息 4000 字符、每页 20000 字符。([#5399]) + +- **gateway:**对话引用现在可以由 SDK 客户端发送,并被它们检测。 + LangGraph JS SDK 的 `RunsClient.stream` 会丢弃未知的顶层字段,因此浏览器客户 + 端此前根本无法发送 `conversation_references` 字段,也没有办法判断读取器是否 + 启用——在关闭时无从隐藏入口。运行创建、流式与等待请求现在都接受 + `context.conversation_references`,限制相同(最多三个,每个 1–2048 字符), + 错误位置也相同,在校验前被提升为规范字段并从 `context` 中移除,因此它不会到 + 达 run 上下文或被 checkpoint 的 configurable;同时发送两种写法会返回 422,而 + 留在 `body.config` 中的副本仍不授予任何权限。`GET /api/features` 报告 + `conversation_references: {enabled, max_references}`,其中 `enabled` 遵循已 + 配置的工具列表,因此 `config.yaml` 的改动无需重启即可生效。([#5463]) + +- **tools:**`list_uploaded_files` 接受可选的 `query`(大小写不敏感的文件名子 + 串)与 `extensions`(`"pdf"` 或 `".PDF"`,两者都表示 `.pdf`),且两个过滤器 + 都运行在 20 文件截断**之前**——而懒加载历史早已应用了该截断。此前上限作用于未 + 过滤的按 mtime 排序的列表,因此在一个满是较新截图的线程里说“分析我之前上传的 + 那些 PDF”会把 PDF 推进 `omitted_summary`,让智能体没有路径可交给 `read_file`; + 现在 `total_count`、`truncated` 与 `omitted_summary` 描述的是过滤后的集合。 + 省略两个参数会保持当前行为,两个过滤器以 AND 组合,空值或无效值视为不过滤, + 过滤后无匹配会返回 `No uploaded files matched the given filters.`。([#5341]) + +- **agents:** 自定义智能体此前在 Gallery、聊天头部与欢迎页显示仅含 ASCII 的 + 存储标识符,因此所有者无法用中文或其他非 ASCII 文字为智能体命名。每个 + 智能体现在可以在其配置文档中,以及 create/update/read/list 各 API 中,携带 + 一个可选的 `display_name`——去除首尾空白、至多 100 个字符; + 更新时省略该字段会保留原值,`null` 或空值则将其清除。该标签从智能体 + Gallery 的设置按钮进入编辑,按 Unicode 码位计数,并受一个可见的字符预算 + 约束;若包含控制字符、不可见的格式字符,或仅由记号(marks)、分隔符 + (separators)与格式字符(format characters)构成,则会被拒绝;普通多语言 + 文字与 ZWJ 表情仍被允许。路径、URL、运行时的 `agent_name`、归属与 React + 组件身份仍使用稳定标识符,因此无需迁移任何数据;存储中无效的取值在读取时 + 会被忽略,而不会破坏 list/detail/bootstrap。([#5324]) + #### 记忆 - **记忆:** 记忆合并(consolidation)合成碎片化的事实,并通过 LLM 为每条事实分 配的 `expected_valid_days` / `staleFactsToExtend` 进行过期审查,剪除静默过期的 @@ -168,7 +335,22 @@ 记忆 provider 的 Honcho。([#4509]、[#4528]、[#4730]) - **记忆:** 混合事实淘汰策略综合多种信号,决定容量满时应丢弃哪些事实。([#4789]) +- **memory:** 可选的写入侧防护,避免把已有事实的改写再次存入记忆,因此重复的 + 抽取不再以重复内容占用记忆容量与 prompt token。写入侧开关是 + `memory.backend_config.fact_dedup_enabled`(默认 `false`),另有 + `memory.backend_config.fact_dedup_similarity_threshold`(默认 `0.7`,取值 + 范围 `0.5`–`1.0`)。在当前 user/agent 作用域内, + 新的同类事实会与既有事实以有界 token-Jaccard 相似度比较——对拉丁词与中文 + 二元组(bigram)做词法启发式,而非语义等价判定。合并会保留既有事实的 id、 + content 与创建时间,把 `confidence` 提升到两者中较高的值, + 时刷新 `source`;`facts_merged_dedup` 计数器记录发生了多少次合并。 + 成对的更正替换会绕过近似去重,移除提议会把自己的目标排除在匹配之外, + 且不会伪造任何确认信号——强化仍然需要真实的人类消息。([#5254]) + #### 技能 +- **skills:** 内置的图像生成技能现在可以使用兼容 OpenAI 的 Images API + 进行生成与参考图编辑,端点、模型、尺寸与输出格式均可配置。([#5389]) + - **技能:** 原生 SkillScan(阶段一)在加载时静态分析技能包;`describe_skill` 支 持延迟发现,模型按需获取技能 schema,而非一开始就加载全部技能。([#3033]、[#3775]) - **技能:** 按用户的自定义技能隔离,并配合沙箱挂载。([#3889]) @@ -179,6 +361,29 @@ - **技能:** 播客生成的火山引擎音色可按说话人性别覆盖配置,默认值经过修剪、对空值 安全。([#5156]) +- **skills:** 可以从设置页导出自定义技能,包括已禁用的那些,因此备份、在多 + 个安装之间搬迁或发给协作者的,都是实际安装的版本,而非原始上传的版本。 + 双语预览会列出包大小、分页的文件路径、声明的依赖要求与可操作的阻塞项, + 两个新增的仅管理员 manifest 与 download 端点会将该预览 + 绑定到一个修订摘要——期间被改动的包会返回 `409`,而不是给出 + 过期的 ZIP。支持文件、空目录与规范化后的可执行权限能在往返中保留, + 而链接、特殊文件、不可移植的路径、嵌套的技能根与可执行二进制会被拒绝。 + 导出复制的是原始保存的文件,不是脱敏步骤:它既不扫描密钥,也不 + 运行技能脚本,因此凭据与账号设置仍需单独配置。([#5332]) + +- **skills:** 延迟技能发现现在会对候选排序,而不是把模型的整段自由文本查询 + 当作一个正则表达式去匹配。设置 `skills.deferred_discovery: true` 后, + 智能体只能看到技能名称,并调用 `describe_skill` 来选择要加载的内容; + 但多词意图此前必须连续匹配:`chart visualization` 找不到 + `chart-visualization`,`analyze Python` 也找不到被描述为 + “Analyze data with Python, pandas, jupyter” 的技能,因此智能体无法加载 + 匹配的工作流及其生效的工具策略。 + 现在查找会规范化 Unicode、大小写与名称分隔符,并按字面意图词的覆盖率 + 给候选打分,覆盖率相同时名称匹配优先于仅描述匹配,并以目录顺序 + 打破平局,使结果保持可复现;模型生成的查询限制为 256 个字符与 16 个 + 不同的词。`select:` 精确选择与 `+required` 名称过滤保持不变,且不涉及 + 任何嵌入、模型调用或遥测。([#5369]) + #### 模型与集成 - **社区工具:** 新增网络检索 / 抓取引擎——GroundRoute、Crawl4AI(`web_fetch`)与 fastCRW provider——并新增 Browserless `web_capture` 截图工具和 Brave `image_search` @@ -213,6 +418,29 @@ 排序后的 chunk 格式化为带引用编号的文本,可选的 `X-API-Key` 在所有模型可见路径 中脱敏,服务端错误消息会映射为可操作的工具错误。([#5209]) +- **models:** 新增按用户区分的收藏层,让常用模型触手可及,且不会改变当前选中 + 或默认的模型。紧凑的两行模型列表现在在主聊天与 Side Chat(侧边对话)中都是 + 锚定式下拉,每行带一个行内星标;收藏会排进第一组,但不会选中任何模型, + 也不会关闭选择器,并按已登录用户持久化,支持跨标签页同步,以及恢复暂时 + 不可用的条目。原有的搜索框被移除,这是有意的简化,且没有单独的管理模式。 + ([#5441]) + +- **models:**有界并发并不限制请求速率:多个并发 run 仍可能耗尽某个 provider + 的每分钟请求配额,而分发前没有任何机制来对需求限速,因此 429 才是出错的第一 + 信号。新增可选的 `models[].request_admission` 配置块,通过进程本地的队列放行 + 模型调用,接受 `requests_per_minute`(必填)、可选的 `group`(默认为模型配置 + 名)、`max_wait_seconds`(默认 `300`)与 `max_queue_size`(默认 `256`);在 + 60 RPM 下放行至少间隔一秒,而空闲期不会积累突发额度。同一个有界 FIFO 由同步 + 调用方、独立的 asyncio 事件循环以及显式组内的每个模型实例共享,它们的策略必 + 须一致——冲突的设置会在构造时失败并需要重启。取消与超时会丢弃一个等待者而不消 + 耗一次放行,队列已满则会在分发前失败。该限制器通过 LangChain 的 + `BaseChatModel` 钩子挂载,因此智能体模型、invoke 与 stream 路径以及工厂创建 + 的辅助模型都在覆盖范围内;工厂会把策略从 provider 参数中剥离,并把暴露给 + SDK 的 `max_retries` 设为零,使重试无法悄悄绕过钩子——现有的智能体中间件重试 + 会重新进入放行队列,而非中间件调用方则会失去 SDK 重试。预算按进程计算,因此 + 运维方必须把账户配额在 worker、副本与其他客户端之间划分;限制器统计的是请求 + 而非 token,未配置的模型行为与之前完全一致。([#5432]) + #### MCP - **MCP:** 新增持久任务运行时:长时工具任务通过持久 driver 跨 Gateway 重启继续, 进度与完成通知显示在聊天 UI 中。([#4665]、[#4690]、[#4833]) @@ -226,6 +454,11 @@ - **MCP:** 共享 HTTP/SSE server 可通过 `headers_from_context` 将请求作用域的 secret 映射为 header:调用方在 `config.context.secrets` 中提供每次请求的值, 配置只存储键名,缺失值默认拒绝。([#5010]) +- **mcp:**`extensions_config.example.json` 中新增一个可选的 + `parallel-search` server 条目(`https://search.parallel.ai/mcp`,HTTP,默认 + 无认证),默认禁用;启用后会暴露 `parallel-search_web_search` 与 + `parallel-search_web_fetch`,并文档化可选的 Bearer 认证。([#5028], [#5501]) + - **MCP:** `extensions_config.example.json` 新增可选的 `parallel-search` server 条目(`https://search.parallel.ai/mcp`,HTTP,默认无鉴权),默认禁用;启用后会 暴露 `parallel-search_web_search` 与 `parallel-search_web_fetch`,并文档化可选 @@ -263,6 +496,14 @@ 读取认证中间件在每个已认证请求上标记的 `AuthContext`——不产生额外的 provider 评估——使前端能隐藏调用方角色无法执行的操作。([#5228]) +- **authz:**被拒绝 `threads:delete` 或 `runs:cancel` 的角色此前仍会看到线程行 + 的删除菜单项、侧边删除按钮与一个可用的输入框停止按钮,于是 UI 提供了一个 + Gateway 的 `@require_permission` 守卫只会拒绝的操作。前端现在消费 + `GET /auth/me` 报告的生效权限并隐藏删除入口,并在流式输出时禁用停止按钮,同 + 时用 `aria-label`/`title` 标明权限边界。强制逻辑没有变化——Gateway 守卫仍是唯 + 一决策点——而缺失、为 `null` 或尚未加载的权限列表会被当作宽松处理,因此新旧后 + 端混布的部署绝不会隐藏调用方仍可执行的操作。([#5294]) + #### 沙箱与 provisioner - **沙箱:** 新增 E2B 与 BoxLite(micro-VM)沙箱 provider;BoxLite 自带预热池。([#3883] 、[#3940]、[#3951]) @@ -293,6 +534,36 @@ 和工具的 MCP 来源。`deerflow-extension-api` 升至 0.2.0,0.1 扩展会在启动时被拒绝。 ([#4863]) +- **extensions:**`extensions.middlewares` 条目除了现有的 + `module.path:ClassName` 字符串外,还可以写成 `{class, kwargs}`,`kwargs` 会 + 传给构造函数。需要设置阈值、请求头名称或任何其他参数的运维方管理中间件,不再 + 必须为了设定一个值而硬编码一个子类。字符串条目仍以无参数构造,空的类路径会在 + 配置校验时被拒绝,而不是留到智能体创建时才失败,像 `apply_to` 这样的未知字段 + 也会被拒绝。([#5312]) + +- **extensions:**`deerflow extensions upgrade SOURCE`(也以 + `make extension-upgrade SOURCE=...` 暴露)会替换一个受管理的本地快照,或为 + 已在 `extensions` 组中的依赖重新固定版本,并沿用现有的 `plugins:` 记录,使其 + 私有的 `config` 与 `required` 得以保留。迁移到更新的固定版本此前意味着先 + `remove` 再 `install`,而 `remove` 会删除整条记录——包括密钥——而已经快照过的 + 本地目录若不手工编辑受管理的副本根本无法重新安装。普通的 `install` 没有变化, + 仍会拒绝已存在的本地快照;升级未安装的来源会以安装提示的方式失败关闭;失败的 + 升级会恢复先前的快照,即使并发的 `pyproject.toml` / `uv.lock` 编辑阻止了依赖 + 文件回滚。([#5347]) + +- **extensions:** 新增可选的 `RunEvidenceReader` 契约,让 Gateway 扩展可以 + 直接得知自上次查看以来发生了什么变化,而不必再反复对齐整个 thread:由宿主 + 持有的单调递增 cursor 持久地发现 changed-run,另有按 run 限定范围的事件分 + 页,以及具备权威性的状态读取。cursor 不透明、带版本、绑定作用域、可安全重 + 放,对使用数据库支撑的存储而言还是持久的;分页按 `(change_seq, run_id)` 排 + 序,而不是从 thread 作用域的事件序号或时间戳推断全局顺序;进度快照与租约心 + 跳不会推进这个时钟;删除操作不产生 tombstone,因此消费方需要轮询状态,并把 + 缺失的 run 视为不存在。既有扩展不受影响,因为 + `ExtensionRuntimeDeps.run_evidence_reader` 默认为 `None`;无法提供某一页的 + 存储会显式失败,而不会返回一个具有误导性的空页。生产环境的 reader 以 app + 为作用域,并为受信任的运营方扩展提供全局跨用户可见性;它会脱敏事件元数据, + 但保持内容不变。([#5405]) + #### 持久化 - **持久化:** 可通过 `postgres_schema` 选择自定义 PostgreSQL schema;ORM、LangGraph checkpointer 与 store 表均创建在其中,启动时自动创建。([#3442]) @@ -337,6 +608,56 @@ 加载器——字面字符串、前导零与多行带引号单元格都得以保留,长单元格可在可复制的 对话框中打开,源码视图一键切换。([#5284]) +- **title:** 仅含附件的首回合现在会得到真正的标题,而不是千篇一律的 + `New Conversation`,因此以文件开头的对话既可区分也可检索。当首个回合只携带 + 一个通过校验的附件时,清理后的文件名会成为本地标题;携带多个附件时则生成类 + 似 `2 files uploaded` 的计数;用户自己撰写的文本仍然优先作为标题来源,而在 + 没有可用合法文件名时仍回退为 `New Conversation`。本地标题在配置的标题模型 + 路径之前返回,因此仅含附件的回合不再需要为一次 LLM 调用付费。文件名只从上 + 传中间件填充的 `uploaded_files` 状态中读取,客户端元数据不受信任,并且会做 + 清洗:剥离控制字符与多余空白,保留普通 Unicode 字符和百分号,长名称在保留 + 扩展名的前提下截断。这取代了此前 `` 标题修复特意保留的 + `New Conversation` 回退行为。([#5304]) + +- **projects:** 项目 MVP 第二阶段让项目真正发挥作用,而不只是给文件夹起个名 + 字。项目说明此前会被存储、可以编辑,却没有任何东西读取;现在它会在每个成员 + 线程的 run 中作为有界的 user 角色 `` 块送达模型,且只出现在该 run + 的请求里,永远不会进入系统提示词或持久化历史;重命名会在下一次 run 生效; + 超长文本会在写入时被拒绝,而不是被截断。每个项目的文档架新增 `Documents` + 标签页(上传、带来源信息的列表、预览、下载、移入回收站、按内容哈希去重), + 智能体可按需通过 `list_project_documents` / `read_project_document` 读取 + ——这些工具只在项目 run 中注册,且仅支持文本。此外还支持双向提升: + `Save to project` 把任意线程文件连同来源信息复制到文档架, + `Attach to thread` 则通过常规上传管道把文档架中的文档重新摄入一次对话。只 + 读的对话文件视图会聚合各成员线程的上传文件与产物,项目删除也终于有了回收站 + 这一层级。([#5443]) + +- **frontend:** 调试模式下可以查看通用调用与 MCP 调用的工具详情。这些步骤此 + 前只显示一个标签,尽管浏览器早已收到它们的输入与结果,很难看清工具之间究竟 + 传了什么。`Token Usage → Debug` 现在会带出一个折叠的 `Tool details` 面板 + ——对没有 token 统计数据的调用同样如此——展示工具名称、call ID、输入,以 + 及原始结果或明确的错误,并配有复制操作与中英文标签。内容只在展开时才格式化, + 文本长度、嵌套层级与已访问的值都有上限,因此大体积载荷不会让面板卡住。 + ([#5309]) + +- **frontend:** 对话现在可以从输入框中引用。附件按钮旁边新增了 + `Reference a conversation` 按钮,仅当 `/api/features` 报告该能力时才渲染, + 并会打开一个选择器,其列表与侧边栏使用的最近会话列表相同——从不会提供当前 + 对话,并以报告的 `max_references`(目前为 3)为上限,超过上限的行会被禁用, + 而已选中的行仍可点击以移除。被附加的对话在输入框中显示为可移除的 chip,在 + 对话记录中则是指回来源的只读 chip。引用是按消息生效的:不随草稿保存,发送 + 或切换线程时清除,重新生成或编辑某个回合时会不带引用运行,除非重新附加。 + ([#5465]) + +- **frontend:** 能力管理从设置中迁出,成为工作区侧边栏里独立的 + `Capability Center`。此前 MCP server、已连接应用授权与技能都挤在账号和外观 + 偏好旁边,既难以找到,也让设置对话框难以扫读;现在它们集中在同一个页面上, + 其中 `Plugins` 合并了 MCP 管理与 Lark/飞书应用的安装与授权,`Skills` 提供 + `Built-in`、`Community`、`My skills` 与 `All skills` 四种视图,支持可搜索 + 的卡片、启用开关、文件导入、创建以及自定义技能导出。设置保留其余七个分区, + API 契约与权限均未改变,旧的 `?settings=tools|integrations|skills` 入口被 + 移除。([#5468]) + #### 可观测性与工具 - **可观测性:** trace-id 关联与增强日志,以及通过 Monocle 实现的智能体可观测性 。([#3902]、[#4024]) @@ -355,6 +676,15 @@ 智能体归因;不含查询、schema 或结果),且仅在技能策略过滤之后观测,被拒绝的 schema 不会被报告为生效晋升。([#5183]) +- **client:** 上下文压缩把结果存放在 `messages` 之外的 + `ThreadState.summary_text` 中,但嵌入式客户端的 `values` 事件只选择了 + `title`、`messages` 与 `artifacts`——因此 Gateway 之外的消费方(例如基准 + 测试运行器)无法通过公开事件流观察到该摘要。现在每个嵌入式 `values` 事件都 + 会携带 `summary_text`,缺失时为 `None`,并把更新、重复与清除作为状态快照转 + 发。消息序列化、AI-delta 去重、工具产物与用量记账均未改变,也没有暴露任何 + checkpoint 内部实现。初始快照本身就可能包含恢复出来的摘要,因此仅凭取值变 + 化并不能断定发生了一次压缩事件。([#5249]) + ### 变更 - **前端性能:** 保持公共根页面和本地化文档静态化;懒加载关闭的工作区面板及编辑器/ @@ -411,11 +741,44 @@ 每轮节点数来换算配置的轮次,调高 `max_turns` 就能得到它所声明的轮次。没有配置项变化;既有的 `max_turns` 取值现在会获得完整预算,因此原先被截断的子智能体运行可能变长,其上界仍由 `subagents.timeout_seconds` 与 `subagents.token_budget` 约束。 +- **middleware:** 循环检测不再中断正在分段读取文件的智能体。`read_file` 调用 + 此前按 200 行的分桶建键,因此短于一个分桶的读取都会塌缩到相邻分桶上:连续 + 五次 40 行的读取哈希完全相同,会触发硬停止,使 run 以强制最终答复和 + `stop_reason=loop_capped` 结束——而这正是 `read_file` 自身的截断提示 + 要求模型去做的分段读取。现在的键使用精确的行区间,省略的 `end_line` 保持 + "读到末行"的开放语义,不带范围的读取与显式 `start_line=1` 仍共用同一个键。 + 重复同一区间仍会在原有阈值被拦下,边界抖动的读取循环仍由按工具类型计数的 + 频率层覆盖。 + ([#5486]) + +- **subagents:** 让 `max_turns` 名副其实——表示运维人员所理解的轮次。它此前被 + 直接当作 LangGraph 的 `recursion_limit` 传入,而后者统计的是 super-step, + 每个图节点算一步;而 `create_agent` + 又会为每个中间件生命周期钩子编译出一个节点,因此在 + 子智能体的中间件链上一轮要花掉 7~8 步:内置 `general-purpose` 的 + `max_turns=150` 实际只买到约 18 轮带工具调用的轮次,随后以 + `turn_capped` 结束;每往链上加一个中间件,有效预算还会再缩水一次。现在 + 执行器会按实际装配出的中间件链的每轮节点数来换算配置的轮次,调高 + `max_turns` 就能得到它所声明的轮次。没有配置项变化;既有的 `max_turns` + 取值现在会获得完整预算,因此原先被截断的子智能体 run 可能变长,其上界 + 仍由 `subagents.timeout_seconds` 与 `subagents.token_budget` 约束。 + ([#5485]) + - **调度器:** 在 SQLite 上同样强制执行全局 `max_concurrent_runs`,此前该上限只在 Postgres 上成立。 认领排队中的 occurrence 时,会先统计正在执行的行,再把其中一行提升为 `launching`,Postgres 用 advisory lock 将这两步串行化。而 SQLite 的 deferred 事务直到那条提升用的 UPDATE 才占用 writer, 因此在不同行上并发认领的调用方——手动触发与轮询重叠,或第二个 Gateway 进程共用同一个数据库 文件——会读到同一个过期计数并全部通过预算检查,导致实际运行数超过配置的上限。([#5469]) +- **sandbox:** AIO 的 `glob` 不再把"恰好填满"的结果报告为截断。其 + `include_dirs` 分支在收集到 `max_results` 个匹配时就立即返回, + 因此一个只有这么多匹配、后面再无匹配的目录列表也会被标记为被截断, + 工具据此告诉模型结果不完整。该分支本就持有整份目录列表, + 现在改为多看一个匹配再判断,与同级的 `include_dirs=False` 分支 + 一致,后者一直是按完整列表判断的。这里涉及的只是**过滤后匹配数**上限; + `parse_remote_search_output` 管的是**原始输出行数**上限,是另一条限制、 + 有自己的"多放一行"记账方式,其他 provider 的过滤后匹配数上限未作改动。 + ([#5449]) + - **沙箱:** AIO 的 `glob` 不再把"恰好填满"的结果报告为截断。其 `include_dirs` 分支在收集到 `max_results` 个匹配时就立即返回,因此一个只有这么多匹配、后面再无匹配的目录列表也会被标记为 被截断,工具据此告诉模型结果不完整。该分支本就持有整份目录列表,现在改为多看一个匹配再判断, @@ -605,6 +968,17 @@ - **社区工具:** `image_search` 现在返回全分辨率图片 URL。([#3990]) - **技能:** 把技能历史接口中的阻塞文件 IO 移出事件循环。([#3563]) - **技能:** SkillScan 不再把惰性求值的 PEP 695 类型别名误判为网络出口。([#4315]) +- **skills:** 技能压缩包的解压现在也按成员数量设限,而非只按未压缩大小。 + `safe_extract_skill_archive()` 是每次 `.skill` 安装都会经过的常开路径;它限 + 制了 + 未压缩字节总量以防御按大小计的 zip 炸弹,却没有成员数量上限, + 因此一个装有数万条微小条目的压缩包可以被顺利解出。 + `scan_archive_preflight()` 早已把条目数限制在 4096,但只会在可选的 + `skill_scan.enabled` 开关开启时运行,默认路径因此没有上限。现在解压会无条件 + 执行同样的 4096 成员上限,直接抛出 `ValueError`;在启用了 SkillScan + 的情况下,结构化的 `package-too-many-members` 命中仍会优先呈现。 + ([#4241]) + - **追踪:** 从运行时上下文解析 Langfuse trace 的用户。([#3794]) - **护栏:** 将内部 owner 归因透传到护栏上下文。([#3839]) - **子智能体:** 子智能体上限与 `MIN_SUBAGENT_LIMIT` 保持一致地限幅。([#4081]) @@ -734,6 +1108,667 @@ - **MCP:** 收到 task snapshot 时拒绝非有限 `poll_after_seconds`。([#4750]) - **MCP:** OAuth token 交换中以配置的 `grant_type` 为准,`extra_token_params` 不再能 静默切换 flow。([#4860]) +- **runtime:** 缓慢或卡死的 stdio MCP server 不再拖住整个 Gateway。智能体装配 + 此前在事件循环上同步装配 MCP 工具,因此等待一次进行中的 MCP + 初始化会阻塞所有其他请求的 SSE 投递、run 取消与定时器, + 而不只是阻塞那个正在等待自己工具的调用方。 + 现在每个异步入口点都会把工具装配派发到工作线程:`task_tool` 中的子智能 + 体生成路径、用于持久批处理的 `SubagentBatchService._execute_item`、 + `run_agent` 的 agent 工厂(它覆盖了 lead-agent 装配中两处 + `get_available_tools` 调用点),以及 checkpoint state-accessor 的构建—— + 其冷缓存读取现在在工作线程中承担 MCP 初始化的等待,而不再停住事件循环。这四 + 处卸载共用同一个有界专用池(`utils/assembly_io.py` 中的 + `run_assembly()`,8 个 worker,可用 `DEER_FLOW_ASSEMBLY_WORKERS` + 覆盖),而非事件循环的默认执行器,因此一个为等待完整 MCP 超时而停住的 + worker 无法把其他所有 `to_thread` + 调用方排到自己身后;contextvars + 会跨这次跳转复制,扩展的 build-context 快照因此仍能传播, + 而当等待中的装配数超过 worker 数时会以限流的警告记录下来。由于装配 + 现在可能被长时间挂起,持久批处理会在启动前立即重新检查其条目的持久状态, + 因此在装配期间被取消的批处理不会再发起模型调用。 + ([#5217], [#5224]) + +- **agents:** `LoopDetectionMiddleware` 不再把一个轮次的预算消耗在另一个轮次的 + 合法工作上。它此前只用 `thread_id` 界定相同调用哈希与按工具频率窗口 + 的作用域,因此一个跨轮次复用的已编译智能体——`DeerFlowClient` + 会保留图并为每个轮次分配新的 `run_id`—— + 会把先前轮次的普通调用计入后一个轮次的限额,在默认的相同调用阈值下, + 第三个独立轮次就产生一次虚假的循环警告,第五个轮次则被剥掉一次合法的工具 + 调用。状态现在按 `(thread_id, run_id)` 界定作用域,同一个 run 内多次进入图 + 时证据仍会累积(包括隐藏的 goal 延续),并发兄弟 run 则各自拥有独立的警 + 告队列。LRU 淘汰会丢弃整个 run 作用域,`reset(thread_id)` 仍会清除该线程 + 拥有的每个作用域,阈值以及警告/硬停止行为均未改变。 + ([#5344]) + +- **agents:** 现在,一个 run 的 `token_budget.max_tokens` 上限在活跃 + `/goal` 的各次隐藏延续中同样成立。worker 会在同一个 `run_id` 下重新进入图, + 而中间件此前会在 `after_agent` 中清空该 run 的用量, + 再把所有既有消息标记为已见,因此每次延续都从零开始计数: + 在 `max_tokens: 10000` 下,一个在 12k 处触发硬停止的轮次 + 之后跟着一次继续调用工具的延续,run 一边上报 `stop_reason: token_capped`, + 一边花掉了 20k 令牌,并在达到上限后又执行了两次工具调用。 + 现在用量与警告状态能够在相同 `run_id` 的多次进入图中存活下来, + 循环检测早已如此;而之后的一次用户 run 仍会拿到全新的 `run_id` + 和全新的预算。达到上限后,延续仍会先做一次模型调用, + 然后其工具调用才会被剥离。 + ([#5410]) + +- **middleware:** 人工输入卡片的回复现在会被识别为用户的当前请求。 + 卡片答案以隐藏的 `HumanMessage` 形式到达,其中携带有效的 + `human_input_response`,而轮次检测辅助函数使用的是 + `is_real_user_message`,它会无差别地拒绝每条隐藏消息,没有任何豁免—— + 因此由卡片回复启动的 run 会把较早的可见请求当作"当前"请求,而答案本身 + 却被视为框架注入。在摘要中,当前请求的挽救逻辑因此锁在了那条过期的消息 + 上:压缩之后模型看到的是原封不动的原始请求, + 而用户的地区与年份约束只存在于摘要里。在 `McpRoutingMiddleware` + 中,只出现在卡片答案里的路由关键词匹配不到任何东西, + 于是被推迟的 MCP 工具永远不会被自动提升,模型只能手动调用 + `tool_search`——同样的答案若以可见消息发送就能完成提升, + 可见行为只取决于答案的传输方式。现在两处都改用 + `is_genuine_user_message`,它跳过隐藏消息,除非其携带有效的 + `human_input_response`;没有该字段的隐藏消息仍会被跳过。 + ([#5416], [#5426]) + +- **goal:** 一个已达到 token 上限的 run 所满足的目标, + 其后不再跟着一次毫无意义的延续。既然延续现在共享该 run 的 token 预算, + 硬停止后排入队列的延续只会做一次模型调用,随后其工具调用便被剥离, + 因此无法取得任何进展——然而 worker 仍然会把它排入队列, + 每次都付出一次评估器调用和一次模型调用的代价, + 直到延续上限或无进展上限终止该目标为止。`run_agent` 现在会把 run 的 + `stop_reason` 传入目标延续的准备流程,当它是 `token_capped` 时,目标会以 + `stand_down_reason: "token_capped"` 退场,而不是再排入一次延续。 + 评估器仍会先运行,因此已达到上限的 run 确实满足了的目标仍会被清除,其他 + `stop_reason` 的行为与之前相同。 + ([#5424]) + +- **agents:** 被重试的模型调用不再会丢掉守护中间件早已为它排入的警告。 + `LoopDetectionMiddleware`、`TokenBudgetMiddleware` 和 + `ToolProgressMiddleware` 会在 `wrap_model_call` 中、调用 handler + 之前取空各自已排队的警告或提示;由于 `LLMErrorHandlingMiddleware` + 包裹着它们并通过再次调用自己的 handler 来重试, + 第二次尝试是在队列为空的情况下运行的,而那条警告早已被 + 标记为已发送。一个在某次工具调用上循环、并在携带该警告的请求上因 503 失败一次 + 的模型从未看到这条警告,径直跑到了强制停止。现在这三者都会在 handler + 抛出时把取出的警告重新放回队列头部,从而让重试拿到它们; + 成功调用的行为不变,每个 run 的上限仍然生效, + 而若 run 在没有再次模型调用的情况下结束,警告仍会在 `after_agent` + 处被丢弃。 + ([#5433]) + +- **agents:** token 预算对没有 `run_id` 的 run 重新生效——包括 LangGraph + Server、`langgraph dev` 以及直接调用 `create_deerflow_agent` 的调用方。 + 两处缺陷都源于那个 runtime 局部的回退键。子智能体的硬停止被存在 id + 字符串下,却用 `consume_stop_reason(None)` 读回,因为 `SubagentExecutor` + 会传播父级的 `run_id` 而父级没有,于是被 token 上限截断的子智能体向父级 + 报告的是干净的 `Task Succeeded`,而不是上限截断失败。此外,LangGraph + 会给每个节点各自的 `Runtime` 包装器,因此 `id(runtime)` 在 `after_model`、 + 下一次 `wrap_model_call` 与 `before_agent` 之间各不相同: + 排队的预算警告从未被投递,`after_model` 错过了 `before_agent` + 的基线而把线程里的每条 `AIMessage` 都计入, + 同一线程上的第二次调用还被计入了第一次调用的令牌。现在,context + 中没有非空字符串 `run_id` 的调用会改用 LangGraph 按 run 作用域的 + `Runtime.control` 对象作为键,也就是循环检测使用的同一个锚点; + 而停止原因会严格按给定的 context `run_id` + 存储,包括 `None`。 + ([#5436]) + +- **agents:** 取消 `read_file` 或 `write_file` 调用不再会让 read-before-write + 门的锁被搁置或提前释放。`asyncio.to_thread()` 的取消只会取消 asyncio + 的等待者,不会取消已在运行的 worker,因此一次取消可能让已排队的 + `threading.Lock.acquire()` 一直挂起,或者在跨线程探测仍在运行时就把门释放掉。 + 现在派发出去的门操作会在取消传播之前先在 `asyncio.shield()` 之下排空,第一个 + `CancelledError` 会被保留并在重复取消时重新抛出(包括被排空的那个 + worker 任务自身被取消的情况),取消之后才成功的锁会恰好释放一次, + 而同路径的门会一直被持有到写入检查与读取标记工作完成。 + ([#5395]) + +- **agents:** `create_deerflow_agent` 的三项功能现在名副其实。工厂图此前是在 + 没有 + `DurableContextMiddleware` 的情况下构建的, + 而正是它写入 `delegations` 账本——因此 `SubagentLimitMiddleware` + 始终把此前的委派数计为零,只有 per-response 上限生效——也正是唯一会把 + `summary_text` 放回模型请求的东西, + 因此第一次压缩之后模型只看到保留的尾部,摘要就此丢失。另外, + `RuntimeFeatures(token_budget=True)` 构建的 `TokenBudgetConfig()` + 其 `enabled` 默认为 `false`,于是每个钩子都提前返回,既没有警告, + 也没有硬停止,更没有 `token_capped`。现在 + `_assemble_from_features` 总会按 lead-agent 链的同一位置添加 + `DurableContextMiddleware`,工厂的模型请求会在图含有摘要、委派或 + 已加载技能文件时携带隐藏的 durable-context 块,而 + `token_budget=True` 会构建 `TokenBudgetConfig(enabled=True)`。 + ([#5488]) + +- **subagents:** 子智能体的验收检查在 Windows 上不再让越界命令算作证据。 + `_cd_target_in_scope()` 此前用宿主机平台的路径模块来规范化 bash `cd` 目标, + 因此 + 在 Windows 上一个绝对 POSIX 路径或 `..` 逃逸 + 可能被判为安全的相对目标,一次在受检范围之外运行的测试就能满足某项验收标准。 + 现在 bash 目标与配置的根路径在任何宿主机上都按 POSIX 语义规范化,带盘符的 + Windows 路径会被识别并做大小写不敏感的比较, + 而盘符或目录不匹配时会失败关闭。 + ([#5162]) + +- **sandbox:** 一旦并发子智能体数量超过 AIO 镜像的 shell 会话上限,并发子智能 + 体就会停止工作。每个并发子智能体都会获得自己的持久化 scoped shell,但 AIO + 镜像把 `MAX_SHELL_SESSIONS` 限制为 10,因此第十一个 shell 会在 DeerFlow + 仍持有其 scoped id 的情况下淘汰掉最旧的空闲会话——该子智能体的下一条命令随即以 + `404 Session not found` 失败,而在不提高容量的情况下重建会话只会淘汰另一个 + 子智能体并丢失其 shell 状态。新的本地容器现在会把 + `subagent_runtime.max_running + 1`(多出的那个槽位给 lead shell 留出空间) + 作为 `MAX_SHELL_SESSIONS`,provisioner 模式会把同一个值转发给 sandbox Pod, + 而显式设置的、低于所需容量的 `sandbox.environment.MAX_SHELL_SESSIONS` + 现在会在 provider 启动时报错并列出两个数值。如果 scoped 会话仍然丢失 + (因超时或外部清理),DeerFlow 会重建它一次,且仅针对结构化的 + `404 Session not found` 响应。([#5178]) + +- **view-image:** `view_image` 不再为远程沙箱图片提供过期或缺失的图片。 + 它此前把 `/mnt/user-data/...` 解析为 Gateway 宿主机路径, + 因此字节只有在同步之后才可用,而宿主机上较旧的副本可能顶替当前的图片。 + 现在只要存在活跃的沙箱,字节就从当前的活跃沙箱读取, + 不会仅仅因为存在持久化的沙箱 ID 就去获取一个替代沙箱;轻量的 + 来源信息——精确的 SHA-256 加上源沙箱 ID——会记录在 `viewed_images` 中,而不是 + 把图片字节或 base64 写入 checkpoint。报告图片缺失的替代沙箱只有在与记录的大小 + 和 SHA-256 都相符时才会回退到同步后的宿主机副本,因此大小相同的过期内容会被 + 拒绝;缺失文件的判定对 provider 中立,依据显式的 `__cause__` 链, + 并有意忽略隐式的异常上下文。异步工具调用与模型注入现在会通过 + `run_sync_lifecycle_operation()` 执行阻塞式的沙箱读取,因此取消操作无法在读取 + 排空之前拆除沙箱租约清理。 + ([#5306]) + +- **subagents:** 子智能体在自己的上下文被压缩之后仍保留自己的指令。执行器以 + `system_prompt=None` 构建 agent,并把拼装好的提示作为第一条消息放进 + state,因此其中承载着角色提示、`` 引用规则、验收标准 + 说明、技能索引,以及被推迟的 MCP 工具和路由提示——而压缩是按索引裁剪的, + 索引 0 永远落在被摘要掉的部分里。第一次压缩之后的每一次模型调用都是在 + 完全没有系统提示的情况下进行的,而摘要也替代不了它,因为它是以隐藏数据 + 注入的,权限契约明确告诉模型不要遵从它。现在动态上下文保留会一并挽救 + `SystemMessage`,以及带标签的提醒和最新的用户消息,并保持它们的顺序, + 使提示仍排在最前、且不会被送进摘要器;当提示加上当前请求就是剩下的全部 + 内容时,直接跳过压缩,而不是把提示摘要掉。lead agent 不受影响——它的提示 + 是请求的 system message,而不是 state 里的一条消息。([#5454]) + +- **subagents:** 被反复取消的子智能体不再永久占用自己的执行槽位。 + `SubagentExecutionCapacity.slot()` 会在让出之前递增进程级运行计数, + 并在异步上下文管理器的 `finally` 中释放它;但第二次 `Task.cancel()` + 若在释放等待容量锁时到达,就会打断这次清理:任务以取消状态退出, + 而 `_running` 仍处于递增状态。在 `max_running=1` 下,之后每个原生子智能 + 体都会排队直到超时或被拒绝,尽管当时并没有任何东西在运行,违反了文档记载 + 的不变量——取消与超时会释放队列与槽位归属。现在最后一次释放在自己的任务 + 中运行,不受调用方取消的影响,并在取消传播出去之前跨反复取消完成排空。 + ([#5477]) + +- **worker:** 被委派的子智能体出错不再导致父 run 失败。当子智能体的模型 + 调用在重试后仍以错误结束时,执行器报告 `task_failed`,lead agent 仍然 + 作答,但 worker 还在根级 `task_running` 自定义事件里看到了 + `deerflow_error_fallback` 标记——每一个都携带一条带 `additional_kwargs` + 的子智能体消息——并据此把父 run 标记为 `error`,错误文本取自子智能体。 + 目标延续随之停止,而一次编辑并重跑把线程回滚,丢弃了编辑后的问题和新的 + 回答。自定义帧不再参与父级的错误回退检测,而 lead 自身的错误回退仍通过 + `values`、`messages` 和 `updates` 帧送达;现在 lead 完成时父 run 以 + `success` 结束。([#5407]) + +- **sandbox:** 规范化掩码输出尾部的分隔符,使虚拟路径在 Windows 宿主机上也 + 按 POSIX 风格拼写。两个输出掩码器都在输出的正斜杠规范化副本中搜索宿主机 + 基路径,却从原始文本里切出匹配到的尾部,因此嵌套的反斜杠得以存活进拼接 + 结果,`glob` 结果与被掩码的技能读取会以混合拼写返回,例如 + `/mnt/user-data/workspace/pkg\util.py`。深度为 1 的尾部恰好能干净拼接, + 这正是 Linux CI 从未发现它的原因;现在两处拼接点都会在把尾部接到虚拟 + 前缀之前先规范化它。([#5247]) + +- **sandbox:** 反向解析 Windows 宿主机路径的正斜杠拼写。正向解析以 `/` + 拼写已解析的路径,因为反斜杠会破坏 `\U` 这类 bash 转义序列,但把输出中的 + 宿主机路径映射回其 `/mnt/...` 形式的反向扫描器仍把匹配锚定在原生的反 + 斜杠基路径上——因此每个在命令输出或智能体写入的文件中回来的正向解析路径 + 都无法匹配,把原始宿主机路径、真实用户名和完整目录树泄露给了模型,而不是 + 智能体本应引用的容器路径。现在扫描器的匹配与分隔符无关,与沙箱工具早已 + 采用的同一份契约一致;在两种拼写重合的 POSIX 宿主机上不受影响。([#5373]) + +- **sandbox:** 远程 `list_dir` 不再把失败报告为空目录。在缺失路径上执行 + `find ... 2>/dev/null` 只产生空的 stdout,而客户端错误被当作 `[]` 吞掉, + 于是 `ls` 告诉智能体该目录是 `(empty)`——一个已死的沙箱、一个已关闭的 + 客户端和一个不存在的路径看上去都像一棵可写的空树,让智能体覆盖已有文件 + 或跳过恢复。AIO、E2B、BoxLite、OpenSandbox 和 Tenki 现在会在命令或客户端 + 失败时抛出 `OSError`,在什么都没列出时抛出 `FileNotFoundError`,与本地 + 沙箱对非目录路径的既有行为一致;真正空的目录仍报告 `(empty)`,而 + `find -H` 让带符号链接的根仍可列出。([#5264]) + +- **sandbox:** 拒绝不完整的远程 `list_dir` 遍历,而不是把它当作完整结果 + 呈现。`find` 对缺失的起始路径、以及对打印了部分条目后无法读取的文件或 + 子目录都退出 `1`,而解析器两种情况都接受 `1`——因此一次中途失败的遍历会 + 把可见条目当作一次成功的完整列目返回,`ls` 交给智能体的是一棵静默不完整 + 的树。仍无条目时的状态 `1` 依旧抛出 `FileNotFoundError`,而只要有任意 + 条目的状态 `1` 现在抛出 `OSError`,指出遍历不完整并建议换用更窄的路径, + 与远程 `glob` 早已应用的同一份契约一致。每个共用远程 `list_dir` helper + 的 provider 都无需 provider 侧改动即可继承它。([#5422]) + +- **sandbox:** 远程 `grep` 与 `glob` 不再把失败报告为“没有匹配”。这些搜索 + 丢弃了 stderr,并从 `head` 取管道状态,因此缺失的搜索根、缺失的 + `grep`/`find` 二进制或不可读的目录树会什么都不打印并以 0 退出——而工具会 + 告诉智能体 “No matches found”,而本地沙箱在这种情形下报告的是 + `Error: Directory not found`。现在一个共享包装器会记录搜索命令自身的状态: + 根缺失时抛出 `FileNotFoundError`,任何其他失败——包括在已经打印了部分结果 + 之后的 `grep` 2 或 `find` 1,因为调用方无法区分部分结果与完整结果——都抛出 + `OSError`,告诉智能体有些路径无法读取。真正的无匹配仍返回 `[]`。E2B、 + OpenSandbox、BoxLite 和 Tenki 在客户端已关闭时也会抛出,而不是返回空。 + ([#5380]) + +- **sandbox:** 以行为单位报告 `read_file` 的截断,并点明应从哪一行继续。 + 该工具按 `sandbox.read_file_output_max_chars`(默认 50,000)做头部截断, + 其标记告诉模型用 `start_line`/`end_line` 继续,但切口是按字符偏移切下的, + 标记也只报告字符计数——因此切口几乎总是落在某一行中间,模型看到的最后 + 一行是一个读起来像完整行的片段,且没有任何东西说明该从哪一行继续。 + 在被要求对截断后的文件做下一次读取时,三个模型在 15 次尝试中有 0 次选对了 + `start_line`,偏差达几十行甚至上百行。现在切口落在预算允许的最后一个行 + 边界上,标记以行为单位陈述位置并给出确切的续读位置,例如 + `[truncated: showing first 743 of 1828 lines (49746 of 155704 chars). Continue with start_line=744]` + 带范围的读取报告的是文件行号,而不是 provider 的切片相对行号。单个超长 + 行仍然在字符上限处截断,因为丢掉它会浪费掉大部分预算,此时标记指向 + `bash`,而不是一次无法返回该内容的 `read_file` 调用。([#5474], [#5478]) + +- **sandbox:** 强制 PowerShell 使用 UTF-8 控制台,使 CJK 工具输出在 Windows + 上不再乱码。命令运行器通过 UTF-8 管道读取器捕获 stdout,但 Windows + PowerShell 5.1 除非显式切换,否则以遗留的 OEM 代码页写出控制台输出—— + 在 zh-CN Windows 上是 GBK——因此每个 CJK 字符到达时都是乱码,而又因为读取 + 器是替换无法解码的字节而不是抛错,损坏是静默的。现在每个 PowerShell + `-Command` 载荷都会在用户命令运行前把 `InputEncoding`、`OutputEncoding` + 和 `$OutputEncoding` 设为 UTF-8。([#5440]) + +- **sandbox:** 以感知平台的方式校验 Windows 宿主机上托管的 Lark CLI 沙箱 + 运行时。该检查要求一个 POSIX 可执行位,而 NTFS 并不保留它,因此每个候选 + 都报告 `st_mode & 0o111 == 0`,Gateway 抛出 + `ValueError: Managed Lark CLI sandbox runtime file is not executable` + ——使托管的 Lark 运行时在 Windows 开发宿主机上、以及把托管的 Linux CLI + 运行时挂载进沙箱的 Windows Docker 宿主机上都无法使用。POSIX 保持严格的可 + 执行位契约不变;在 Windows 上,仅限 Linux 的构件改由内容校验,要求各架构 + 二进制具备可执行镜像魔数、启动器具备 shebang。([#5442]) + +- **sandbox:** Docker-outside-of-Docker 沙箱的端口绑定在 Docker Desktop 上 + 默认绑定到回环地址。`DEER_FLOW_SANDBOX_HOST` 在 DooD 模式下默认为 + `host.docker.internal`,它在容器内解析为 Docker Desktop VM 网关,而把沙箱 + 端口发布到该地址会让宿主机套接字层以 `WSAEADDRNOTAVAIL` 拒绝绑定——因此 + 即使启动成功了,第一个沙箱 shell 动作仍以 `ports are not available` 失败。 + 当 Docker server 是 Docker Desktop 且未配置 + `DEER_FLOW_SANDBOX_BIND_HOST` 覆盖时,绑定主机现在是 `127.0.0.1`, + Docker Desktop 会转发它;显式覆盖仍然优先,原生 Linux DooD 不受影响。 + ([#5446]) + +- **sandbox:** 在取消被当作失败清理之前排空技能同步 worker。默认的 + `sync_agent_skills_async()` 包装器使用裸的 `asyncio.to_thread()`,因此取消 + 等待方会在同步的沙箱变更尚未完成时返回,而 `SandboxMiddleware` 随即在 + provider 工作仍在途中时释放执行持有者——破坏了生命周期契约:代表某个 + 持有者启动的阻塞工作必须在该持有者离开边界之前排空。现在该包装器改经 + 既有的取消栅栏运行,因此对那些自身不对同步与释放做串行化的第三方和基于 + 上传的 provider,顺序也能成立。([#5350]) + +- **frontend:** 让人工输入卡片与请求它的回合待在一起。在多轮对话中,一张 + 既有的 `needYourHelp` 卡片可能漂到下一条用户消息下方,于是时间顺序变得 + 误导,一个已回答的请求可能读起来像新一轮的一部分。一条提交前的基线消息 + 可能被编进新持久化的用户消息之后,一张仅通过 REST 历史确认的卡片可能 + 永远进不了 checkpoint 基线,而已经为当前 run 持久化的步骤可能在一次中断 + 之后被抬进上一轮。现在基线消息会恢复在待处理的人类消息之前,经 REST + 确认的卡片算作已确立的历史,而当前 run 自身的步骤留在启动它们的消息 + 之下。([#4892]) + +- **frontend:** 在实时内容合并中保留服务端分配的消息位置。流式更新在替换 + 消息内容的同时也会丢掉它的 `deerflow_seq`,于是服务端已经定妥的排序被在 + 客户端重算并算错——已加载的 `1,3,5` 历史窗口与 `2,5` 的实时尾部合并后 + 渲染为 `1,3,2,5`,长线程(历史分页,或压缩之后恢复的对话)会以乱序显示 + 其步骤,直到一次刷新。现在内容与位置分开处理,合并、压缩桥接和渲染账本 + 共用同一份位置优先级,使上游已排序的结果不会在下游被重新排序,而异常 + 序列值(null、字符串、NaN、非整数、非安全整数)永远不会覆盖一个已知位置。 + ([#5293]) + +- **frontend:** 在增量流重连之后恢复用户的输入。在较晚一个回合期间刷新, + 可能让重连流在当前人类输入进入持久历史 feed 之前先回放 AI 与工具 chunk, + 于是用户的消息短暂消失,其推理步骤被归到上一轮之下。现在活动 run 的输入 + 会在加入增量流之前先从 `kwargs.input` 恢复并合并入最新的持久线程状态,按 + id 去重;如果元数据或状态读取不可用,则原样沿用先前的重连路径。([#5428]) + +- **frontend:** 即使一个智能体的工具组显式为空,也显示它的技能徽章。徽章 + 容器的空值合并链停在工具组计数 `0` 上,因此一个声明为 `tool_groups: []` + 且 `skills: ["data-analysis"]` 的智能体在 Agents gallery 中丢掉了徽章—— + 尽管显式为空的工具组列表是有效的,并受 Agent API 与 `update_agent` 工具 + 支持。现在容器在两个列表中任意一个至少有一项时即显示。([#5326]) + +- **frontend:** 把 composer 的斜杠技能建议限定到活动智能体。自定义智能体 + 对话可以限制自己能激活哪些技能,包括限定为一个显式空列表,但 composer + 仍提供全局的已启用技能目录——于是智能体可能建议一个它无权激活的技能。 + 现在建议会经过活动智能体的允许列表过滤,显式空列表被视为没有可用技能, + 而不是在智能体加载期间短暂泄露全局目录;当一份保存的、带有已选技能 chip + 的 composer 草稿被恢复时,也应用同样的限定。([#5451]) + +- **frontend:** 删除侧边栏会话前先确认。在某个最近会话的菜单中选择 Delete + 会立即移除该对话及其文件,没有任何确认步骤,尽管删除是不可逆的。现在一个 + 对话框会点明该对话并警告删除不可逆,聚焦 Cancel,并在 Cancel、Escape 或 + 关闭时保持会话完好;删除待处理期间操作被禁用、驳回被阻止,而删除失败会让 + 对话框保持打开并带上底层错误,以便用键盘重试。该对话框挂在虚拟化的侧边栏 + 行之外,因此一次列表刷新无法在一次部分删除之后移除重试 UI。([#5406]) + +- **agents:** 让 Custom Agent 设置对话框留在视口内。选择 **Selected + subagents** 可能使对话框高于窗口,在一条长长的 worker 描述填满嵌套列表时 + 把标题、关闭控件和 Save/Cancel 按钮推到屏幕之外。现在头部与操作按钮位于 + 单一滚动表单区域之外,子智能体描述显示两行预览,并有可通过键盘访问的展开 + 在不移动焦点或改变复选框状态的前提下滚动进视区,而长名称与描述会换行而 + 不缩小复选框。([#5458]) + +- **frontend:** 让 MCP 配置对话框留在视口内。在 Capability Center 中打开 + 一份很长的 MCP server 定义会让 JSON textarea 长过窗口,于是居中的对话框 + 裁掉了标题、关闭控件和 Save/Cancel 按钮,在较小的窗口上变得难以编辑或 + 关闭。现在对话框受视口约束、在桌面上有更多横向空间,长 JSON 在编辑器内部 + 滚动,而标题、Close、Save 和 Cancel 留在滚动区域之外,即使在极小的视口下、 + 或遇到很长的服务器名称或校验消息时也是如此。([#5492]) + +- **frontend:** 在那些可点击、却仍显示默认光标而使其可操作性不清晰的交互 + 控件上显示指针光标。现在一个全局选择器覆盖原生按钮、`role="button"` 元素、 + 下拉菜单项和命令项,排除原生禁用控件和任何标记了 `aria-disabled="true"` + 的元素;生成的 `ui/` 与 `ai-elements/` 组件不受影响。([#4921]) + +- **frontend:** 中文本地化文档链接。本地化文档中的 MDX 链接与 Nextra 卡片 + 此前在改写时并不知道活动的文档语言,于是跟着快速上手链接走的中文读者被 + 送到了英文页面。现在本地化文档布局通过 context 提供该语言,链接改写会 + 尊重它。([#5275]) + +- **runtime:** 仅内存部署在计划清理之下保留其 run 历史。`cleanup()` 此前 + 无条件淘汰,因此一个按文档默认 `store=None` 构建 `RunManager()` 的内嵌 + 消费者会在清理触发时从历史中丢掉已完成的 run——`get(run_id)` 返回 `None`, + `list_by_thread()` 丢掉该记录,因为这些读取没有持久副本可以回退。现在淘汰 + 以存在后端存储为前提,恢复了仅内存模式此前永久保留的行为;有存储支撑的 + manager 仍在宽限期后淘汰。([#5453]) + +- **events:** 读取含有 U+0085、U+2028 或 U+2029 的 JSONL 事件记录。三个 + 读取器都会把这些 Unicode 分隔符当作记录边界来切分,因此携带其中之一的 + 记录在读取时会被跳过——这会在重新打开之后复用事件序列号,并把幂等插入 + 变成重复。现在线程读取、run 读取与序列恢复都按物理换行切分;既有有效 + 文件无需重写。([#5429]) + +- **events:** 把每线程的 JSONL 写锁一直保留到文件系统工作落定。在其 worker + 释放期间取消一次存储调用会在追加仍在途中时丢掉锁,于是稍后的一次删除 + 可能在被取消的追加重建该记录之前就完成,而一个被取消的混合 run 批可能 + 回滚掉一次它已经确认的写入。现在被接纳的变更会在传播取消之前,一直持有 + 其锁跨过文件 I/O、回滚与簿记,覆盖 `put`、`put_if_absent`、批量写入和两种 + 删除方法;排队的调用方仍可在被接纳前取消,无关的线程保持彼此独立。 + ([#5439]) + +- **events:** 阻止 JSONL 线程变更在一次删除中分裂到两代锁。 + `delete_by_thread()` 在仍持有每线程锁的同时把它从注册表中移除,因此一个 + 已排在旧锁上的变更可能与一个稍后为同一线程解析出新建锁的变更并发运行 + ——在紧跟删除边界处出现重叠的序列号分配与文件变更。现在注册表是一个 + `WeakValueDictionary`,且不再弹出该条目,于是持有者与排队的等待者让同一代 + 保持存活直到它们排空,之后该条目自行消失。([#5455]) + +- **channels:** 让渠道的首次线程创建只使用一代锁。清理逻辑在当前创建者 + 退出时无条件移除每会话锁,于是一个失败或被取消的创建者会释放并注销自己的 + 锁,而一个排队的创建者正通过它进入,一个迟到的到达者还可能装上新锁并发 + 进入——两个调用方为同一会话创建 Gateway 线程,靠后的映射写入胜出,相邻的 + 消息被拆到被孤立的线程上。现在手工管理的生命周期被替换为既有的、感知等待者 + 的键控锁表,于是会话键跨异常和取消保持同一代可发现,空闲键只在最后一个 + 参与者离开后才被回收。([#5480]) + +- **gateway:** 以数据属主、而非授权身份读取一个线程的 run。列表、keyset + 分页和单 run 读取端点按调用方的授权身份过滤 run 行,而 `start_run` 给它们 + 盖的是数据身份戳——两者只在本来就是安全的属主取值上重合,因此受信任的 + 内部调用方即使在自己有权操作的线程上也总是看到一个空 run 列表和 404, + 而这个症状很容易被误读为 run 丢失。现在内部调用方会跳过每用户存储过滤器, + 而这些端点的线程可见性已经做了授权;浏览器与 API 会话则保持各自确切的每 + 用户过滤器;两个消息读取端点有同样的混淆,也以同样方式修正。([#5448]) + +- **gateway:** 对消息编辑与重新生成的 helper 路径应用同样的数据身份限定。 + 那三个 helper 仍在解析授权身份并把它当作数据过滤器传入,因此内部调用方在 + 自己有权的线程上 `regenerate/prepare` 与 `edit-regenerate/prepare` 会以 409 + 失败。现在它们按与读取端点相同的方式解析自己的过滤器 id,浏览器与 API + 会话则保持先前的每用户过滤器、以及对跨用户 run 的 409。([#5483]) + +- **conversation:** 让会话读取器的页面留在内联范围内,并说明何时丢掉了 + 文本。一个页面此前是靠切掉放不下的最后一条消息来填满其 20,000 字符上限的, + 而被切掉的后缀永远无法翻页取回——六条各 3,500 字符的消息中最旧的一条回来 + 时约 2,500 字符,且 `has_more: false`。更糟的是,超出默认 12,000 字符工具 + 输出预算的页面会被 `ToolOutputBudgetMiddleware` 外置,于是模型只看到一份 + 摘要加上一份源文本的文件副本,而这是在目标线程里。现在页面按实际适用于 + `read_conversation` 的预算来确定大小(每工具覆盖,否则 + `externalize_min_chars`,以及 `fallback_max_chars`),仍以 20,000 文本字符 + 为上限,而放不下的一条消息会完整地开启下一页——于是一项运维设置始终当家, + 且 `tool_output.tool_overrides.read_conversation` 只调整这一个工具。现在 + 被截断的结果会带上一条提示,要求智能体承认这一省略、并在声称完整覆盖之前 + 索取缺失的材料。([#5421]) + +- **goal:** 在澄清卡片仍打开时停下目标循环。目标评估器只读取人类与 AI + 消息,因此通过 `ask_clarification` 提出的问题——它以工具结果的形式到达 + ——对它不可见;它判定目标未达成,worker 便排入一次隐藏延续,告诉智能体 + 继续工作、除非真正被阻塞否则不要问用户。智能体于是按自己的猜测行事, + 包括对 `risk_confirmation` 问题,而卡片就那样在 UI 里等着。现在一次尾部的 + 人类输入请求会在评估器运行之前被检测到,并以既有原因 + `blocked:needs_user_input` 让目标退场;用户的回答是一条新的人类消息,于是 + 下一次 run 正常评估,而前面没有助手文本的卡片会报告它为何停下,而不是 + `run_failed`。([#5467]) + +- **client:** 发出后又被后续某个节点改写的 AI 消息,其新增文本现在也能流式送 + 出。`LoopDetectionMiddleware`、`TokenBudgetMiddleware`、 + `SafetyFinishReasonMiddleware`、`SubagentLimitMiddleware` 与 + `TerminalResponseMiddleware` 都会在各自的节点里用同一个 id 改写最后一条 AI + 消息,但流只会把首次见到的 id 记下来,之后一律跳过,因此改写从未送达 + `messages-tuple` 消费方:`chat()` 与 headless `--print` 返回的是改写之前的文 + 本,而不是终态错误或强制停止提示;TUI 从未显示停止提示或安全提示;模型节点 + 之后追加的 `token_usage_attribution` 也始终没到。values 路径现在会为每个 id + 记住最后一次见到的消息对象,并在快照中持有另一个对象时重新检查它,只把新增的 + 文本作为又一次 delta 发出,并把新的 `additional_kwargs` 走既有的仅元数据后续 + 事件;未变化的消息仍会被跳过,不再重新抽取文本。不会扩展已发送文本的替换则不 + 会重新发出。([#5479]) + +- **mcp:** 并发初始化时 MCP 会话池不再超出容量上限。`MCPSessionPool` 只在会话 + 创建开始之前检查 LRU 容量,而由于初始化需要 await,多个不同的 key 可能各自观 + 察到还有空余容量、各自进入 `_inflight`,随后在没有二次检查的情况下被提升进 + `_entries`——让持久会话注册表突破 `MAX_SESSIONS`,并连带保留其所暗示的额外子 + 进程与连接。现在会话被提升时会原子地重新检查并强制执行容量,提升时的淘汰受害 + 者则经由其属主任务生命周期,在注册表锁之外关闭。([#4962]) + +- **mcp:** 并行的同步 MCP 调用不再互相取消对方的连接。当内嵌客户端向同一个 + stdio server 派发两次同步调用时,每个包装器都跑在自己的事件循环上,而池会把 + 兄弟循环上的存活会话与进行中的创建视为过期——把它们取消或关闭,并以 + `CancelledError` 中止某个工具步骤,而不是返回两个结果。已建立的会话与进行中 + 的创建现在按 `(server_name, scope_key, owning_loop)` 归键,因此同一循环内的 + 调用方仍共享创建与状态,而不同的循环各自独立,每个属主也只回收自己的记录,包 + 括在正常的 `asyncio.run()` 关闭之后。同步调用仍使用彼此独立的子进程会话,也 + 不获得任何共享的服务端状态。([#5396]) + +- **web-fetch:** 抽取出的 Markdown 里的相对目标现在会按其来源页面解析。Jina、 + Browserless 与 InfoQuest 原样返回 HTML 中书写的链接,因此位于 + `https://example.com/docs/current` 的页面会产出 `[Next](../next)`,让智能体拿 + 不到任何可以继续跟进的 URL。三个 provider 现在都会把请求的 URL 传给共享抽取 + 器,由它把锚点与图片目标相对该 URL、或相对首个可用的文档 `` 解析;只改 + 写目标属性的取值,而不是序列化另一棵解析树,因此畸形的标记、注释、脚本与属性 + 格式都原封不动地保留,并仍以原样到达 Readability.js。fetch 校验、4096 字符的 + 输出上限与线程外抽取均未改变,Python 回退路径也保持纯文本行为。([#5310]) + +- **community:** Firecrawl 的 `web_fetch` 与 `web_search` 工具现在遵循该工具的 + `base_url`。`_get_firecrawl_client` 只读取 `api_key`,因此 `FirecrawlApp` 总是 + 回退到 `https://api.firecrawl.dev`——并且由于只要目标是云端 API 而未设置 key, + SDK 就会抛出 `Error: No API key provided`,自托管的 Firecrawl 无论怎么配置都 + 无法访问。`base_url` 现在会作为 `api_url` 转发,让本地的 Firecrawl 无需云端 + key 即可同时服务这两个工具;未配置 `base_url` 的配置仍与原先完全一样地构造客 + 户端。([#5392]) + +- **community:** Tavily 抽取现在从 `web_fetch` 条目而非搜索配置读取凭据。当 + `web_search` 用 Serper、`web_fetch` 用 Tavily 时,fetch 用 Serper 的 key 构造 + 客户端,把独立的 Tavily fetch key 完全忽略了。现在每个工具都从自己的条目读取 + `api_key`,并在未提供时回退到 SDK 的 `TAVILY_API_KEY`,与既有的 Exa 辅助函数保 + 持一致。只在 `web_search` 下放置共享 Tavily key 的配置,必须同时在 + `web_fetch` 下也设置它,或让两者都依赖 `TAVILY_API_KEY`。([#5496]) + +- **client:** `DeerFlowClient.stream()` 现在会把流式工具调用只发出一次,并带上 + 完整参数。OpenAI 风格的模型把工具名与 id 放在第一个 chunk 里,不带 id 的参数 + 片段放在其余 chunk 里,而客户端为每个 chunk 都发出一个 `messages-tuple` + tool_calls 事件,每个都只依据那一个 chunk 解析——因此完整调用(它确实出现在 + values 快照里)反而因为其消息 id 已在 `streamed_ids` 中而被跳过。工具本身仍以 + 正确的参数运行;错的只是事件流,在 TUI 里工具卡片显示的是 `bash` 而详情为空, + 而不是那条命令。现在每个消息从 values 快照只发出一个事件,而非流式模型的完整 + AI 消息与流式文本则不受影响。([#5408]) + +- **browser:** 浏览器会话的拆除不再随调用方的取消而中断。 + `BrowserSessionManager.close_session()` 会在 await 其关闭之前先把会话从注册表 + 中移除,而该关闭又直接 await 私有的 Playwright 循环,因此此刻的取消会穿过 + `asyncio.wrap_future()` 传播到清理逻辑本身——留下浏览器进程与 context 成为孤 + 儿,会话却已不在注册表中,也没有属主再来重试。`close_all_sessions()` 在更大规 + 模上有同样的属主问题:它先清空整个注册表,因此关闭第一个会话时的取消会让之后 + 每个会话都彻底没有安排任何拆除。现在拆除会在任何可取消的 await 之前提交给私有 + 循环,并在 `asyncio.shield()` 之后 await,而 close-all 会在其首个 await 之前先 + 安排好每个会话,并对组等待加 shield。调用方仍会收到 `CancelledError`;只有已 + 经启动的清理的属主身份与它隔离了。([#5444]) + +- **browser:** 无论工具字段顺序如何都能检测到浏览器依赖。启动期的依赖检测只 + 在 `name` 是 YAML 列表项的第一个 key 时才认得该工具名,因此把 `name` 移到 + `use` 或 `group` 之后,工具配置仍能工作,却静默地把 browser extra 从 + `uv sync` 中漏掉,让浏览器工具没有必需的依赖。现在直接的 `name` 字段在工具条 + 目中任何位置都能被认得,按缩进追踪可防止嵌套选项内部的名称去启用该 extra,而 + 检测器仍保持仅用标准库,因为它运行在依赖安装之前。([#5456]) + +- **setup:** 同一个 pre-sync extras 检测器现在按 `utf-8-sig` 读取 + `config.yaml`。PyYAML 能接受前导的 UTF-8 BOM,但检测器按纯 `utf-8` 读取该文 + 件,把 BOM 留在了第一行上,因此其锚定的章节模式无法认出合法配置的开头章节, + 并漏掉了它声明的 extras——`postgres`、`browser` 与 `ollama` 的检测在其章节位 + 于最前时都以同样方式失败,而同一份文件的纯 UTF-8 副本却可以正常工作。 + ([#5504]) + +- **video:** 把 `--aspect-ratio` 转发进 Gemini Veo 请求。该技能 CLI 接受此旗标 + 并传给 `generate_video()`,但 Gemini 分支仅用 `instances` 构造它的 + `predictLongRunning` 请求体,把该值丢掉了,因此无论参数是什么,每次 Veo 请求 + 都按 provider 默认比例渲染。现在它作为 `parameters.aspectRatio` 发送。 + ([#5388]) + +- **channels:** WeCom 的两条对外路径都发送无上限的文本,而 bot 协议把内容上限 + 压到 20480 UTF-8 字节,这个门槛一份深度研究报告轻易就能越过。超过上限时, + `_send_with_retry` 重试三次后放弃:流式路径让回复卡在中途,从未发送过 + `finish=true`;推送路径则把回复整个丢弃。流式回复现在在 UTF-8 字符边界上截 + 断,其后带一个可见的截断标记——一个流承载整条回复,因此不会在中途断开——而 + 没有可回复帧的主动推送(例如定时任务通知)则按换行边界拆成顺序的 markdown 消 + 息,让完整内容仍能送达。两条路径现在都以 UTF-8 字节而非字符来度量载荷。 + ([#5148]) + +- **channels:** 入站 WeChat(iLink)与 WeCom 媒体在任何大小限制生效之前就已 + 被完整下载,而下载目标又是一个直接取自消息 payload、未做任何目的地校验的 + URL,于是一个确实超限的附件会在遭到拒绝之前先把 Gateway 的内存顶上去。两个 + 频道现在都会流式传输,一旦超过 `max_inbound_image_bytes` / + `max_inbound_file_bytes` 就在传输途中中止,而精确的解密后大小检查仍保留为 + 第二道防线。入站 `full_url` 被限制为 http/https 加一份按点边界的主机后缀允 + 许列表,默认为 `qq.com` 系列与已配置的 CDN 主机,可用 + `channels.wechat.allowed_media_hosts` 与 + `channels.wecom.allowed_media_hosts` 扩展,其中 WeCom COS 门禁固定为已验证的 + Tencent Cloud APPID 形态,WeCom 管理端读取器还有 50 MB 的传输中上限。读取 + 器会请求 `Accept-Encoding: identity` 并拒绝任何残留的 `Content-Encoding`, + 因此 httpx 无法在测量之前把压缩过的响应体膨胀到超过上限。格式正确且未超限 + 的媒体不受影响;超限或不在允许列表中的媒体现在会被跳过,并附上指明原因的警 + 告。([#5225]) + +- **channels:** 现在单条无法解码的 WeChat 消息不会再拖累同批其余消 + 息。`_poll_loop` 在收到整批消息后立刻持久化 `get_updates_buf` 游标,随后逐 + 条迭代 `data["msgs"]` 且没有按消息隔离错误,于是只要有一条消息的处理抛出异 + 常,现实中的触发因素是损坏或无法解密的附件,例如被截断的加密图片 payload, + 循环就会中断,而游标此时已经越过整批消息。失败消息之后的每条消息都会被永久 + 丢弃,下一次轮询也不会重新拉取它们。现在 `_handle_update` 在按消息包裹的 + `try`/`except` 中运行,因此同批消息仍能送达 bus,失败会连同其消息 id 一起 + 记入日志,而游标仍会在整批消息全部尝试过之后前进。([#4231]) + +- **channels:** 当 Discord 客户端线程死亡时,原因是 token 失效或不可恢复的 + 关闭,`Client.start()` 会返回,并留下一个已停止但未关闭的事件循环,于是 + `call_soon_threadsafe` 排入了一个永远不会执行的回调,而 `send`、`send_file` + 与 `_get_channel_or_thread` 中那个没有上限的 + `await asyncio.wrap_future(...)` 会永远挂住。每次向已死频道发出的消息都会 + 从默认五人的 worker 池里永久占用一个 `ChannelManager` worker,因此区区几条 + 消息就能冻结所有 IM 频道的入站处理,而不仅是 Discord。这些跨循环调用现在统 + 一经由一个辅助函数,把等待时间限制在 `DISCORD_OUTBOUND_TIMEOUT_SECONDS`(30 + 秒),文件上传则另有 `DISCORD_UPLOAD_TIMEOUT_SECONDS`(120 秒),这样缓慢 + 的上行链路加上较长的 429 retry-after 就不会取消一次健康的传输,事件循环缺 + 失或已停止时则会关闭该协程并立即抛出异常。`DiscordChannel.is_running` 现在 + 也要求客户端线程存活,正如 `FeishuChannel` 此前的做法,因此就绪轮询会重启 + 频道,而不是把一个已死的频道当作健康;`start()` 失败时,半启动状态的实例现 + 在会被停止并丢弃,这样反复的就绪重试就不会在 bus 上累积陈旧的对外监听 + 器。([#5227]) + +- **channels:** Discord 线程映射在线程创建之后于事件循环之外持久化,因此进 + 程若在创建与其后台写入之间被杀掉,仍可能丢失最新的映射,并让某个 Discord + 线程在重启后无法访问。`stop()` 现在会把内存中的映射刷写到磁盘,并做了包 + 装,使关闭路径永不被阻塞,这是在硬杀窗口之上的一张尽力而为的兜底网,而不是 + 新的写入路径。([#5461]) + +- **channels:** 在 Telegram 频道配置中启用 `rich_messages` 会让每条最终对外 + 消息都渲染成一行损坏的单行文本:像 `/help` 这样的命令菜单与普通的错误回复 + 都被经 `sendRichMessage` 以 `rich_message.markdown` 发送,而后者会合并单个 + 换行,并剥掉菜单所依赖的 `` / `` / `` 尖括号 + token。现在只有 `rich_messages` 已启用 *且* 文本确实包含某种富文本构造,即 + 围栏代码块、表格行或分隔行、任务列表、`[text](url)` 链接、粗体、斜 + 体、`
` 块或 `$$` 数学式时,消息才会以富文本形式发送。该检测器刻意 + 保守,要求表格必须让该行以 `|` 开头,因此 `/help` 中像 + `/goal [condition|clear]` 这样的一行永远不会触发它。([#5470]) + +- **community:** InfoQuest 的 reader、web search 与 image search 调用 + `requests` 时没有设置传输层超时,因此即使配置了远程 crawl 的 `timeout` 字 + 段,端点停滞仍会让同步 worker 无限期等待。三处调用点现在共享一个 30 秒的连 + 接/读取非活动上限;crawl 超时与导航 payload 字段、成功的解析以及既有的 + `Error:` 返回都未改变。这限制的是非活动时间,而非总墙上时钟时间。([#5315]) + +- **models:** 请求准入限流器在两次独立的加锁中分别决定立即准入与入队,因此 + 一个阻塞型调用方可能观察到下一个许可尚未到期、在入队之前被挂起,随后在许可 + 到期而等待队列仍为空时被更新的调用方抢先:新来者拿走了许可,较早的调用方只 + 能再等一个间隔,或在 `max_wait_seconds` 较短的情况下超时,而较晚的那个请求 + 却成功了。立即准入与 FIFO 入队现在是一次加锁保护的单一决策。非阻塞调用仍会 + 快速失败且永不入队,队列化限速、取消、超时、队列容量与无突发行为均未改 + 变。([#5459]) + +- **uploads:** 上传文档的大纲把任何以 `#` 开头的行都当作标题,因此一份在 + `# Real section` 之前有 51 行 `#tag` 的文档会耗尽所有大纲槽位,把真正的章 + 节藏起来;缩进四个空格或用制表符的注释会变成假标题,可选的结尾井号也会留在 + 标题里。提取器现在按原始行匹配根层级的 ATX 语法,即一到六个井号、空格或制 + 表符分隔符或行尾、最多三个前导空格,并在既有的粗体清理之前剥掉合法的结尾井 + 号。物理行号、PDF 结构标题、围栏代码排除与标题数量上限均未改变,扫描器仍保 + 持有界,而不会变成一个 Markdown 解析器。([#5316]) + +- **uploads:** 单个 200,000 字符的段落会产出 199,999 字符的上传预览,尽管有 + 五行的限制;过长的合法标题也会绕过 50 条的上限,而该上限实际上是上下文大小 + 的约束。大纲标题现在统一截断到 200 字符,回退预览文本则截断到 2,000 字符并 + 跨所有行,省略标记也计入这些预算。物理行号、短文本、既有的标题与预览数量, + 以及原始文件字节都得到保留;这约束的只是返回的摘要文本,而不是文件扫描内 + 存,也不是跨上传的总预算。([#5323]) + +- **doctor:** 在全新克隆上,先 `make config` 再 `make doctor` 会报出四个错 + 误,而其中只有一个是真实的:`config.example.yaml` 提供的 `models:` 键里每 + 个条目都被注释掉了,它解析为 `None`,于是 `.get("models", [])` 里的 `[]` + 默认值从未生效,一个宽泛的 `except` 又把由此产生的 `TypeError` 渲染成三条 + `'NoneType' object is not iterable` 检查结果。新用户会在 `make doctor` 本 + 应减少困惑的那一刻看到一副安装损坏的样子,而他们其实只是还没有配置任何模 + 型。现在当没有配置任何模型时 LLM 检查会跳过,只留下可操作的 + `models configured` 失败及其 `make setup` 提示;一旦配置了模型,一切都不会 + 改变。([#5296]) + +- **scripts:** `detect_uv_extras.py` 从 `config.yaml` 解析 uv extras,使 + `make dev` 不会在每次重启时清掉可选依赖,但它没有针对 Ollama 的规则,于是 + 配置了 Ollama 模型时它什么都不返回,`serve.sh` 便运行了不带 `--extra ollama` + 的 `uv sync`,`langchain-ollama` 就从一套能正常工作的环境里被卸载了。随后 + 配置的模型会以 `ModuleNotFoundError: No module named 'langchain_ollama'` + 失败,而没有任何线索指向 `make dev` 才是原因,尽管 `make doctor` 刚刚报告 + 该包已安装。现在配置 Ollama 模型会让 `make dev` 传入 `--extra ollama`,与 + `database.backend: postgres` 传入 `--extra postgres` 的方式相同;不含 Ollama + 模型的配置不受影响,`config.example.yaml` 中被注释掉的 Ollama 示例也会被正 + 确地忽略。([#5318]) + +- **docker:** 在 Windows Git Bash 上,`make docker-start` 与 `make up` 会无 + 条件中止,并报出 `Docker socket not found at /var/run/docker.sock — + AioSandboxProvider (DooD) will not work.` MSYS2/Git Bash 在该路径下并没有 + 物理的 Unix socket,Docker Desktop 使用的是命名管道,尽管守护进程原生支持把 + `/var/run/docker.sock` 挂载进 Linux 容器,因此这项检查永远不可能通过。在 + `MINGW*`、`MSYS*` 与 `CYGWIN*` 下,预检现在改为验证 `docker info` 的连通 + 性,而不再要求存在 socket 文件;POSIX 主机在缺少 socket 时仍会快速失 + 败。([#5371]) + +- **deploy:** 在 Windows Git Bash 上,`make up` 会在容器启动阶段失败,报出 + `mkdir C:\Program Files\Git\var: Access is denied.` `scripts/deploy.sh` 导 + 出了 `DEER_FLOW_DOCKER_SOCKET=/var/run/docker.sock`,而 MSYS 在调用原生 + `docker compose` 时把这个已导出的值转换成了 Windows 主机路径,于是 + `docker-compose.dood.yaml` 挂载了 + `C:\Program Files\Git\var\run\docker.sock`,守护进程便试图创建主机上并不存 + 在的目录。socket 现在保存在一个未导出的局部变量里,正如 `scripts/docker.sh` + 此前的做法;在 Windows Git Bash 上,当该变量持有默认值时会将其 unset,让 + Compose 展开它自己的字面量 `/var/run/docker.sock`;运维人员设置的自定义 + socket 路径仍会照常传入。([#5402]) + - **MCP:** 从工作区变更排除内部 stdio 临时目录 `.mcp/tmp`。([#4898]) - **MCP:** 持久任务提交中途取消时同时取消远端任务。([#4933]) - **沙箱:** 接受文档中的 E2B reconciliation 配置字段。([#4772]) @@ -804,6 +1839,17 @@ 不会扩大访问范围。([#4984]) - **智能体:** 规范化 `Command` 结果中返回的 `ToolMessage`,错误 payload 不再默认 获得成功回执,工具进度追踪也能正确识别。([#4977]) +- **config:** `use_previous_response_id` 在 `config.yaml` 的模型条目里是可 + 用的;它并非 `ModelConfig` 字段,只经由模型工厂的 `extra="allow"` 透传抵达 + `ChatOpenAI`;但它从未被记录,读起来又像是 `use_responses_api` 的同义词。 + 事实并非如此:`use_responses_api` 选择的是端点,而 + `use_previous_response_id` 则把 Responses API 从每轮重放完整历史切换为在服 + 务端状态上串接。`config.example.yaml` 中的 OpenAI Responses API 示例新增了 + 一行被注释掉的 `use_previous_response_id: false`,并注明它会原样转发给 + `ChatOpenAI`、串接起来的上下文仍按 input token 计费、以及客户端的历史改写 + 只在历史被重放时才生效。仅新增注释,没有 schema 变更,没有新的 `ModelConfig` + 字段,也没有 `config_version` 版本号提升。([#5359]) + - **MCP:** `get_session` 在 eviction 期间被取消时拆除 in-flight session owner, 避免调用方取消后泄漏 owner task 或超时后仍保持挂起。([#5008]) - **MCP:** 普通 stdio 工具在 transport 断开后可重新连接:仅当失败的 pooled @@ -852,6 +1898,75 @@ 与 Honcho 读取都会传播;OpenViking 作用域解析失败遵循配置的读取策略;5 秒注入 deadline 同样遵循该策略(fail-open 时无上下文继续,严格模式以超时为原因抛出)。 ([#4726]) +- **persistence:** 在不启用它的前提下,先加上按线程的 incarnation token 所 + 需的存储基础。新增可空的 `threads_meta.incarnation` 与 + `mcp_tasks.thread_incarnation` 列,即一个新的 Alembic head,使任务可以绑定 + 到被复用的线程 ID 所属的那个具体线程,因为已删除线程的 ID 可能被后来的线程 + 占用,而持久化的 MCP 工作会比这次删除活得更久。新建的线程记录会被赋予一个 + 随机的 32 字符 incarnation,并仅在该线程属于任务用户、或是无属主的遗留行时 + 才复制进新的 MCP 任务行;查询与插入在 SQLite 上通过标量子查询、在 PostgreSQL + 上通过 share lock 保持原子。目前还没有任何代码读取这些字段,它们也被排除在 + 线程与 MCP 任务的 API 响应之外,且两列都可空、无默认值,从而让较旧的二进制 + 文件在滚动部署期间仍能继续写入。([#5216]) + +- **scripts:** 在原生 Windows 上,`shutil.which("pnpm")` 遵循 + `PATH`/`PATHEXT` 的解析规则,可能选中一个通用的 `pnpm` 匹配,即 `.exe` 或 + `.bat`,或位于更早路径上的包装脚本,而不是 npm 安装的 `pnpm.cmd` 包装脚 + 本。共享的主机运行器现在会先检查 `pnpm.cmd` 与 `corepack.cmd`,再检查那些 + 通用名称,与仓库既有文档中记录的 Windows 命令选择顺序一致;POSIX 上的解析 + 顺序未变。([#5305]) + +- **tests:** 在一位 Windows 贡献者的主机上,后端测试套件跨七个文件失败了 68 + 个 shell 脚本测试,而它们没有一个是由于被测代码本身有问题。CreateProcess + 会先搜索 `System32` 再搜索 `PATH`,因此 WSL 的 `bash.exe` 启动器赢下了每一次 + `["bash", ...]` 的派发,却又无法针对 Windows 检出路径运行仓库脚本:在非英语 + Windows 上,它那条本地化的 UTF-16LE 警告会击穿 harness 的管道解码,把真正 + 的失败埋在 `TypeError` 背后;`sh` 在 MSYS2 之外并不存在;而 + `dev-entrypoint.sh` 的 `command -v python3` 探测选中的是 Microsoft Store + 的别名存根,它们在执行时会以 49 退出。各套件现在通过一个共享辅助函数来解析 + shell,它镜像了仓库的 Git Bash 包装脚本发现逻辑,并显式拒绝 WSL 启动器与 + Store 存根;在不存在 Git Bash 的地方则干净地跳过。POSIX 与 CI 的行为未 + 变。([#5404]) + +- **tests:** 在 Windows 主机上,跨三个套件的六个测试纯粹因路径分隔符的写法 + 而失败。provisioner 的 `join_host_path` 有意保留主机原生风格,甚至有一条显 + 式的 `PureWindowsPath` 分支,而 `os.path.normpath` 在那里会把 POSIX 形式的 + 输入用反斜杠重新拼写,因此它构造的 `hostPath` 字符串、docker `--mount` 的 + `src=` 写法,以及 review CLI 的 `PYTHONPATH` 都与 POSIX 风格的字面量对不 + 上。断言现在会在比较之前先把主机原生的一侧规范化,这在 POSIX 上是空操作, + 因此 CI 的期望值保持逐字节一致;生产行为未变。([#5413]) + +- **tests:** 技能 request-scoped-secrets 套件中的五个测试在 Windows 主机上 + 失败,而两个负向检查则以空洞的方式通过,因为探针里嵌入了 POSIX 语 + 法:`LocalSandbox._get_shell()` 在那里解析为 PowerShell,其中 `$VAR` 是一 + 个未定义的 PowerShell 变量,会展开为空,因此正向检查永远看不到注入的值,而 + `"secret" not in out` 断言实际上是在与空输出比较。探针现在会按生产实际解析 + 出的 shell 的语法来渲染,PowerShell 下为 `$env:NAME`,cmd.exe 下为 + `%NAME%`,POSIX shell 下为 `$NAME`,因此负向检查在 Windows 上真正验证了擦 + 除效果,而按调用划分作用域的用例也证明了注入的值确实到达了第一个子进 + 程。POSIX 上的输出在形式上未变。([#5415]) + +- **tests:** + `test_run_on_isolated_subagent_loop_survives_caller_loop_teardown` 在 CI + 负载下、在无关的 PR 上,以及 `main` 自身上都间歇性失败。竞态出在测试里,而 + 不是 `run_on_isolated_subagent_loop` 中:那个辅助函数就是 + `asyncio.run_coroutine_threadsafe`,它的 `concurrent.futures.Future` 只有 + 在协程返回之后才会被标记为完成,但测试从协程内部发出信号并立刻断言 + `done()`,于是主线程可能在 future 仍处于 `pending` 时就醒了过来。断言现在 + 会在检查 `done()` 之前先阻塞在 `result(timeout=10)` 上;没有引入 `sleep`, + 被测行为也未改变。([#5299]) + +- **tests:** checkpoint 保留需要一份可执行的声明,说明一次删除绝不可以破坏 + 什么;一个只保留最新 N 个的提案就曾在评审显示它会静默破坏分支与时间旅行的 + 父链语义之后被撤回。新套件把六种保留场景固定在 memory、SQLite 与 Postgres + 三套 checkpointer 上:完整 schema 与 delta schema 各自按步骤的增长基线;删 + 除分支祖先会以 `CheckpointLineageError` 大声失败,而不是静默破坏分支或重新 + 生成;删除一个显式的恢复目标会移除恢复入口;挂起的写入是被保留的状态,而非 + 垃圾;末尾仅含时长的叶子与叶子的同级分支(真正的分叉路径)可以被安全删除。 + 一份配套草案记录了受保护的集合、可证明安全的删除形态,以及带孤儿记账的联合 + 表机制,供未来任何 `max_checkpoints_per_thread` 或 TTL 工作对照校 + 验。([#5255]) + - **记忆:** 删除或清空自定义智能体时会取消缓冲中的记忆抽取,待处理的防抖定时器 不再复活已删除的按智能体记忆作用域,也不会用过期的待处理更新覆盖新的清空结果。 ([#5123]) @@ -973,6 +2088,19 @@ 要、`SOUL.md`、子智能体描述、技能元数据,以及记忆更新 prompt 中的会话块——并中 和 `web_capture` 工具结果中的提示词注入标签。([#4028]、[#4119]、[#4137]、[#4157] 、[#4162]、[#4099]、[#4060]、[#4097]、[#4128]) +- **prompt-injection:**对 MindIE 工具响应框架进行转义。该 provider 的 + `_fix_messages` 在把工具调用名称与参数渲染进 + `` / `` 标签前会对其进行转义,但把 `ToolMessage` + 包进 `` 的姊妹路径却原样传递文本——而该输出到达时大体未经净化, + 因为工具结果净化器只覆盖其允许列表中的远程内容工具(`web_fetch`、 + `web_search`、`image_search`、`web_capture`),并有意不处理本地 + `bash` / `read_file` 输出以及名称不同的 MCP 工具。因此 `read_file` 结果中一 + 个字面的 `` 会提前闭合框架,其后的一切都以仿佛位于工具响应之 + 外的方式呈现给 MindIE 模型,于是埋在不可信文件中的负载可以伪造 + ``。内容现在用 `quote=False` 做 HTML 转义,与工具调用路径 + 一致;模型仍会将实体解码回来,因此只有框架受到保护,合法结果的理解方式与之前 + 完全一致。([#4253]) + - **提示词注入:** 修复两处输入净化绕过。`hide_from_ui` 与人类消息上的 `name="summary"` 会让 `is_genuine_user_message` 认定该消息由框架写入,从而完全跳 过净化;现在携带这两者的不可信 run 输入与线程状态写入会在服务端被标记并照常净化, @@ -1073,6 +2201,15 @@ - **CI:** 发布 lark-cli-init 与 lark-broker 镜像。([#4558]) - **开发:** host 侧 pnpm 调用统一经带 Corepack fallback 的 runner。([#4405]) - **基准:** 新增隔离的 checkpoint channel-mode 基准,对比 `full` 与 `delta` 的延迟、存储和回放。([#4395]) +- **deps:** 升级 `cryptography` 49.0.0 -> 50.0.0、`postcss` 8.4.31 -> 8.5.25、 + `h2` 4.3.0 -> 4.4.1、`langgraph-checkpoint-sqlite` 和 + `langgraph-checkpoint-postgres` 3.1.0 -> 3.1.1、`nanoid` 5.1.6 -> 5.1.16、 + `h3` 1.15.6 -> 1.15.9,以及 `next` 16.2.11 -> 16.3.3。其中 `next` 的升级修复 + 了两条未经身份验证的远程代码执行公告,一条位于 Image Optimization API 的 + AVIF 路径,一条影响部署在 Windows 上的服务器; + `h3` 则阻止经过双重编码的 `.` 段越出静态路由目录。 + ([#4211], [#4681], [#4683], [#4737], [#4738], [#4747], [#4748], [#5377]) + - **依赖:** 升级 `cryptography` 49.0.0 -> 50.0.0、`postcss` 8.4.31 -> 8.5.25、 `h2` 4.3.0 -> 4.4.1、两个 `langgraph-checkpoint-*` 3.1.0 -> 3.1.1,以及 `nanoid` 5.1.6 -> 5.1.16。([#4681]、[#4683]、[#4737]、[#4738]、[#4747]、[#4748]) @@ -1094,7 +2231,6 @@ - **测试:** Windows 上跳过 POSIX mode-bit 技能权限断言(`chmod` 契约在该平台不可 观测),使 Windows 贡献者可以获得绿色的后端套件基线。([#5244]) - ## [2.0.0] — 2026-06-15 DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子智能体、持久化记忆、 @@ -1459,6 +2595,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#3153]: https://github.com/bytedance/deer-flow/pull/3153 [#3174]: https://github.com/bytedance/deer-flow/pull/3174 [#3176]: https://github.com/bytedance/deer-flow/pull/3176 +[#3183]: https://github.com/bytedance/deer-flow/pull/3183 [#3191]: https://github.com/bytedance/deer-flow/pull/3191 [#3200]: https://github.com/bytedance/deer-flow/pull/3200 [#3228]: https://github.com/bytedance/deer-flow/pull/3228 @@ -1477,6 +2614,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#3342]: https://github.com/bytedance/deer-flow/pull/3342 [#3377]: https://github.com/bytedance/deer-flow/pull/3377 [#3393]: https://github.com/bytedance/deer-flow/pull/3393 +[#3396]: https://github.com/bytedance/deer-flow/pull/3396 [#3397]: https://github.com/bytedance/deer-flow/pull/3397 [#3398]: https://github.com/bytedance/deer-flow/pull/3398 [#3408]: https://github.com/bytedance/deer-flow/pull/3408 @@ -1496,6 +2634,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#3435]: https://github.com/bytedance/deer-flow/pull/3435 [#3436]: https://github.com/bytedance/deer-flow/pull/3436 [#3437]: https://github.com/bytedance/deer-flow/pull/3437 +[#3442]: https://github.com/bytedance/deer-flow/pull/3442 [#3451]: https://github.com/bytedance/deer-flow/pull/3451 [#3453]: https://github.com/bytedance/deer-flow/pull/3453 [#3455]: https://github.com/bytedance/deer-flow/pull/3455 @@ -1546,6 +2685,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#3561]: https://github.com/bytedance/deer-flow/pull/3561 [#3562]: https://github.com/bytedance/deer-flow/pull/3562 [#3563]: https://github.com/bytedance/deer-flow/pull/3563 +[#3565]: https://github.com/bytedance/deer-flow/pull/3565 [#3566]: https://github.com/bytedance/deer-flow/pull/3566 [#3569]: https://github.com/bytedance/deer-flow/pull/3569 [#3570]: https://github.com/bytedance/deer-flow/pull/3570 @@ -1597,6 +2737,8 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#3686]: https://github.com/bytedance/deer-flow/pull/3686 [#3687]: https://github.com/bytedance/deer-flow/pull/3687 [#3698]: https://github.com/bytedance/deer-flow/pull/3698 +[#3703]: https://github.com/bytedance/deer-flow/pull/3703 +[#3708]: https://github.com/bytedance/deer-flow/pull/3708 [#3709]: https://github.com/bytedance/deer-flow/pull/3709 [#3711]: https://github.com/bytedance/deer-flow/pull/3711 [#3713]: https://github.com/bytedance/deer-flow/pull/3713 @@ -1615,6 +2757,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#3770]: https://github.com/bytedance/deer-flow/pull/3770 [#3772]: https://github.com/bytedance/deer-flow/pull/3772 [#3775]: https://github.com/bytedance/deer-flow/pull/3775 +[#3783]: https://github.com/bytedance/deer-flow/pull/3783 [#3786]: https://github.com/bytedance/deer-flow/pull/3786 [#3790]: https://github.com/bytedance/deer-flow/pull/3790 [#3791]: https://github.com/bytedance/deer-flow/pull/3791 @@ -1625,8 +2768,10 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#3810]: https://github.com/bytedance/deer-flow/pull/3810 [#3812]: https://github.com/bytedance/deer-flow/pull/3812 [#3821]: https://github.com/bytedance/deer-flow/pull/3821 +[#3824]: https://github.com/bytedance/deer-flow/pull/3824 [#3826]: https://github.com/bytedance/deer-flow/pull/3826 [#3828]: https://github.com/bytedance/deer-flow/pull/3828 +[#3833]: https://github.com/bytedance/deer-flow/pull/3833 [#3837]: https://github.com/bytedance/deer-flow/pull/3837 [#3839]: https://github.com/bytedance/deer-flow/pull/3839 [#3843]: https://github.com/bytedance/deer-flow/pull/3843 @@ -1709,6 +2854,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4018]: https://github.com/bytedance/deer-flow/pull/4018 [#4023]: https://github.com/bytedance/deer-flow/pull/4023 [#4024]: https://github.com/bytedance/deer-flow/pull/4024 +[#4025]: https://github.com/bytedance/deer-flow/pull/4025 [#4026]: https://github.com/bytedance/deer-flow/pull/4026 [#4028]: https://github.com/bytedance/deer-flow/pull/4028 [#4033]: https://github.com/bytedance/deer-flow/pull/4033 @@ -1742,7 +2888,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4085]: https://github.com/bytedance/deer-flow/pull/4085 [#4090]: https://github.com/bytedance/deer-flow/pull/4090 [#4094]: https://github.com/bytedance/deer-flow/pull/4094 -[#4095]: https://github.com/bytedance/deer-flow/issues/4095 +[#4095]: https://github.com/bytedance/deer-flow/pull/4095 [#4096]: https://github.com/bytedance/deer-flow/pull/4096 [#4097]: https://github.com/bytedance/deer-flow/pull/4097 [#4098]: https://github.com/bytedance/deer-flow/pull/4098 @@ -1784,18 +2930,22 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4170]: https://github.com/bytedance/deer-flow/pull/4170 [#4171]: https://github.com/bytedance/deer-flow/pull/4171 [#4174]: https://github.com/bytedance/deer-flow/pull/4174 +[#4178]: https://github.com/bytedance/deer-flow/pull/4178 [#4181]: https://github.com/bytedance/deer-flow/pull/4181 [#4187]: https://github.com/bytedance/deer-flow/pull/4187 [#4188]: https://github.com/bytedance/deer-flow/pull/4188 [#4190]: https://github.com/bytedance/deer-flow/pull/4190 -[#4192]: https://github.com/bytedance/deer-flow/issues/4192 +[#4192]: https://github.com/bytedance/deer-flow/pull/4192 [#4193]: https://github.com/bytedance/deer-flow/pull/4193 [#4197]: https://github.com/bytedance/deer-flow/pull/4197 [#4199]: https://github.com/bytedance/deer-flow/pull/4199 +[#4200]: https://github.com/bytedance/deer-flow/pull/4200 [#4202]: https://github.com/bytedance/deer-flow/pull/4202 [#4203]: https://github.com/bytedance/deer-flow/pull/4203 [#4208]: https://github.com/bytedance/deer-flow/pull/4208 [#4209]: https://github.com/bytedance/deer-flow/pull/4209 +[#4210]: https://github.com/bytedance/deer-flow/pull/4210 +[#4211]: https://github.com/bytedance/deer-flow/pull/4211 [#4215]: https://github.com/bytedance/deer-flow/pull/4215 [#4217]: https://github.com/bytedance/deer-flow/pull/4217 [#4218]: https://github.com/bytedance/deer-flow/pull/4218 @@ -1804,32 +2954,48 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4225]: https://github.com/bytedance/deer-flow/pull/4225 [#4229]: https://github.com/bytedance/deer-flow/pull/4229 [#4230]: https://github.com/bytedance/deer-flow/pull/4230 +[#4231]: https://github.com/bytedance/deer-flow/pull/4231 [#4234]: https://github.com/bytedance/deer-flow/pull/4234 [#4235]: https://github.com/bytedance/deer-flow/pull/4235 [#4238]: https://github.com/bytedance/deer-flow/pull/4238 [#4239]: https://github.com/bytedance/deer-flow/pull/4239 +[#4241]: https://github.com/bytedance/deer-flow/pull/4241 +[#4242]: https://github.com/bytedance/deer-flow/pull/4242 [#4245]: https://github.com/bytedance/deer-flow/pull/4245 [#4246]: https://github.com/bytedance/deer-flow/pull/4246 +[#4247]: https://github.com/bytedance/deer-flow/pull/4247 +[#4250]: https://github.com/bytedance/deer-flow/pull/4250 [#4251]: https://github.com/bytedance/deer-flow/pull/4251 +[#4253]: https://github.com/bytedance/deer-flow/pull/4253 [#4255]: https://github.com/bytedance/deer-flow/pull/4255 [#4256]: https://github.com/bytedance/deer-flow/pull/4256 [#4260]: https://github.com/bytedance/deer-flow/pull/4260 +[#4262]: https://github.com/bytedance/deer-flow/pull/4262 [#4264]: https://github.com/bytedance/deer-flow/pull/4264 +[#4266]: https://github.com/bytedance/deer-flow/pull/4266 [#4267]: https://github.com/bytedance/deer-flow/pull/4267 [#4268]: https://github.com/bytedance/deer-flow/pull/4268 +[#4274]: https://github.com/bytedance/deer-flow/pull/4274 +[#4275]: https://github.com/bytedance/deer-flow/pull/4275 [#4277]: https://github.com/bytedance/deer-flow/pull/4277 [#4278]: https://github.com/bytedance/deer-flow/pull/4278 [#4279]: https://github.com/bytedance/deer-flow/pull/4279 [#4283]: https://github.com/bytedance/deer-flow/pull/4283 +[#4284]: https://github.com/bytedance/deer-flow/pull/4284 [#4287]: https://github.com/bytedance/deer-flow/pull/4287 [#4288]: https://github.com/bytedance/deer-flow/pull/4288 [#4292]: https://github.com/bytedance/deer-flow/pull/4292 +[#4293]: https://github.com/bytedance/deer-flow/pull/4293 +[#4298]: https://github.com/bytedance/deer-flow/pull/4298 +[#4301]: https://github.com/bytedance/deer-flow/pull/4301 +[#4302]: https://github.com/bytedance/deer-flow/pull/4302 [#4306]: https://github.com/bytedance/deer-flow/pull/4306 [#4309]: https://github.com/bytedance/deer-flow/pull/4309 [#4311]: https://github.com/bytedance/deer-flow/pull/4311 +[#4314]: https://github.com/bytedance/deer-flow/pull/4314 [#4315]: https://github.com/bytedance/deer-flow/pull/4315 [#4316]: https://github.com/bytedance/deer-flow/pull/4316 -[#4324]: https://github.com/bytedance/deer-flow/issues/4324 +[#4324]: https://github.com/bytedance/deer-flow/pull/4324 [#4326]: https://github.com/bytedance/deer-flow/pull/4326 [#4337]: https://github.com/bytedance/deer-flow/pull/4337 [#4347]: https://github.com/bytedance/deer-flow/pull/4347 @@ -1838,6 +3004,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4355]: https://github.com/bytedance/deer-flow/pull/4355 [#4356]: https://github.com/bytedance/deer-flow/pull/4356 [#4358]: https://github.com/bytedance/deer-flow/pull/4358 +[#4360]: https://github.com/bytedance/deer-flow/pull/4360 [#4361]: https://github.com/bytedance/deer-flow/pull/4361 [#4364]: https://github.com/bytedance/deer-flow/pull/4364 [#4365]: https://github.com/bytedance/deer-flow/pull/4365 @@ -1846,78 +3013,51 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4373]: https://github.com/bytedance/deer-flow/pull/4373 [#4374]: https://github.com/bytedance/deer-flow/pull/4374 [#4376]: https://github.com/bytedance/deer-flow/pull/4376 +[#4377]: https://github.com/bytedance/deer-flow/pull/4377 [#4381]: https://github.com/bytedance/deer-flow/pull/4381 +[#4382]: https://github.com/bytedance/deer-flow/pull/4382 [#4383]: https://github.com/bytedance/deer-flow/pull/4383 +[#4384]: https://github.com/bytedance/deer-flow/pull/4384 [#4385]: https://github.com/bytedance/deer-flow/pull/4385 [#4391]: https://github.com/bytedance/deer-flow/pull/4391 [#4392]: https://github.com/bytedance/deer-flow/pull/4392 [#4394]: https://github.com/bytedance/deer-flow/pull/4394 +[#4395]: https://github.com/bytedance/deer-flow/pull/4395 [#4402]: https://github.com/bytedance/deer-flow/pull/4402 [#4403]: https://github.com/bytedance/deer-flow/pull/4403 +[#4405]: https://github.com/bytedance/deer-flow/pull/4405 +[#4406]: https://github.com/bytedance/deer-flow/pull/4406 [#4407]: https://github.com/bytedance/deer-flow/pull/4407 [#4408]: https://github.com/bytedance/deer-flow/pull/4408 [#4411]: https://github.com/bytedance/deer-flow/pull/4411 -[#4414]: https://github.com/bytedance/deer-flow/issues/4414 -[#4424]: https://github.com/bytedance/deer-flow/issues/4424 +[#4414]: https://github.com/bytedance/deer-flow/pull/4414 +[#4423]: https://github.com/bytedance/deer-flow/pull/4423 +[#4424]: https://github.com/bytedance/deer-flow/pull/4424 [#4425]: https://github.com/bytedance/deer-flow/pull/4425 [#4426]: https://github.com/bytedance/deer-flow/pull/4426 +[#4427]: https://github.com/bytedance/deer-flow/pull/4427 +[#4429]: https://github.com/bytedance/deer-flow/pull/4429 [#4430]: https://github.com/bytedance/deer-flow/pull/4430 [#4431]: https://github.com/bytedance/deer-flow/pull/4431 [#4432]: https://github.com/bytedance/deer-flow/pull/4432 [#4434]: https://github.com/bytedance/deer-flow/pull/4434 [#4437]: https://github.com/bytedance/deer-flow/pull/4437 +[#4439]: https://github.com/bytedance/deer-flow/pull/4439 [#4441]: https://github.com/bytedance/deer-flow/pull/4441 [#4442]: https://github.com/bytedance/deer-flow/pull/4442 +[#4443]: https://github.com/bytedance/deer-flow/pull/4443 [#4444]: https://github.com/bytedance/deer-flow/pull/4444 [#4446]: https://github.com/bytedance/deer-flow/pull/4446 [#4447]: https://github.com/bytedance/deer-flow/pull/4447 +[#4448]: https://github.com/bytedance/deer-flow/pull/4448 [#4450]: https://github.com/bytedance/deer-flow/pull/4450 +[#4453]: https://github.com/bytedance/deer-flow/pull/4453 [#4456]: https://github.com/bytedance/deer-flow/pull/4456 [#4459]: https://github.com/bytedance/deer-flow/pull/4459 [#4460]: https://github.com/bytedance/deer-flow/pull/4460 [#4468]: https://github.com/bytedance/deer-flow/pull/4468 [#4469]: https://github.com/bytedance/deer-flow/pull/4469 [#4471]: https://github.com/bytedance/deer-flow/pull/4471 -+[#3183]: https://github.com/bytedance/deer-flow/pull/3183 -[#3396]: https://github.com/bytedance/deer-flow/pull/3396 -[#3442]: https://github.com/bytedance/deer-flow/pull/3442 -[#3565]: https://github.com/bytedance/deer-flow/pull/3565 -[#3703]: https://github.com/bytedance/deer-flow/pull/3703 -[#3708]: https://github.com/bytedance/deer-flow/pull/3708 -[#3783]: https://github.com/bytedance/deer-flow/pull/3783 -[#3824]: https://github.com/bytedance/deer-flow/pull/3824 -[#3833]: https://github.com/bytedance/deer-flow/pull/3833 -[#4025]: https://github.com/bytedance/deer-flow/pull/4025 -[#4178]: https://github.com/bytedance/deer-flow/pull/4178 -[#4200]: https://github.com/bytedance/deer-flow/pull/4200 -[#4210]: https://github.com/bytedance/deer-flow/pull/4210 -[#4242]: https://github.com/bytedance/deer-flow/pull/4242 -[#4247]: https://github.com/bytedance/deer-flow/pull/4247 -[#4250]: https://github.com/bytedance/deer-flow/pull/4250 -[#4262]: https://github.com/bytedance/deer-flow/pull/4262 -[#4266]: https://github.com/bytedance/deer-flow/pull/4266 -[#4274]: https://github.com/bytedance/deer-flow/pull/4274 -[#4275]: https://github.com/bytedance/deer-flow/pull/4275 -[#4284]: https://github.com/bytedance/deer-flow/pull/4284 -[#4293]: https://github.com/bytedance/deer-flow/pull/4293 -[#4298]: https://github.com/bytedance/deer-flow/pull/4298 -[#4301]: https://github.com/bytedance/deer-flow/pull/4301 -[#4302]: https://github.com/bytedance/deer-flow/pull/4302 -[#4314]: https://github.com/bytedance/deer-flow/pull/4314 -[#4360]: https://github.com/bytedance/deer-flow/pull/4360 -[#4377]: https://github.com/bytedance/deer-flow/pull/4377 -[#4382]: https://github.com/bytedance/deer-flow/pull/4382 -[#4384]: https://github.com/bytedance/deer-flow/pull/4384 -[#4395]: https://github.com/bytedance/deer-flow/pull/4395 -[#4405]: https://github.com/bytedance/deer-flow/pull/4405 -[#4406]: https://github.com/bytedance/deer-flow/pull/4406 -[#4423]: https://github.com/bytedance/deer-flow/pull/4423 -[#4427]: https://github.com/bytedance/deer-flow/pull/4427 -[#4429]: https://github.com/bytedance/deer-flow/pull/4429 -[#4439]: https://github.com/bytedance/deer-flow/pull/4439 -[#4443]: https://github.com/bytedance/deer-flow/pull/4443 -[#4448]: https://github.com/bytedance/deer-flow/pull/4448 -[#4453]: https://github.com/bytedance/deer-flow/pull/4453 [#4472]: https://github.com/bytedance/deer-flow/pull/4472 [#4480]: https://github.com/bytedance/deer-flow/pull/4480 [#4482]: https://github.com/bytedance/deer-flow/pull/4482 @@ -1953,7 +3093,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4559]: https://github.com/bytedance/deer-flow/pull/4559 [#4564]: https://github.com/bytedance/deer-flow/pull/4564 [#4570]: https://github.com/bytedance/deer-flow/pull/4570 -[#4574]: https://github.com/bytedance/deer-flow/issues/4574 +[#4574]: https://github.com/bytedance/deer-flow/pull/4574 [#4575]: https://github.com/bytedance/deer-flow/pull/4575 [#4577]: https://github.com/bytedance/deer-flow/pull/4577 [#4578]: https://github.com/bytedance/deer-flow/pull/4578 @@ -1966,7 +3106,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4599]: https://github.com/bytedance/deer-flow/pull/4599 [#4600]: https://github.com/bytedance/deer-flow/pull/4600 [#4604]: https://github.com/bytedance/deer-flow/pull/4604 -[#4611]: https://github.com/bytedance/deer-flow/issues/4611 +[#4611]: https://github.com/bytedance/deer-flow/pull/4611 [#4615]: https://github.com/bytedance/deer-flow/pull/4615 [#4617]: https://github.com/bytedance/deer-flow/pull/4617 [#4618]: https://github.com/bytedance/deer-flow/pull/4618 @@ -2000,6 +3140,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4684]: https://github.com/bytedance/deer-flow/pull/4684 [#4690]: https://github.com/bytedance/deer-flow/pull/4690 [#4693]: https://github.com/bytedance/deer-flow/pull/4693 +[#4696]: https://github.com/bytedance/deer-flow/pull/4696 [#4701]: https://github.com/bytedance/deer-flow/pull/4701 [#4703]: https://github.com/bytedance/deer-flow/pull/4703 [#4707]: https://github.com/bytedance/deer-flow/pull/4707 @@ -2008,7 +3149,9 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4719]: https://github.com/bytedance/deer-flow/pull/4719 [#4722]: https://github.com/bytedance/deer-flow/pull/4722 [#4724]: https://github.com/bytedance/deer-flow/pull/4724 +[#4726]: https://github.com/bytedance/deer-flow/pull/4726 [#4727]: https://github.com/bytedance/deer-flow/pull/4727 +[#4729]: https://github.com/bytedance/deer-flow/pull/4729 [#4730]: https://github.com/bytedance/deer-flow/pull/4730 [#4735]: https://github.com/bytedance/deer-flow/pull/4735 [#4736]: https://github.com/bytedance/deer-flow/pull/4736 @@ -2038,18 +3181,23 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4800]: https://github.com/bytedance/deer-flow/pull/4800 [#4804]: https://github.com/bytedance/deer-flow/pull/4804 [#4806]: https://github.com/bytedance/deer-flow/pull/4806 +[#4810]: https://github.com/bytedance/deer-flow/pull/4810 [#4812]: https://github.com/bytedance/deer-flow/pull/4812 [#4815]: https://github.com/bytedance/deer-flow/pull/4815 [#4816]: https://github.com/bytedance/deer-flow/pull/4816 [#4817]: https://github.com/bytedance/deer-flow/pull/4817 +[#4820]: https://github.com/bytedance/deer-flow/pull/4820 [#4822]: https://github.com/bytedance/deer-flow/pull/4822 [#4823]: https://github.com/bytedance/deer-flow/pull/4823 [#4825]: https://github.com/bytedance/deer-flow/pull/4825 +[#4826]: https://github.com/bytedance/deer-flow/pull/4826 [#4827]: https://github.com/bytedance/deer-flow/pull/4827 [#4830]: https://github.com/bytedance/deer-flow/pull/4830 [#4833]: https://github.com/bytedance/deer-flow/pull/4833 +[#4834]: https://github.com/bytedance/deer-flow/pull/4834 [#4836]: https://github.com/bytedance/deer-flow/pull/4836 [#4838]: https://github.com/bytedance/deer-flow/pull/4838 +[#4839]: https://github.com/bytedance/deer-flow/pull/4839 [#4840]: https://github.com/bytedance/deer-flow/pull/4840 [#4842]: https://github.com/bytedance/deer-flow/pull/4842 [#4844]: https://github.com/bytedance/deer-flow/pull/4844 @@ -2061,45 +3209,48 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#4861]: https://github.com/bytedance/deer-flow/pull/4861 [#4863]: https://github.com/bytedance/deer-flow/pull/4863 [#4865]: https://github.com/bytedance/deer-flow/pull/4865 +[#4867]: https://github.com/bytedance/deer-flow/pull/4867 [#4868]: https://github.com/bytedance/deer-flow/pull/4868 +[#4876]: https://github.com/bytedance/deer-flow/pull/4876 [#4877]: https://github.com/bytedance/deer-flow/pull/4877 +[#4878]: https://github.com/bytedance/deer-flow/pull/4878 [#4882]: https://github.com/bytedance/deer-flow/pull/4882 +[#4884]: https://github.com/bytedance/deer-flow/pull/4884 [#4887]: https://github.com/bytedance/deer-flow/pull/4887 [#4888]: https://github.com/bytedance/deer-flow/pull/4888 +[#4892]: https://github.com/bytedance/deer-flow/pull/4892 [#4898]: https://github.com/bytedance/deer-flow/pull/4898 +[#4901]: https://github.com/bytedance/deer-flow/pull/4901 [#4903]: https://github.com/bytedance/deer-flow/pull/4903 [#4911]: https://github.com/bytedance/deer-flow/pull/4911 [#4918]: https://github.com/bytedance/deer-flow/pull/4918 +[#4921]: https://github.com/bytedance/deer-flow/pull/4921 [#4928]: https://github.com/bytedance/deer-flow/pull/4928 [#4933]: https://github.com/bytedance/deer-flow/pull/4933 [#4936]: https://github.com/bytedance/deer-flow/pull/4936 [#4938]: https://github.com/bytedance/deer-flow/pull/4938 +[#4944]: https://github.com/bytedance/deer-flow/pull/4944 +[#4946]: https://github.com/bytedance/deer-flow/pull/4946 [#4951]: https://github.com/bytedance/deer-flow/pull/4951 +[#4952]: https://github.com/bytedance/deer-flow/pull/4952 [#4953]: https://github.com/bytedance/deer-flow/pull/4953 +[#4955]: https://github.com/bytedance/deer-flow/pull/4955 [#4956]: https://github.com/bytedance/deer-flow/pull/4956 [#4959]: https://github.com/bytedance/deer-flow/pull/4959 [#4960]: https://github.com/bytedance/deer-flow/pull/4960 +[#4962]: https://github.com/bytedance/deer-flow/pull/4962 [#4963]: https://github.com/bytedance/deer-flow/pull/4963 [#4965]: https://github.com/bytedance/deer-flow/pull/4965 [#4970]: https://github.com/bytedance/deer-flow/pull/4970 -[#4983]: https://github.com/bytedance/deer-flow/pull/4983 -[#4987]: https://github.com/bytedance/deer-flow/pull/4987 -[#4998]: https://github.com/bytedance/deer-flow/pull/4998 -[#4696]: https://github.com/bytedance/deer-flow/pull/4696 -[#4810]: https://github.com/bytedance/deer-flow/pull/4810 -[#4820]: https://github.com/bytedance/deer-flow/pull/4820 -[#4826]: https://github.com/bytedance/deer-flow/pull/4826 -[#4834]: https://github.com/bytedance/deer-flow/pull/4834 -[#4839]: https://github.com/bytedance/deer-flow/pull/4839 -[#4867]: https://github.com/bytedance/deer-flow/pull/4867 -[#4876]: https://github.com/bytedance/deer-flow/pull/4876 -[#4878]: https://github.com/bytedance/deer-flow/pull/4878 -[#4946]: https://github.com/bytedance/deer-flow/pull/4946 -[#4955]: https://github.com/bytedance/deer-flow/pull/4955 [#4972]: https://github.com/bytedance/deer-flow/pull/4972 [#4977]: https://github.com/bytedance/deer-flow/pull/4977 +[#4980]: https://github.com/bytedance/deer-flow/pull/4980 +[#4983]: https://github.com/bytedance/deer-flow/pull/4983 [#4984]: https://github.com/bytedance/deer-flow/pull/4984 [#4986]: https://github.com/bytedance/deer-flow/pull/4986 +[#4987]: https://github.com/bytedance/deer-flow/pull/4987 +[#4995]: https://github.com/bytedance/deer-flow/pull/4995 +[#4998]: https://github.com/bytedance/deer-flow/pull/4998 [#5003]: https://github.com/bytedance/deer-flow/pull/5003 [#5006]: https://github.com/bytedance/deer-flow/pull/5006 [#5008]: https://github.com/bytedance/deer-flow/pull/5008 @@ -2111,7 +3262,9 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#5022]: https://github.com/bytedance/deer-flow/pull/5022 [#5023]: https://github.com/bytedance/deer-flow/pull/5023 [#5025]: https://github.com/bytedance/deer-flow/pull/5025 +[#5026]: https://github.com/bytedance/deer-flow/pull/5026 [#5027]: https://github.com/bytedance/deer-flow/pull/5027 +[#5028]: https://github.com/bytedance/deer-flow/pull/5028 [#5030]: https://github.com/bytedance/deer-flow/pull/5030 [#5031]: https://github.com/bytedance/deer-flow/pull/5031 [#5036]: https://github.com/bytedance/deer-flow/pull/5036 @@ -2125,6 +3278,7 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#5056]: https://github.com/bytedance/deer-flow/pull/5056 [#5057]: https://github.com/bytedance/deer-flow/pull/5057 [#5059]: https://github.com/bytedance/deer-flow/pull/5059 +[#5062]: https://github.com/bytedance/deer-flow/pull/5062 [#5064]: https://github.com/bytedance/deer-flow/pull/5064 [#5066]: https://github.com/bytedance/deer-flow/pull/5066 [#5069]: https://github.com/bytedance/deer-flow/pull/5069 @@ -2143,31 +3297,19 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#5103]: https://github.com/bytedance/deer-flow/pull/5103 [#5105]: https://github.com/bytedance/deer-flow/pull/5105 [#5109]: https://github.com/bytedance/deer-flow/pull/5109 +[#5110]: https://github.com/bytedance/deer-flow/pull/5110 +[#5111]: https://github.com/bytedance/deer-flow/pull/5111 [#5112]: https://github.com/bytedance/deer-flow/pull/5112 [#5117]: https://github.com/bytedance/deer-flow/pull/5117 [#5119]: https://github.com/bytedance/deer-flow/pull/5119 -[#5133]: https://github.com/bytedance/deer-flow/pull/5133 -[#5136]: https://github.com/bytedance/deer-flow/pull/5136 -[#5239]: https://github.com/bytedance/deer-flow/pull/5239 -[#3183]: https://github.com/bytedance/deer-flow/pull/3183 -[#4726]: https://github.com/bytedance/deer-flow/pull/4726 -[#4729]: https://github.com/bytedance/deer-flow/pull/4729 -[#4884]: https://github.com/bytedance/deer-flow/pull/4884 -[#4901]: https://github.com/bytedance/deer-flow/pull/4901 -[#4944]: https://github.com/bytedance/deer-flow/pull/4944 -[#4952]: https://github.com/bytedance/deer-flow/pull/4952 -[#4980]: https://github.com/bytedance/deer-flow/pull/4980 -[#4995]: https://github.com/bytedance/deer-flow/pull/4995 -[#5026]: https://github.com/bytedance/deer-flow/pull/5026 -[#5028]: https://github.com/bytedance/deer-flow/pull/5028 -[#5062]: https://github.com/bytedance/deer-flow/pull/5062 -[#5110]: https://github.com/bytedance/deer-flow/pull/5110 -[#5111]: https://github.com/bytedance/deer-flow/pull/5111 [#5123]: https://github.com/bytedance/deer-flow/pull/5123 +[#5133]: https://github.com/bytedance/deer-flow/pull/5133 [#5134]: https://github.com/bytedance/deer-flow/pull/5134 +[#5136]: https://github.com/bytedance/deer-flow/pull/5136 [#5137]: https://github.com/bytedance/deer-flow/pull/5137 [#5141]: https://github.com/bytedance/deer-flow/pull/5141 [#5145]: https://github.com/bytedance/deer-flow/pull/5145 +[#5148]: https://github.com/bytedance/deer-flow/pull/5148 [#5149]: https://github.com/bytedance/deer-flow/pull/5149 [#5152]: https://github.com/bytedance/deer-flow/pull/5152 [#5153]: https://github.com/bytedance/deer-flow/pull/5153 @@ -2175,11 +3317,14 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#5155]: https://github.com/bytedance/deer-flow/pull/5155 [#5156]: https://github.com/bytedance/deer-flow/pull/5156 [#5159]: https://github.com/bytedance/deer-flow/pull/5159 +[#5162]: https://github.com/bytedance/deer-flow/pull/5162 [#5163]: https://github.com/bytedance/deer-flow/pull/5163 [#5164]: https://github.com/bytedance/deer-flow/pull/5164 [#5166]: https://github.com/bytedance/deer-flow/pull/5166 +[#5167]: https://github.com/bytedance/deer-flow/pull/5167 [#5168]: https://github.com/bytedance/deer-flow/pull/5168 [#5170]: https://github.com/bytedance/deer-flow/pull/5170 +[#5178]: https://github.com/bytedance/deer-flow/pull/5178 [#5181]: https://github.com/bytedance/deer-flow/pull/5181 [#5183]: https://github.com/bytedance/deer-flow/pull/5183 [#5185]: https://github.com/bytedance/deer-flow/pull/5185 @@ -2188,32 +3333,147 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#5197]: https://github.com/bytedance/deer-flow/pull/5197 [#5206]: https://github.com/bytedance/deer-flow/pull/5206 [#5209]: https://github.com/bytedance/deer-flow/pull/5209 +[#5214]: https://github.com/bytedance/deer-flow/pull/5214 +[#5216]: https://github.com/bytedance/deer-flow/pull/5216 +[#5217]: https://github.com/bytedance/deer-flow/pull/5217 [#5219]: https://github.com/bytedance/deer-flow/pull/5219 +[#5224]: https://github.com/bytedance/deer-flow/pull/5224 +[#5225]: https://github.com/bytedance/deer-flow/pull/5225 +[#5227]: https://github.com/bytedance/deer-flow/pull/5227 [#5228]: https://github.com/bytedance/deer-flow/pull/5228 [#5232]: https://github.com/bytedance/deer-flow/pull/5232 [#5234]: https://github.com/bytedance/deer-flow/pull/5234 [#5236]: https://github.com/bytedance/deer-flow/pull/5236 +[#5239]: https://github.com/bytedance/deer-flow/pull/5239 [#5244]: https://github.com/bytedance/deer-flow/pull/5244 [#5245]: https://github.com/bytedance/deer-flow/pull/5245 +[#5247]: https://github.com/bytedance/deer-flow/pull/5247 +[#5249]: https://github.com/bytedance/deer-flow/pull/5249 +[#5254]: https://github.com/bytedance/deer-flow/pull/5254 +[#5255]: https://github.com/bytedance/deer-flow/pull/5255 [#5261]: https://github.com/bytedance/deer-flow/pull/5261 +[#5264]: https://github.com/bytedance/deer-flow/pull/5264 [#5265]: https://github.com/bytedance/deer-flow/pull/5265 +[#5275]: https://github.com/bytedance/deer-flow/pull/5275 [#5278]: https://github.com/bytedance/deer-flow/pull/5278 [#5280]: https://github.com/bytedance/deer-flow/pull/5280 [#5281]: https://github.com/bytedance/deer-flow/pull/5281 +[#5282]: https://github.com/bytedance/deer-flow/pull/5282 +[#5283]: https://github.com/bytedance/deer-flow/pull/5283 [#5284]: https://github.com/bytedance/deer-flow/pull/5284 +[#5286]: https://github.com/bytedance/deer-flow/pull/5286 [#5287]: https://github.com/bytedance/deer-flow/pull/5287 +[#5288]: https://github.com/bytedance/deer-flow/pull/5288 +[#5289]: https://github.com/bytedance/deer-flow/pull/5289 +[#5291]: https://github.com/bytedance/deer-flow/pull/5291 +[#5293]: https://github.com/bytedance/deer-flow/pull/5293 +[#5294]: https://github.com/bytedance/deer-flow/pull/5294 +[#5296]: https://github.com/bytedance/deer-flow/pull/5296 +[#5299]: https://github.com/bytedance/deer-flow/pull/5299 +[#5304]: https://github.com/bytedance/deer-flow/pull/5304 +[#5305]: https://github.com/bytedance/deer-flow/pull/5305 +[#5306]: https://github.com/bytedance/deer-flow/pull/5306 +[#5309]: https://github.com/bytedance/deer-flow/pull/5309 +[#5310]: https://github.com/bytedance/deer-flow/pull/5310 +[#5312]: https://github.com/bytedance/deer-flow/pull/5312 +[#5315]: https://github.com/bytedance/deer-flow/pull/5315 +[#5316]: https://github.com/bytedance/deer-flow/pull/5316 +[#5318]: https://github.com/bytedance/deer-flow/pull/5318 [#5321]: https://github.com/bytedance/deer-flow/pull/5321 +[#5323]: https://github.com/bytedance/deer-flow/pull/5323 +[#5324]: https://github.com/bytedance/deer-flow/pull/5324 +[#5326]: https://github.com/bytedance/deer-flow/pull/5326 +[#5329]: https://github.com/bytedance/deer-flow/pull/5329 +[#5332]: https://github.com/bytedance/deer-flow/pull/5332 [#5338]: https://github.com/bytedance/deer-flow/pull/5338 +[#5341]: https://github.com/bytedance/deer-flow/pull/5341 +[#5344]: https://github.com/bytedance/deer-flow/pull/5344 +[#5347]: https://github.com/bytedance/deer-flow/pull/5347 +[#5350]: https://github.com/bytedance/deer-flow/pull/5350 [#5353]: https://github.com/bytedance/deer-flow/pull/5353 [#5357]: https://github.com/bytedance/deer-flow/pull/5357 +[#5359]: https://github.com/bytedance/deer-flow/pull/5359 +[#5363]: https://github.com/bytedance/deer-flow/pull/5363 +[#5367]: https://github.com/bytedance/deer-flow/pull/5367 +[#5369]: https://github.com/bytedance/deer-flow/pull/5369 +[#5371]: https://github.com/bytedance/deer-flow/pull/5371 +[#5373]: https://github.com/bytedance/deer-flow/pull/5373 +[#5374]: https://github.com/bytedance/deer-flow/pull/5374 [#5375]: https://github.com/bytedance/deer-flow/pull/5375 +[#5377]: https://github.com/bytedance/deer-flow/pull/5377 +[#5380]: https://github.com/bytedance/deer-flow/pull/5380 +[#5381]: https://github.com/bytedance/deer-flow/pull/5381 +[#5382]: https://github.com/bytedance/deer-flow/pull/5382 +[#5384]: https://github.com/bytedance/deer-flow/pull/5384 +[#5388]: https://github.com/bytedance/deer-flow/pull/5388 +[#5389]: https://github.com/bytedance/deer-flow/pull/5389 +[#5390]: https://github.com/bytedance/deer-flow/pull/5390 +[#5392]: https://github.com/bytedance/deer-flow/pull/5392 [#5393]: https://github.com/bytedance/deer-flow/pull/5393 +[#5395]: https://github.com/bytedance/deer-flow/pull/5395 +[#5396]: https://github.com/bytedance/deer-flow/pull/5396 +[#5397]: https://github.com/bytedance/deer-flow/pull/5397 +[#5399]: https://github.com/bytedance/deer-flow/pull/5399 [#5401]: https://github.com/bytedance/deer-flow/pull/5401 +[#5402]: https://github.com/bytedance/deer-flow/pull/5402 [#5403]: https://github.com/bytedance/deer-flow/pull/5403 +[#5404]: https://github.com/bytedance/deer-flow/pull/5404 +[#5405]: https://github.com/bytedance/deer-flow/pull/5405 +[#5406]: https://github.com/bytedance/deer-flow/pull/5406 +[#5407]: https://github.com/bytedance/deer-flow/pull/5407 +[#5408]: https://github.com/bytedance/deer-flow/pull/5408 +[#5410]: https://github.com/bytedance/deer-flow/pull/5410 [#5411]: https://github.com/bytedance/deer-flow/pull/5411 +[#5413]: https://github.com/bytedance/deer-flow/pull/5413 +[#5415]: https://github.com/bytedance/deer-flow/pull/5415 +[#5416]: https://github.com/bytedance/deer-flow/pull/5416 [#5418]: https://github.com/bytedance/deer-flow/pull/5418 [#5419]: https://github.com/bytedance/deer-flow/pull/5419 +[#5421]: https://github.com/bytedance/deer-flow/pull/5421 +[#5422]: https://github.com/bytedance/deer-flow/pull/5422 +[#5424]: https://github.com/bytedance/deer-flow/pull/5424 +[#5426]: https://github.com/bytedance/deer-flow/pull/5426 [#5427]: https://github.com/bytedance/deer-flow/pull/5427 +[#5428]: https://github.com/bytedance/deer-flow/pull/5428 +[#5429]: https://github.com/bytedance/deer-flow/pull/5429 [#5431]: https://github.com/bytedance/deer-flow/pull/5431 +[#5432]: https://github.com/bytedance/deer-flow/pull/5432 +[#5433]: https://github.com/bytedance/deer-flow/pull/5433 +[#5436]: https://github.com/bytedance/deer-flow/pull/5436 +[#5439]: https://github.com/bytedance/deer-flow/pull/5439 +[#5440]: https://github.com/bytedance/deer-flow/pull/5440 +[#5441]: https://github.com/bytedance/deer-flow/pull/5441 +[#5442]: https://github.com/bytedance/deer-flow/pull/5442 +[#5443]: https://github.com/bytedance/deer-flow/pull/5443 +[#5444]: https://github.com/bytedance/deer-flow/pull/5444 +[#5446]: https://github.com/bytedance/deer-flow/pull/5446 [#5447]: https://github.com/bytedance/deer-flow/pull/5447 +[#5448]: https://github.com/bytedance/deer-flow/pull/5448 +[#5449]: https://github.com/bytedance/deer-flow/pull/5449 +[#5451]: https://github.com/bytedance/deer-flow/pull/5451 +[#5453]: https://github.com/bytedance/deer-flow/pull/5453 +[#5454]: https://github.com/bytedance/deer-flow/pull/5454 +[#5455]: https://github.com/bytedance/deer-flow/pull/5455 +[#5456]: https://github.com/bytedance/deer-flow/pull/5456 +[#5458]: https://github.com/bytedance/deer-flow/pull/5458 +[#5459]: https://github.com/bytedance/deer-flow/pull/5459 +[#5461]: https://github.com/bytedance/deer-flow/pull/5461 +[#5463]: https://github.com/bytedance/deer-flow/pull/5463 +[#5465]: https://github.com/bytedance/deer-flow/pull/5465 +[#5467]: https://github.com/bytedance/deer-flow/pull/5467 +[#5468]: https://github.com/bytedance/deer-flow/pull/5468 [#5469]: https://github.com/bytedance/deer-flow/pull/5469 +[#5470]: https://github.com/bytedance/deer-flow/pull/5470 +[#5474]: https://github.com/bytedance/deer-flow/pull/5474 +[#5477]: https://github.com/bytedance/deer-flow/pull/5477 +[#5478]: https://github.com/bytedance/deer-flow/pull/5478 +[#5479]: https://github.com/bytedance/deer-flow/pull/5479 +[#5480]: https://github.com/bytedance/deer-flow/pull/5480 +[#5483]: https://github.com/bytedance/deer-flow/pull/5483 +[#5485]: https://github.com/bytedance/deer-flow/pull/5485 +[#5486]: https://github.com/bytedance/deer-flow/pull/5486 +[#5488]: https://github.com/bytedance/deer-flow/pull/5488 +[#5492]: https://github.com/bytedance/deer-flow/pull/5492 +[#5496]: https://github.com/bytedance/deer-flow/pull/5496 +[#5501]: https://github.com/bytedance/deer-flow/pull/5501 +[#5504]: https://github.com/bytedance/deer-flow/pull/5504