Skip to content

fix(deps): update dependency agents to ^0.14.0#88

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/agents-0.x
Open

fix(deps): update dependency agents to ^0.14.0#88
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/agents-0.x

Conversation

@renovate

@renovate renovate Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
agents (source) ^0.9.0^0.14.0 age confidence

Release Notes

cloudflare/agents (agents)

v0.14.5

Compare Source

Patch Changes
  • #​1613 124a47a Thanks @​threepointone! - Introduce the first Think framework layer for convention-driven agent apps.

    This release adds a manifest-driven Vite plugin that discovers agents from the
    agents/ directory, generates a Worker entrypoint and virtual framework
    modules, derives stable Durable Object class names, and merges framework-owned
    Worker config defaults with user Wrangler config. It also keeps the Think Vite
    plugin usable directly in normal Vite plugin arrays.

    The framework now supports optional app server entries, manifest-scoped friendly
    agent and sub-agent routing, deterministic route surfaces, colocated skill
    detection, Worker Loader requirement diagnostics, and explicit diagnostics for
    unsupported nested sub-agent conventions. Think currently supports top-level
    agents and one sub-agent layer; deeper nesting is rejected with guidance so that
    the routing and lifecycle model can be designed deliberately.

    This framework layer is experimental: both the Vite plugin (once, on build
    start) and the think CLI (on startup) emit a notice that the API may change
    or be removed in any release. The core Think agent runtime is unchanged.

    The Think CLI now includes think init, think inspect, and think types.
    think init scaffolds a minimal Workers/Vite Think app, safely handles prompted
    or named target directories, refuses unsafe migrations, and installs npm
    dependencies by default. think inspect exposes manifest/config diagnostics in
    text or JSON, while think types generates Think-owned declarations and can
    optionally compose with Wrangler type generation.

    This release also adds host-framework coverage for React Router and TanStack
    Start, updates examples to use the convention-first framework shape, and hardens
    Agents/worker-bundler virtual modules for bundled skill compatibility.

  • #​1613 124a47a Thanks @​threepointone! - Compile skill scripts ahead of time and remove the in-Worker bundler (drops ~14MB of esbuild-wasm from Worker bundles).

    Skill scripts are now always compiled to self-contained JavaScript before they run, and the runtime no longer ships an in-Worker bundler (@cloudflare/worker-bundler is no longer a dependency of agents):

    • The Agents Vite plugin compiles bundled skill scripts (scripts/*.ts/.tsx/.js/.mjs) with esbuild at build time — resolving sibling imports and stripping TypeScript — and marks them precompiled.
    • Skills served from R2 or other dynamic sources must be compiled before upload. A new compileSkillScript helper is exported from agents/skills/compile for use in your publish/upload tooling.
    • At runtime, a skill script that still needs compiling (raw TypeScript or a multi-file skill that wasn't bundled) throws a clear "must be compiled to a self-contained JavaScript module" error instead of silently bundling in-Worker.

    Breaking: if you ship raw TypeScript or multi-file skill scripts to R2 (or another dynamic source) and relied on the in-Worker bundler to compile them at runtime, bundle them ahead of time (e.g. with compileSkillScript) before upload. Bundled skills handled by the Vite plugin require no changes. The previously-added stubWorkerBundler option has been removed (there is nothing left to stub).

v0.14.4

Compare Source

Patch Changes
  • #​1693 6496c80 Thanks @​threepointone! - Fix AIChatAgent orphaned-stream recovery merging a new assistant turn into the previous assistant message (#​1691).

    When a stream was interrupted before its final assistant message was persisted (Durable Object hibernation, deploy churn, isolate restart, reconnect), orphan recovery reconstructed the message from stored chunks. If those chunks carried no provider start.messageId — the common case — recovery fell back to the last assistant message in history. That is correct for a continuation, but wrong for a normal new turn after a later user message: the recovered chunks for the new turn were appended onto the previous assistant message, corrupting both the persisted transcript and future model context.

    The assistant message id allocated when a stream starts is now persisted in the resumable-stream metadata (ResumableStream.start() records message_id). When the reconstructed chunks carry no provider start.messageId — the common case, and the one that triggered the bug — orphan recovery now uses this stored id instead of the last-assistant fallback, so a new turn becomes its own message and a continuation still merges into the message it was extending (it stored the cloned last-assistant id). A provider start.messageId, when present, still wins, matching the live path which adopts it for new turns. Stream rows written before this release have no stored id and keep the previous behavior (provider id if present, otherwise the last assistant message). The metadata migration adds a single column, guarded by a schema check so it runs only once.

    This also fixes two related variants of the same corruption on the durable (chatRecovery) continuation path:

    • When a stream was persisted early (e.g. at a tool-approval pause) and then recovered, the merge re-appended chunks it had already stored, leaving two parts for the same tool call. Recovery now skips reconstructed parts whose toolCallId already exists on the message.
    • When a new turn was interrupted before any assistant part was persisted — either because it was cut off in the window before the first chunk materialized, or because onChatRecovery returned { persist: false } — recovery would "continue" it by cloning the previous assistant message, merging the new turn into it. Recovery now detects that the conversation leaf is still the user message (no partial to continue) and re-runs the turn fresh, so it becomes its own message.

    @cloudflare/think is unaffected — its session-tree recovery already allocates a distinct message id per orphan and never falls back to the last assistant message.

v0.14.3

Compare Source

Patch Changes
  • #​1686 1e49880 Thanks @​threepointone! - Batch and pack chat-persistence SQLite writes to reduce rows written and round-trips.
    • agents: ResumableStream now packs each buffered group of stream chunks into a single SQLite row (a JSON array of chunk bodies) instead of writing one row per chunk. Single-chunk and large-chunk segments are stored unwrapped, and a per-segment byte cap keeps rows within the 2 MB SQLite row limit. This cuts chunk rows written / stored / scanned-on-replay by up to ~10×. Reads (replay, orphan reconstruction, getStreamChunks) transparently unpack both packed segments and legacy per-chunk rows, so existing stored data keeps working. Adds shared buildInClauseStrings and MAX_BOUND_PARAMS helpers exported from agents/chat.
    • @cloudflare/ai-chat: message cleanup (stale-row pruning and maxPersistedMessages enforcement) previously issued one DELETE per row in a loop; it now deletes rows in batched DELETE ... WHERE id IN (...) queries (capped at 100 bound parameters per query).
    • @cloudflare/think: deleteSubmissions() cleanup previously issued one DELETE per terminal submission (up to 500 per call); it now deletes rows in batched DELETE ... WHERE submission_id IN (...) queries.
    • @cloudflare/ai-chat & @cloudflare/think: chat-recovery incident TTL sweep previously deleted each stale incident with a separate awaited storage.delete(key) (which also defeats Durable Object write-coalescing); it now deletes incidents in batched storage.delete(keys) calls (up to 128 keys per call).

v0.14.2

Compare Source

Patch Changes
  • #​1684 ab6dd95 Thanks @​threepointone! - warn when chatRecovery is configured in onStart() (applied too late for wake recovery)

    On every Durable Object wake the SDK evaluates chat-recovery budgets — and may seal an interrupted turn, firing onExhaustedbefore the user's onStart() runs (_checkRunFibers() is ordered ahead of onStart()). A chatRecovery config produced inside onStart() is therefore read as the built-in defaults at the moment recovery decides, so a configured maxRecoveryWork / shouldKeepRecovering / onExhausted silently never applies to the recovery that matters.

    This is now documented on ChatRecoveryConfig and the chatRecovery fields of Think / AIChatAgent, and the SDK logs a one-time warning if it detects chatRecovery being reassigned during onStart(). The warning fires both for a custom config object and for chatRecovery = true (enabling recovery / its defaults too late); assigning false (disabling) in onStart() is intentionally not warned, since recovery already ran with the pre-onStart() value and disabling it afterward is a benign no-op for that wake. The fix is to assign chatRecovery as a class field or in the constructor.

  • #​1672 f96a2ba Thanks @​threepointone! - fix(chat-recovery): a turn making forward progress now survives unbounded deploy churn; add a work budget + shouldKeepRecovering runaway guard

    Durable chat recovery used to bound a single incident with a non-resetting 15-minute wall-clock ceiling (CHAT_RECOVERY_MAX_WINDOW_MS). That ceiling was overloaded — it served as both a recovery-duration bound and a runaway-loop guard — and it terminated healthy, actively-progressing turns that simply took longer than 15 minutes of wall-clock to finish while being repeatedly interrupted by a dense deploy window, sealing them with reason="max_recovery_window_exceeded" and discarding completed work.

    The two jobs are now decoupled (see design/rfc-chat-recovery-work-budget.md):

    • Duration is no longer a bound for a progressing turn. The non-resetting wall-clock ceiling is removed. A turn that keeps producing content survives unbounded deploy churn. Stuck turns are still sealed by the no-progress window (5 min, resets on progress); tight no-progress alarm loops by the attempt cap.
    • New runaway-loop guard, keyed to work, not time. The existing durable, monotonic, reconnect-immune progress counter is reused as a work meter. chatRecovery.maxRecoveryWork caps the produced content/tool units since an incident opened; exceeding it seals with reason="work_budget_exceeded". Defaults to Infinity — the SDK ships the mechanism but imposes no implicit cap, so it never terminates a progressing turn on its own.
    • New caller predicate. chatRecovery.shouldKeepRecovering(ctx) is consulted per recovery attempt from the second onward (only when no hard bound has already sealed the incident); returning false seals with reason="recovery_aborted". This is where integrators express token/cost/step budgets the SDK should not hardcode. A throwing predicate is logged and treated as "keep recovering".
    • The no-progress timeout is now configurable. chatRecovery.noProgressTimeoutMs (default 5 min, resets on progress) is the primary stuck-turn bound, now overridable per agent instead of a hardcoded constant.

    New public types from agents/chat: ChatRecoveryProgressContext. New ChatRecoveryConfig fields: maxRecoveryWork, shouldKeepRecovering, noProgressTimeoutMs. ChatRecoveryExhaustedContext.reason gains work_budget_exceeded and recovery_aborted; max_recovery_window_exceeded is retained as an open-string value but is no longer emitted.

    Both @cloudflare/ai-chat and @cloudflare/think (which carries its own copy of the recovery engine) are updated identically. Defaults are unchanged except that a progressing turn is no longer terminated by wall-clock age.

  • #​1668 d40cc8a Thanks @​ghostwriternr! - Fix RPC resource leaks in workflows.

    Workflows that use waitForApproval() or ThinkWorkflow.prompt() now release their RPC stubs promptly, preventing resource leaks and the associated "RPC stub was not disposed" warnings in your logs.

  • #​1679 c8d1d32 Thanks @​threepointone! - fix(sub-agents): a facet sub-agent no longer touches the root DO's WebSockets, fixing a production-only "Cannot perform I/O on behalf of a different Durable Object (Native)" crash (#​1677)

    A sub-agent (facet) that called setState(), broadcast(), or otherwise enumerated connections — directly or indirectly via the internal _broadcastProtocol() — could crash in production with Cannot perform I/O on behalf of a different Durable Object. ... (I/O type: Native). It reproduced when the root Agent held a live (hibernatable) WebSocket connection and the child facet was freshly bootstrapped; it never reproduced in wrangler dev/miniflare, which made it hard to catch.

    Root cause: the Agent overrides of getConnections() and getConnection() fell through to super.getConnections() / super.getConnection() for facets too. On a facet, that resolves to the host/root DO's hibernatable WebSockets, and reading their attachments from the facet's I/O context is a cross-DO native I/O access that workerd aborts. setState() tripped it only incidentally, because _broadcastProtocol() enumerates connections to compute its exclude list before sending anything.

    Fix: a facet's client connections are all virtual (real sockets owned by the root and bridged in), so getConnections()/getConnection() now return only the facet's virtual sub-agent connections and never fall through to the host DO's sockets. Delivery of facet state updates to clients connected directly to the sub-agent is unchanged.

  • #​1670 5d64940 Thanks @​threepointone! - Fix: a deploy that interrupts an in-flight runAgentTool child no longer abandons the still-running child as interrupted.

    Parent recovery re-attaches to a still-running child and tails it to its real terminal. Previously that re-attach used a flat 120s wall-clock budget that was not reset by the child's forward progress, so a healthy child whose recovery legitimately ran longer than the budget was sealed interrupted (and its already-completed work re-run from scratch), even while it was actively streaming.

    The re-attach budget is now progress-keyed: it bounds how long the parent waits with no forward progress from the child (resetting on every forwarded chunk), so a genuinely hung/silent child still seals interrupted after one no-progress window and can never block recovery forever, while a healthy child that keeps streaming is followed through to terminal. The parent re-arms (opens a fresh tail) only when the child's stream closes cleanly while it is still advancing — i.e. a re-evicted-but-progressing child. A full no-progress window (the child went silent) seals no-progress immediately even if the child streamed earlier in that window; it no longer grants a bonus window. This is both the honest stall signal and what keeps at most one pending tail reader alive per re-attach (no per-cycle reader accumulation).

    @cloudflare/think and @cloudflare/ai-chat additionally finalize a child facet's own agent-tool run row as soon as its recovered turn settles — regardless of whether recovery took the continue path (_chatRecoveryContinue) or the pre-stream retry path (_chatRecoveryRetry) — so a re-attached parent collects the terminal result immediately instead of waiting out a full no-progress window after the child has already finished.

    This release also adds:

    • Typed interrupted cause. RunAgentToolResult, the agentTool() AgentToolFailure envelope, the onAgentToolFinish lifecycle result, and the agent-tool-event wire event (kind "interrupted") now carry a machine-readable reason (AgentToolInterruptedReason: "no-progress" | "window-exceeded" | "not-tailable" | "inspect-timeout" | "inspect-failed" | "recovery-deadline") and a childStillRunning boolean on interrupted results, so callers (and UIs) can branch on why a run was abandoned (and whether the child is still running) instead of pattern-matching the human-readable error prose. retryable stays coarse (always true for interrupted); refine with reason / childStillRunning. These fields are persisted (schema bump), so they survive a reconnect replay — a client that reconnects after an interrupt reconstructs the same reason / childStillRunning a live client saw, rather than undefined. The persisted cause is cleared when a soft interrupted row is later repaired to completed/error.
    • Configurable re-attach budgets. Two new public AgentStaticOptionsagentToolReattachNoProgressTimeoutMs (default 120000, the progress-keyed no-progress budget) and agentToolReattachMaxWindowMs (default Infinity — no implicit wall-clock cap) — let an Agent tune re-attach. The hard ceiling defaults to uncapped to mirror chat-recovery's maxRecoveryWork: Infinity: a re-attached parent follows a healthy, still-advancing child for as long as it makes progress — exactly as it would on the live (never-evicted) path — so it never abandons a long-running-but-healthy child that simply outlasts a fixed wall clock under deploy churn. A hung/silent child is bounded by the no-progress budget; a content-runaway is bounded uniformly (live and recovery) by the child's own maxRecoveryWork / shouldKeepRecovering. Integrators that want a hard wall-clock cap (and the window-exceeded child teardown it triggers) can set agentToolReattachMaxWindowMs to a finite value. Symmetrically, setting agentToolReattachNoProgressTimeoutMs to Infinity now means "never seal on no-progress" (a silent-but-alive child is followed until its stream closes or the hard ceiling fires) instead of silently skipping the wait — 0 remains the "don't wait, collect only an already-terminal child" sentinel.
    • Give-up teardown (ceiling only). When the parent gives up at the hard window-exceeded ceiling — where the child has had its full recovery window and is truly exhausted — it now cancels the child (childStillRunning: false) so it stops consuming a fiber / keep-alive. no-progress give-ups stay soft (childStillRunning: true): the child is left running so a re-issue can still re-attach and repair it if it self-heals, preserving the repair-on-re-issue path. In both @cloudflare/think and @cloudflare/ai-chat, cancelAgentToolRun also aborts an in-flight chat-recovery turn (not just the original in-isolate run) and releases live tails — Think sweeps its _submissionAbortControllers, ai-chat its request AbortRegistry (abortAllRequests) — so a torn-down child stops grinding instead of finishing an orphaned recovered turn.
  • #​1680 8f9500a Thanks @​threepointone! - Remove the now-redundant _suppressProtocolBroadcasts facet-bootstrap guard.

    This flag was added in #​1425 to stop _broadcastProtocol() from enumerating the
    parent DO's WebSockets during facet bootstrap (the cross-DO Native I/O crash,
    #​1410/#​1677). The proper fix in #​1679 makes getConnections()/broadcast()
    facet-safe at the source — on a facet they return only virtual sub-agent
    connections and route through the parent bridge, never touching the parent's own
    sockets. With that, suppressing broadcasts during bootstrap is unnecessary, and
    removing it also lets legitimate state sync run during the bootstrap window.

    The separate request/WebSocket/email native-handle clearing from #​1425 is
    retained, since #​1679 does not cover that vector.

  • #​1675 d915bc6 Thanks @​threepointone! - The skill runner now imports just-bash and @cloudflare/codemode statically instead of dynamically, and both have moved from optional peer dependencies to regular dependencies of agents. The dynamic imports were ineffective in bundled Workers (the bundler includes them eagerly regardless) and triggered INEFFECTIVE_DYNAMIC_IMPORT warnings when bundled alongside @cloudflare/think, which imports them statically. @cloudflare/think also now statically imports its internal ExtensionManager instead of dynamically, removing the third such warning.

  • #​1662 df6c0d6 Thanks @​threepointone! - Add opt-in recovery for mid-turn context-window overflow.

    Compaction only fires between turns (Session.compactAfter checks the threshold on appendMessage). A single long, tool-heavy turn grows the prompt step-by-step inside one streamText loop and can exceed the model's context window mid-turn, before the next pre-turn check — the provider then 400s ("prompt is too long" / context_length_exceeded) and the turn dies terminally. Think deliberately ships no provider-specific error matching, so it could neither detect nor recover from this.

    This adds opt-in, provider-agnostic recovery (all default off — no behavior change unless enabled), configured through a single contextOverflow property on Think:

    • classifyChatError(error, ctx) — the app maps a raw error (or the in-stream error string) to a ChatErrorClassification ("context_overflow" | "rate_limit" | "transient" | "fatal" | "unknown"). Same framework-owns-the-mechanism / app-owns-the-provider-knowledge split as tokenCounter. The classification is also threaded to onChatError/observers via ChatErrorContext.classification. The bundled, exported defaultContextOverflowClassifier covers the common providers (Anthropic, OpenAI, Google, Bedrock, …) for apps that do not need custom classification.
    • contextOverflow.reactive + contextOverflow.maxRetries — when a turn fails with a context_overflow the app classified, Think discards the truncated partial, runs session.compact(), and re-runs the turn (bounded) from the compacted history instead of dying. The partial is intentionally not persisted: the retry restarts the turn from scratch, so keeping the cut-off partial would orphan a half-finished assistant message beside the recovered answer (and duplicate any tool work the retry re-issues). A no-op compaction or a spent budget surfaces the overflow terminally through onChatError with classification: "context_overflow" — never a silent end, never an infinite loop. Wired into the WebSocket, chat()/RPC, and programmatic (saveMessages/submitMessages) turn paths.
    • contextOverflow.proactive — a { maxInputTokens, headroom?, maxCompactions? } pre-step guard: when the previous step's model-reported usage.inputTokens crosses maxInputTokens * (headroom ?? 0.9), Think compacts in place and feeds the recompacted history into the upcoming step, heading off the provider 400 before it happens. Keys off model-reported usage (every provider reports it), not provider error strings. Bounded per step loop by its own maxCompactions (default 1, independent of the reactive maxRetries budget).

    Also adds a chat:context:compacted observability event (agents) emitted (once) on both proactive and reactive compaction.

    Notes:

    • Provider context-overflow errors always surface as in-stream error parts (confirmed against the AI SDK: streamText re-enqueues even top-level rejections as { type: "error" } fullStream parts, and toUIMessageStream passes them through without throwing), so the in-stream seam catches them on every path; the thrown-error catch path does not need separate wiring.
    • Recovery effectiveness depends on the app's compaction config — a no-op compaction cannot rescue an over-budget turn (handled gracefully: terminal, not a loop). A one-time warning fires if contextOverflow.reactive is enabled but classifyChatError was never overridden.
  • #​1675 d915bc6 Thanks @​threepointone! - The agents/vite plugin now stubs turndown by default. turndown (pulled in transitively by just-bash for the workspace bash tool and skill runner) runs a top-level require() in its Node DOM fallback, which throws ReferenceError: require is not defined at Worker startup — even when the bash tool is never used. The plugin replaces it with an inert stub so Workers deploys stay clean. Opt out with agents({ stubTurndown: false }) if your app uses turndown directly.

v0.14.1

Compare Source

Patch Changes
  • #​1659 f99f890 Thanks @​threepointone! - Recover one-shot scheduled work (alarms) killed by a "This script has been upgraded…" deploy/code-update, not just "Durable Object reset because its code was updated.".

    _executeScheduleCallback only re-runs a one-shot schedule row after a superseded-isolate error if the error matched /reset because its code was updated/i. The platform also surfaces the same failure class as "This script has been upgraded. Please send a new request to connect to the new version." (a stub/connection to a superseded script), which fell through to the swallow-and-delete branch — the one-shot row was deleted and the work abandoned. For a queued submission this orphaned the pending row with no driver (no alarm, no retry) until something unrelated woke the Durable Object, leaving the user on an indefinite spinner.

    The superseded-isolate matcher now recognizes both messages, so either causes the row to be preserved and re-run on the fresh isolate under the at-least-once alarm guarantee. "Network connection lost." is intentionally not included (it is a connection error that may succeed on in-process retry, not an isolate replacement).

  • #​1661 41315b6 Thanks @​threepointone! - Enforce the tool_use.input invariant at the chat write boundary.

    A streamed tool call that finishes with no input_json_delta events (the model called the tool with no args), or whose input surfaces as a stringified JSON blob, could persist a non-object inputnull, undefined, "", an array, or a raw string. The Anthropic Messages API requires tool_use.input to be a JSON object and rejects every subsequent turn with tool_use.input: Input should be an object (verified against the live API: {} → 200, but "", [], and [{...}] all → 400). Because the bad shape lives in durable storage, the session is wedged across reconnects, redeploys, and DO evictions.

    applyChunkToParts (the shared accumulator used by @cloudflare/ai-chat and @cloudflare/think) now normalizes the finalized tool input on tool-input-available / tool-input-error: a plain object passes through untouched, a stringified-JSON object is parsed, and everything else (null/undefined/""/arrays/primitives/unparseable strings) collapses to {}. A new normalizeToolInput helper is exported from agents/chat so read-side transcript repair can enforce the same invariant.

  • #​1665 13d6db0 Thanks @​threepointone! - Await Chat SDK state-agent cleanup scheduling during startup so tests and short-lived worker isolates do not leave dangling cleanup work.

  • #​1666 01a0b35 Thanks @​dcartertwo! - Fix MCP OAuth PKCE verifier lookup for overlapping authorization attempts.

    DurableObjectOAuthClientProvider now binds pending PKCE verifiers to the OAuth callback state instead of storing a single verifier per client/server. Callback handling runs token exchange and verifier cleanup in the returned state's context, so older auth windows and retry churn no longer exchange an authorization code with another attempt's verifier.

v0.14.0

Compare Source

Minor Changes
  • #​1623 4c8b371 Thanks @​threepointone! - agentTool() now returns a structured failure envelope instead of an opaque error string, so a parent agent can tell a transient interruption apart from a terminal failure.

    Previously every non-completed sub-agent run collapsed to { ok: false, error: string }. A child that was reset/superseded by a deploy or parent recovery (interrupted) looked identical to a genuine failure or an intentional cancellation, so the parent model would often parrot the interruption text back to the user as if the work had permanently failed.

    The failure value is now AgentToolFailure:

    type AgentToolFailure = {
      ok: false;
      status: "error" | "aborted" | "interrupted";
      error: string; // still human-readable
      retryable: boolean;
    };
    • interruptedretryable: true (the run never reached a logical outcome; re-dispatching can succeed), and now surfaces the underlying interruption reason via error.
    • aborted (intentional cancellation) and error (genuine failure) → retryable: false.

    This is backward compatible for consumers that read ok/error; the new status and retryable fields let an orchestration harness (or a parent prompt convention) re-run an interrupted sub-agent automatically rather than reporting it as final. AgentToolFailure is exported from agents.

  • #​1636 f5a0d00 Thanks @​threepointone! - Expose recovery incident identity and enrich the onExhausted payload so
    products can build a terminal-state policy without re-deriving anything (#​1631).

    • ChatRecoveryContext (the onChatRecovery argument) now includes
      recoveryRootRequestId — the stable request ID for the whole continuation
      chain. Unlike requestId, it doesn't change across chained continuations, so
      it's the right key for per-incident budget tracking / fresh-incident detection
      without re-deriving identity from message IDs.
    • ChatRecoveryExhaustedContext (the onExhausted argument) now carries
      recoveryRootRequestId, terminalMessage (the exact text shown to the user),
      partialText / partialParts (what the turn produced before it was given up
      on), and streamId / createdAt — enough to render or persist a user-facing
      terminal banner AND emit correlated terminal telemetry (e.g. time-since-turn-start,
      stream correlation) directly, without re-deriving anything.

    All fields are additive. Applied across agents (shared types),
    @cloudflare/think, and @cloudflare/ai-chat.

  • #​1584 87006e2 Thanks @​threepointone! - Add a framework-agnostic Agent Skills engine at agents/skills: skill sources (fromManifest, R2), a SkillRegistry that produces a catalog prompt and AI SDK activation tools (activate_skill, read_skill_resource, run_skill_script), binary-safe resource reads, and qualified cross-skill resource paths. Bundled skills are imported through the Agents Vite plugin with the agents:skills specifier (defaulting to a ./skills directory), typed via ambient declarations shipped from agents. @cloudflare/think re-exports the engine as skills and wires getSkills() into the turn; any AI SDK caller (including @cloudflare/ai-chat) can build a SkillRegistry directly.

    Skill loading is resilient: duplicate or failing sources are skipped with a warning (first source wins) instead of throwing. Optional, experimental script execution (skills.runner) runs function-style JavaScript/TypeScript (export default run(input, ctx) with ctx = { skill, files, workspace, tools, output }) plus path-based Python and Bash, all behind a single capability and permission bridge.

  • #​1648 d6827ab Thanks @​threepointone! - Surface a live "recovering…" status to chat clients during durable recovery (#​1620)

    When a durable chat turn is interrupted (a deploy/eviction, or a stream-stall
    watchdog abort) and resumes, clients had no "in progress" signal — the turn
    looked frozen until it completed or a terminal error was replayed. A new
    cf_agent_chat_recovering protocol frame is now broadcast on recovery schedule
    and cleared on every terminal outcome (completed/skipped/failed/exhausted), so
    the indicator can't spin forever. In @cloudflare/think it's also persisted and
    replayed on connect, so a client that joins mid-recovery learns the turn is
    working. useAgentChat exposes a new isRecovering flag (distinct from
    isStreaming — a recovering turn isn't producing tokens yet); most UIs render
    isStreaming || isRecovering as "busy". Backward-compatible: clients that don't
    understand the frame ignore it.

    Note: @cloudflare/ai-chat broadcasts the live signal but does not yet replay
    it on connect (it has no idle-connect hydration path; tracked in #​1645).
    @cloudflare/think has both.

    For recovery telemetry, subscribe to the chat:recovery:* observability events
    and route them to your analytics sink.

  • #​1611 02f9380 Thanks @​threepointone! - Add bounded, observable recovery foundations for durable chat turns and fibers.

    • Add dedicated recovery observability channels/events for fibers, chat recovery, transcript repair, and agent-tool recovery.
    • Bound internal framework fiber recovery hooks and parent agent-tool recovery scans so startup and recovery work cannot wedge indefinitely.
    • Add shared chat recovery incident tracking with attempt counts, configurable chatRecovery defaults, and terminal exhaustion behavior for AIChatAgent and Think. Think recovery now exhausts after six failed attempts by default and sends a terminal error frame instead of spinning indefinitely.
    • Keep the recovery attempt budget bounded even when an interrupted turn flips between retry and continue recovery kinds (the incident identity no longer includes the kind), guard a throwing onExhausted hook so the terminal UX is still delivered, mark incidents failed when the recovery dispatch throws, and reclaim incident records on success plus a TTL sweep for abandoned ones so durable storage does not grow without bound.
    • Bound generic unmanaged fiber recovery with a configurable fiberRecoveryMaxAgeMs so a repeatedly-throwing onFiberRecovered() hook cannot re-trigger forever across restarts.
    • Surface Think post-persist chat request failures through onChatError(error, ctx) and chat:request:failed.
    • Repair incomplete Think tool-call transcripts before provider calls and allow createCompactFunction() to use a supplied token counter for tail budgeting.
  • #​1640 edb126a Thanks @​threepointone! - Re-attach to a still-running sub-agent (agentTool()) run on parent recovery instead of abandoning and re-running it (#​1630).

    When a parent agent was interrupted (deploy / Durable Object eviction) while a child agentTool() run was still in flight, recovery marked the run interrupted within a ~5s window and the parent re-issued the task — re-running the child's already-completed work. For long-running children under continuous deploys this surfaced to users as "the agent went all the way back and lost the files it already wrote."

    Three changes fix this:

    • Stable child runId. agentTool() now derives the child runId from the (recovery-preserved) tool call id (agent-tool:<toolCallId>) instead of minting a fresh nanoid per call. A turn re-run by chat recovery now resolves to the same idempotent child facet rather than spawning a brand-new one, so completed child work is never re-run.
    • Bounded re-attach. A duplicate non-terminal runId (in runAgentTool) and a still-running child during startup reconciliation now tail the live child to its real terminal result and collect it, instead of immediately sealing interrupted. Re-attach is bounded by a generous wall-clock budget (DEFAULT_AGENT_TOOL_REATTACH_TIMEOUT_MS, 120s, internal): a child that keeps advancing toward terminal within the window is collected; a genuinely hung child still seals interrupted so recovery can never block forever.
    • Durable child-run reconcile. A child facet self-heals its interrupted turn via its own chatRecovery, but that recovery path never wrote the child's agent-tool run row — so after a real eviction the row stranded running (think) / was force-errored (ai-chat) and the parent could never collect the recovered result. Both @cloudflare/think and @cloudflare/ai-chat now reconcile a stale child-run row from the durable transcript on inspect: while recovery is still resolving the row stays running; once it settles, a completed assistant response surfaces as completed (so the parent collects the real result) and an empty/failed recovery as error. This keeps the child's own (working) recovery path untouched.

    No new public configuration. Adds an internal agent_tool:recovery:reattach observability event. @cloudflare/think and @cloudflare/ai-chat child tails are now read-only on consumer detach (a parent's re-attach budget expiring never cancels the still-running child).

  • #​1598 f5e37bf Thanks @​threepointone! - Add ThinkWorkflow with durable step.prompt() support for Workflow-owned Think reasoning steps.

Patch Changes
  • #​1623 4c8b371 Thanks @​threepointone! - Compaction: the Session's tokenCounter now also drives the bundled createCompactFunction's boundary ("what to compress") decision, not just the fire/no-fire trigger. Fixes #​1593.

    Previously a tokenCounter configured on Session.compactAfter() only influenced whether compaction fired; the boundary walk inside createCompactFunction still used the Workers-safe chars/4 heuristic. On tool-heavy agent histories that heuristic under-counts badly, so the configured tail budget covered the entire history and compressEnd <= compressStart — compaction fired every turn but silently returned null, never shortening history (strictly worse than not configuring it).

    Now the Session passes its counter to the compaction function via a new CompactContext argument, and createCompactFunction uses it for the tail-budget walk when no explicit tokenCounter was given on CompactOptions. So a single tokenCounter on compactAfter() drives both "should we compact?" and "what should we compact?". When the trigger fires but compaction still returns null (e.g. no counter configured and the heuristic protects everything), the Session logs a one-time warning instead of looping silently.

    CompactFunction gains an optional second context?: CompactContext argument (backward compatible — existing one-arg functions are unaffected).

    Note: the flowed counter is invoked per-message during the tail walk. A tokenizer-style counter gives accurate per-message budgeting; a usage-only counter that reports a fixed whole-prompt total degrades the tail budget to minTailMessages (compaction still runs and context stays bounded, but the byte budget is effectively ignored). For precise budgeting with such counters, pass an explicit per-message CompactOptions.tokenCounter.

  • #​1617 5e60034 Thanks @​threepointone! - Scheduled callbacks no longer drop their work when an alarm fires on an isolate
    that a deploy has just superseded. In that window the first ctx.storage op
    throws Durable Object reset because its code was updated. for the entire
    invocation (code never reloads mid-invocation). Previously
    Agent._executeScheduleCallback burned its in-process retries (all doomed),
    swallowed the error, and alarm() deleted the one-shot row — permanently
    abandoning the work even though the next fresh invocation would succeed. This
    was a second deploy-churn abandonment path for chat recovery
    (_chatRecoveryContinue / _chatRecoveryRetry) that the progress-aware budget
    in @cloudflare/think / @cloudflare/ai-chat could not reach, because the
    continuation was deleted before it could be re-detected.

    For a one-shot schedule failing with this transient, the SDK now skips the
    doomed in-process retries and re-throws so alarm() rejects: the one-shot row
    survives and Cloudflare re-runs the alarm on a fresh isolate (= new code) under
    the at-least-once alarm guarantee, so the work auto-resumes once the deploy
    settles. All other callbacks and error classes keep the existing behavior.

  • #​1608 7c17736 Thanks @​cjol! - Fix auto-continuation stream resumes so immediate client-tool resume requests attach to the pending continuation instead of receiving cf_agent_stream_resume_none.

  • #​1639 6bac0f4 Thanks @​whoiskatrin! - Prevent MCP Streamable HTTP result responses from crossing between concurrent
    POST streams when a reused session receives duplicate in-flight JSON-RPC
    request ids. Responses now prefer the live connection that originated their
    request and return JSON-RPC internal errors instead of guessing when no origin
    can safely disambiguate colliding streams.

    Completion tracking for batched POST streams is now scoped per stream so an id
    collision on another POST cannot prevent the original stream from closing.

  • #​1629 7d38363 Thanks @​whoiskatrin! - Fix server-side needsApproval tool continuations remaining stuck after the
    user approves them. Think now keeps approved/denied/errored tool parts in the
    model transcript, updates its live transcript before an immediate continuation,
    and persists and broadcasts terminal tool output emitted for a prior assistant
    message. Continuation response frames are also labelled consistently so
    useAgentChat can apply streamed continuation updates to the active UI state.
    A pending approval-responded tool is no longer mis-reported by the
    incomplete-tool-call backstop, so approval continuations stop logging a false
    "repair gap" warning and emitting a spurious chat:transcript:repaired event.

    The cross-message tool result now flows through StreamAccumulator's
    cross-message-tool-update action and a shared, replay-safe
    crossMessageToolResultUpdate builder (exported from agents/chat): it matches
    terminal states for first-write-wins idempotency against provider replays (e.g.
    the OpenAI Responses API, #​1404), preserves a streamed preliminary flag, and
    lets Think skip redundant writes/broadcasts when a result is already settled.

  • #​1607 f82d897 Thanks @​mattzcarey! - Tighten SSE resumability in McpAgent's streamable HTTP transport.
    Follow-up to #​1583.

    • Final tool response is now actually replayable. The previous code
      stored the final response in the event store and then immediately
      called clearStream(streamId) on shouldClose, deleting every event
      for that stream — including the one just written. A client that lost
      the connection mid-flight could reconnect with Last-Event-ID and
      find nothing to replay. Fixed by flipping the order: write the SSE
      event to the wire first, then drop the persisted
      streamId -> requestIds mapping and clear the stored events. Every
      event up to and including the final response is replayable while the
      in-flight stream is open; the trade-off is that if the WS pipe is
      enqueued but the client TCP dies before the bytes arrive, that one
      final message is lost.

    • POST event store writes are unconditional, matching the
      standalone path. Previously the transport relied on a live WS
      connection at send() time to record the event; if the client had
      dropped (common during long tool calls on flaky networks) the event
      was lost. Now the transport falls back to a persisted
      requestId -> streamId reverse lookup (McpAgent.getStreamForRequestId),
      stores the event, and writes to the wire only if a live connection is
      still attached. Reconnecting with Last-Event-ID replays anything
      that was missed.

    • Resumed connection registers under the source streamId, matching
      the SDK reference. For an active POST stream the persisted
      requestIds are restored so future tool messages route to the new
      WS. For the standalone listen stream the connection takes over that
      role. For a completed POST the connection serves as a one-shot
      replay channel. In every resumable case any prior connection bound
      to the same streamId is closed, so there is at most one live
      connection per stream and routing stays deterministic.

    • One stream per message, per the MCP spec. The spec requires the
      server to send each message on exactly one connected stream and
      forbids broadcasting the same message across streams. Server-
      initiated notifications go to the single standalone GET stream (the
      transport supersedes any prior standalone GET when a new one opens),
      and POST responses go to their own stream. Events are still stored
      for replay when no live stream is attached.

    • Cleanup is immediate, not background. Each POST stream's events
      are cleared the moment the close frame is written. No alarms, no
      metadata index, no sweep. Storage cost is bounded by the in-flight
      POST streams plus the standalone GET stream. Multi-key deletes are
      chunked at the Durable Object 128-key limit, and replayEventsAfter
      uses an explicit limit so a pathological history can't OOM the DO.
      Standalone GET events are not cleared automatically; they accumulate
      for the lifetime of the session's Durable Object.

    • DurableObjectEventStore is exported so callers embedding
      WorkerTransport inside an Agent / Durable Object can wire up
      resumability with new DurableObjectEventStore(this.ctx.storage).

  • #​1602 cfc75bc Thanks @​mattzcarey! - Fix SSE keepalive and enable resumability on the MCP transports (#​1583).

    The MCP transports had a defective SSE keepalive (event: ping\ndata: \n\n
    — a named event the SSE parser dispatched with empty data, firing
    addEventListener("ping", …) on the client) and no recovery path for the
    ~5 min Cloudflare edge idle-stream watchdog. This change makes
    resumability the first-class recovery mechanism while keeping the
    keepalive available when resumability isn't configured.

    • GET (standalone listen stream) — when an EventStore is configured,
      no keepalive; idle drops are recovered by clients reconnecting with
      Last-Event-ID. Without an EventStore, the comment-frame keepalive
      (: keepalive\n\n every 25s) keeps long-lived listeners alive.
    • POST (tool response stream) — always keepalive. In-flight tool
      calls survive the ~5 min idle watchdog. POST streams can additionally
      be resumed via Last-Event-ID when an EventStore is configured: a
      reconnecting GET inherits the original POST's requestIds so
      subsequent tool messages route to the resumed connection.
    • Storage boundsDurableObjectEventStore now wraps each event
      with a write timestamp and exposes sweep(maxAgeMs). McpAgent
      schedules a recurring sweep (default hourly, 24 hr TTL) so events from
      abandoned POST streams whose clients never returned don't accumulate
      forever in Durable Object storage. Streams that close cleanly are
      cleared in full on the final response.

    Also fixed: a pre-existing bug where an McpAgent GET stream that
    reconnected with Last-Event-ID received the replayed backlog but
    wasn't re-tagged as the standalone SSE stream, so subsequent
    server-initiated notifications had no connection to land on.

    All changes are additive — patch-level, no breaking changes.
    DurableObjectEventStore is exported from agents/mcp for stateful
    WorkerTransport callers (e.g. the elicitation example, which now
    wires resumability via eventStore: new DurableObjectEventStore(this.ctx.storage)).

  • #​1641 3aa1936 Thanks @​threepointone! - Count a sub-agent's progress as the orchestrating parent's recovery progress

    A parent turn whose work is "run a sub-agent and await its result" produced no
    recoverable content of its own, so under deploy churn the parent's own
    chat-recovery no-progress window could exhaust while the child was still
    healthily streaming — abandoning the turn as interrupted and collecting an
    interrupted result even though the child went on t

Note

PR body was truncated to here.


Configuration

📅 Schedule: (in timezone Asia/Tokyo)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot enabled auto-merge June 10, 2026 04:02
@renovate renovate Bot force-pushed the renovate/agents-0.x branch from 523234a to 0cab6c9 Compare June 10, 2026 04:41
@renovate renovate Bot force-pushed the renovate/agents-0.x branch from 0cab6c9 to 8c04c20 Compare June 10, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants