Releases: cloudflare/agents
create-think@0.1.1
Patch Changes
- #1817
7f367d8Thanks @threepointone! -create-thinknow prompts for a starter template when--templateis omitted (and falls back tobasicwhen stdin is non-interactive).npm create thinkandthink initinitialize a git repository — skipping cleanly when the target is already inside one — and scaffold projects with Oxlint/Oxfmt config plus acheckscript. Removes the unused declarativeagent()framework helper and the identity helpers (defineMessengers,defineScheduledTasks,defineChannels) in favor of class-based agents and typed object returns.
agents@0.17.0
Minor Changes
-
#1758
6b46b04Thanks @threepointone! - Add progress signalling and durable milestones for agent-tool sub-agents
(#1758, rfc-detached-agent-tools §progress, phases 4a + 4b).A sub-agent running as an agent tool (awaited or detached/background) can now
report mid-run progress:// Inside the child sub-agent (e.g. from a tool's execute): await this.reportProgress({ fraction: 0.6, phase: "deploying", message: "Generating menu page…", });
These signals ride the child's own turn stream as a transient
data-agent-progresspart, so they re-broadcast to the parent's connected
clients and surface onAgentToolRunState.progressviauseAgentToolEvents— a
background-runs tray can render a live bar / phase / status line without drilling
in. Highlights:reportProgress({ fraction?, message?, phase?, data? }, { persist? })on
chat agents (@cloudflare/think,AIChatAgent); a no-op with a dev warning on
the baseAgentand when called outside an active agent-tool run. The framework
resolves the run id from the active turn — no threading required. Bursts are
coalesced (latest-wins; afraction >= 1"done" frame always flushes).data
is live-only unless{ persist: true }.onProgress(run, progress)parent hook, fired best-effort from the tail
for both awaited and detached runs.- Latest-snapshot persistence + recovery inspect. The child stores a
progress_json+last_signal_aton its run row and surfaces it through
inspectAgentToolRun().progress, so a rehydrated parent reconstructs progress
after eviction. - Resetting no-progress budget for detached runs. Once a detached child has
reported at least one signal, the backbone gives up if it then goes silent for
detachedNoProgressBudgetMs(default 1h; per-run override via
detached: { noProgressBudgetMs }), surfaced asinterruptedwith the
no-progressreason. A child that never reports is bounded only by the absolute
detachedMaxBudgetMsceiling — we never give up on a run merely for being slow.
Durable milestones (phase 4b)
Naming a
milestonepromotes a signal from the ephemeral tier to a durable
one — there is still only one emit method:// Inside the child sub-agent: await this.reportProgress({ milestone: "sources-gathered", data: { sources: 2 }, });
-
Persisted + replayable. Each milestone is one row on the child
(cf_agent_tool_milestones/cf_ai_chat_agent_tool_milestones) with a
monotonic per-runsequence. It rides the stream as a persisted
data-agent-milestonepart (vs. transient progress), so drill-in replay and a
rehydrated parent both see it. Surfaced viainspectAgentToolRun().milestones
andAgentToolRunState.milestones(deduped bysequence). -
onProgressfires for milestones too — the snapshot carries
progress.milestone, so a consumer can branch on milestone vs. ephemeral. -
detached: { onMilestones }chat convenience (@cloudflare/thinkand
AIChatAgent). When a configured milestone lands, the chat agent surfaces an
idempotent synthetic chat message (keyed/idempotent per(runId, name))
before the run finishes. Delivered from both the warm tail and the cold
backbone reconcile; the deterministic id collapses them to at-most-once. Two
modes (thestring[]shorthand defaults to"narrate"):"narrate"(default) — a synthetic assistant message injected directly
(no inference): a cheap, honest status line that does not trigger a turn."react"— a user-role turn so the model responds to the milestone
(steer, start dependent work). Costs a model turn.
detached: { onMilestones: ["preview-ready"] } // narrate (default) detached: { onMilestones: { names: ["needs-approval"], mode: "react" } }
Override the wording via
formatDetachedMilestone(run, milestone). These
synthetic messages carrymetadata.sourceso clients can render them as an
agent event rather than a human turn (the example does this).
The awaitable join point (
awaitAgentToolMilestone, phase 4c) is intentionally
not included here — it is gated behind a design addendum. -
#1790
190ea81Thanks @threepointone! - Add typed action ledger observability events to the diagnostics channel event union. -
#1790
190ea81Thanks @threepointone! - Add typedaction:pause:*(created/approved/rejected/swept) observability events to the diagnostics channel event union for durable-pause action approvals. -
#1790
190ea81Thanks @threepointone! - Add theReplyAttachmenttype and an optionalattachmentsfield onChatResponseResult, plus anaction:reply-attacheddiagnostics event, to support the Think actions reply-attachment side-channel. -
#1799
3c2afc9Thanks @threepointone! - Stop reconnecting on terminal WebSocket close events and expose terminal connection failures viaconnectionError/onConnectionErroronAgentClient,useAgent, anduseAgentChat. -
#1758
6b46b04Thanks @threepointone! - Add first-class detached ("background") agent-tool runs with a durable
completion hook (#1752).runAgentTool(cls, { detached })now dispatches a sub-agent without blocking
the calling turn, returning a{ runId, agentType, status: "running" }handle
immediately:// Fire-and-forget — observe via agent-tool-event frames + onAgentToolFinish. const { runId } = await this.runAgentTool(ImportAgent, { input, detached: true }); // Or wire a durable, eviction-surviving completion callback (by METHOD NAME, // like schedule()): await this.runAgentTool(ImportAgent, { input, detached: { onFinish: "onImportDone", maxBudgetMs: 60 * 60 * 1000 } }); async onImportDone(run: AgentToolRunInfo, result: AgentToolLifecycleResult) { // Branch on result.status: "completed" | "error" | "aborted" | "interrupted". // A budget give-up arrives as interrupted / reason "budget-exceeded". }
Highlights:
- Durable, exactly-once-on-the-happy-path completion. A warm fast path
(low-latency while the isolate is alive) plus a self-scheduling reconcile
backbone (survives eviction / deploys) route through one guarded delivery
funnel. Two independent ledger slots (finish / give-up) with a claim+lease
mean a premature give-up can never dedupe a child's real late completion away. - No silent abandonment. Detached runs are never sealed
interruptedjust
because their dispatching turn ended (the normal state for a background run);
the backbone owns them and re-arms on restart. - Bounded. An absolute
maxBudgetMsceiling (default 24h, configurable via
thedetachedMaxBudgetMsstatic option) gives up — surfaced asinterrupted
with the newbudget-exceededreason — and tears the child down so an
abandoned run cannot hold a concurrency slot forever. cancelAgentTool(runId)cancels a detached (or awaited) run by id through
the same guarded path, so a wiredonFinishstill fires once with
status: "aborted", and the terminalagent-tool-eventis always broadcast
to connected clients (a cancelled run's UI settles immediately).- Recovery-safe delivery. A chat host (
@cloudflare/think/AIChatAgent)
runs the completion callback serialized on its turn queue, so anonFinish
that mutates chat state can never interleave with a live LLM turn. Concurrent
detached dispatches in one turn no longer race to arm multiple reconcile
backbones (arming is serialized). - Observability. New events
agent_tool:detached:delivery_failed(a wired
callback threw; the slot stays open for retry) and
agent_tool:detached:live_count_warning(edge-triggered when live detached
runs cross a threshold — a leak smoke alarm, since detached runs hold a
concurrency slot for their whole life).
A detached run deliberately does NOT inherit
options.signal(it must outlive
the spawning turn); cancel it explicitly withcancelAgentTool. - Durable, exactly-once-on-the-happy-path completion. A warm fast path
-
#1801
c58b401Thanks @threepointone! - Add the sharedagents/chat/reactentry withuseAgentChat, chat transport helper...
@cloudflare/voice@0.3.3
Patch Changes
-
#1605
8bfebf0Thanks @cjol! - Support AI SDK fullStream responses in voice turns and warn when textStream is used. -
#1772
d4f27feThanks @mattzcarey! - Include each package's documentation in its published package. -
#1816
f18ff01Thanks @cjol! - Fix assistant speech playing back slow on a new turn after an idle gap.VoiceClientroutes playback through aMediaStreamAudioDestinationNode->HTMLAudioElementbridge, and reusing that element for a fresh burst after it had been idle between turns made the new turn resume at the wrong rate (audible as slow-motion that re-converges to normal over the turn). The bridge is now torn down and rebuilt once it has fully drained and been idle past a short threshold, so each turn plays through a freshly created element. Rebuilds never happen mid-turn, since chunks within a turn keep at least one source scheduled on the playback cursor.
@cloudflare/think@0.11.0
Minor Changes
-
#1758
6b46b04Thanks @threepointone! - Add progress signalling and durable milestones for agent-tool sub-agents
(#1758, rfc-detached-agent-tools §progress, phases 4a + 4b).A sub-agent running as an agent tool (awaited or detached/background) can now
report mid-run progress:// Inside the child sub-agent (e.g. from a tool's execute): await this.reportProgress({ fraction: 0.6, phase: "deploying", message: "Generating menu page…" });
These signals ride the child's own turn stream as a transient
data-agent-progresspart, so they re-broadcast to the parent's connected
clients and surface onAgentToolRunState.progressviauseAgentToolEvents— a
background-runs tray can render a live bar / phase / status line without drilling
in. Highlights:reportProgress({ fraction?, message?, phase?, data? }, { persist? })on
chat agents (@cloudflare/think,AIChatAgent); a no-op with a dev warning on
the baseAgentand when called outside an active agent-tool run. The framework
resolves the run id from the active turn — no threading required. Bursts are
coalesced (latest-wins; afraction >= 1"done" frame always flushes).data
is live-only unless{ persist: true }.onProgress(run, progress)parent hook, fired best-effort from the tail
for both awaited and detached runs.- Latest-snapshot persistence + recovery inspect. The child stores a
progress_json+last_signal_aton its run row and surfaces it through
inspectAgentToolRun().progress, so a rehydrated parent reconstructs progress
after eviction. - Resetting no-progress budget for detached runs. Once a detached child has
reported at least one signal, the backbone gives up if it then goes silent for
detachedNoProgressBudgetMs(default 1h; per-run override via
detached: { noProgressBudgetMs }), surfaced asinterruptedwith the
no-progressreason. A child that never reports is bounded only by the absolute
detachedMaxBudgetMsceiling — we never give up on a run merely for being slow.
Durable milestones (phase 4b)
Naming a
milestonepromotes a signal from the ephemeral tier to a durable
one — there is still only one emit method:// Inside the child sub-agent: await this.reportProgress({ milestone: "sources-gathered", data: { sources: 2 } });
-
Persisted + replayable. Each milestone is one row on the child
(cf_agent_tool_milestones/cf_ai_chat_agent_tool_milestones) with a
monotonic per-runsequence. It rides the stream as a persisted
data-agent-milestonepart (vs. transient progress), so drill-in replay and a
rehydrated parent both see it. Surfaced viainspectAgentToolRun().milestones
andAgentToolRunState.milestones(deduped bysequence). -
onProgressfires for milestones too — the snapshot carries
progress.milestone, so a consumer can branch on milestone vs. ephemeral. -
detached: { onMilestones }chat convenience (@cloudflare/thinkand
AIChatAgent). When a configured milestone lands, the chat agent surfaces an
idempotent synthetic chat message (keyed/idempotent per(runId, name))
before the run finishes. Delivered from both the warm tail and the cold
backbone reconcile; the deterministic id collapses them to at-most-once. Two
modes (thestring[]shorthand defaults to"narrate"):"narrate"(default) — a synthetic assistant message injected directly
(no inference): a cheap, honest status line that does not trigger a turn."react"— a user-role turn so the model responds to the milestone
(steer, start dependent work). Costs a model turn.
detached: { onMilestones: ["preview-ready"] } // narrate (default) detached: { onMilestones: { names: ["needs-approval"], mode: "react" } }
Override the wording via
formatDetachedMilestone(run, milestone). These
synthetic messages carrymetadata.sourceso clients can render them as an
agent event rather than a human turn (the example does this).
The awaitable join point (
awaitAgentToolMilestone, phase 4c) is intentionally
not included here — it is gated behind a design addendum. -
#1758
6b46b04Thanks @threepointone! - Adddetached: { notify: true }support forrunAgentToolon chat agents
(@cloudflare/thinkandAIChatAgent) (#1752).When a detached sub-agent run finishes, a chat agent can inject a message back
into the chat so the model reacts to the result — without you wiringonFinish
by hand:await this.runAgentTool(ResearchAgent, { input, detached: { notify: { source: "research-background" } } });
The injected turn is idempotent per run + terminal status, so an exactly-once
finish never duplicates, while a soft give-up followed by a real late completion
surfaces as two distinct turns. (Think dedupes via asubmitMessages
idempotency key;AIChatAgent, which has no durable-submission layer, persists
under a deterministic message id and runs the follow-up turn inline within the
already-serialized delivery slot.) Usenotify: truefor the default
metadata.source, passnotify: { source }to match your app's message
taxonomy, and overrideformatDetachedCompletion(run, result)to customize (or
suppress) the injected text. -
#1817
7f367d8Thanks @threepointone! -create-thinknow prompts for a starter template when--templateis omitted (and falls back tobasicwhen stdin is non-interactive).npm create thinkandthink initinitialize a git repository — skipping cleanly when the target is already inside one — and scaffold projects with Oxlint/Oxfmt config plus acheckscript. Removes the unused declarativeagent()framework helper and the identity helpers (defineMessengers,defineScheduledTasks,defineChannels) in favor of class-based agents and typed object returns. -
#1790
190ea81Thanks @threepointone! - Addctx.attachReply(attachment)for actions: an advisory, recording-only reply-attachment side-channel surfaced onChatResponseResult.attachments(inonChatResponse) and a publicreplyAttachments(requestId?)getter. Attachments are JSON-normalized, deep-copied on read, capped per turn, and never alter the model-visible tool output; policy callbacks are no-ops, failed executions discard their attachments, approval-gated approved actions support it, and durable-pause approved actions are a v1 no-op. -
#1790
190ea81Thanks @threepointone! - Add a pending-retry lease for the action ledger via the newactionLedgerPendingRetryLeaseMsconfig (default 5 minutes). Apendingledger row left behind by a crashed executor is now reclaimed and re-run once it is stale, but ONLY for actions that declare an explicitidempotencyKey— the key is the developer's assertion that re-running the keyed side effect is safe. Behavior change: such a stale row previously blocked forever withActionPendingError; it now reclaims (refreshingupdated_atin place, stillpending), emitsaction:ledger:reclaimed, and re-runsexecute. Fresh rows, fallbacktool:${toolCallId}keys, and a disabled lease (actionLedgerPendingRetryLeaseMs = false) keep the conservativeActionPendingErrorbehavior. Same-isolate coalescing still wins first, so an in-flight run is never reclaimed. -
#1790
190ea81Thanks @threepointone! - Add a durable action ledger foraction()descriptors so settled server action outputs can be replayed by stable idempotency key without re-running side effects. -
#1790
190ea81Thanks @threepointone! - Generalize the messenger runtime into a public channel surface. AddconfigureChannels()andChannelDefinition(web, voice, messenger, and custom channels) wrappinggetMessengers(), a no-turndeliverNotice()withinformModel, additiveDeliveryTag(kind + turnEnded) on messenger snapshots, per-channel policy (instructions, tool-narrowing,maxTurns) applied as overridable defaults, turn-scoped channel context threaded throughrunTurn(persisted for recovery), reply-attachment rendering at delivery, andchannel:*/notice:*observability events. -
#1790 [
190ea81](190ea814c4ea61c216509a431b...
@cloudflare/shell@0.4.1
Patch Changes
- #1772
d4f27feThanks @mattzcarey! - Include each package's documentation in its published package.
@cloudflare/codemode@0.4.2
Patch Changes
-
#1807
7eea2fbThanks @mattzcarey! - Cleanup connector imports so connector modules are imported normally and the Vite plugin only auto-exports the CodemodeRuntime facet class. Codemode now fails loudly when the runtime facet class is not exported from the Worker entry. -
#1814
a79144dThanks @threepointone! - Dispose the dynamically-loaded Worker and its RPC entrypoint stub after each
DynamicWorkerExecutor.execute()run.Each execution spins up a child Worker via
loader.load()and obtains an RPC
Fetcherstub viagetEntrypoint(). These own native handles, and the code
previously left them for the garbage collector. When such a handle is finalized
late — for example during isolate shutdown under
@cloudflare/vitest-pool-workers— workerd raises a fatal assertion ("tried to
defer destruction during isolate shutdown") that kills the worker, surfacing as
a flaky "Worker exited unexpectedly" with no failing assertion. The milder
manifestation is workerd's "An RPC result was not disposed properly" warning.The executor now disposes the entrypoint stub and the loaded worker in
finally
blocks (best-effort, viaSymbol.dispose), releasing the handles while the
isolate is still alive. No behavior or API change for callers. -
#1793
247ebebThanks @mattzcarey! - Pass the outer MCP tool-call context toopenApiMcpServerrequest callbacks so server-to-client requests and notifications can be associated with the originating response stream. -
#1791
9c85369Thanks @mattzcarey! - Remove the root entry's runtime dependency on the optionalaiandzodpeers. Executor and runtime imports now bundle without either framework package installed. -
#1772
d4f27feThanks @mattzcarey! - Include each package's documentation in its published package. -
#1806
43f663dThanks @mattzcarey! - Increase the default DynamicWorkerExecutor timeout from 30 seconds to 60 seconds to better support longer-running codemode executions.
@cloudflare/ai-chat@0.9.0
Minor Changes
-
#1758
6b46b04Thanks @threepointone! - Add progress signalling and durable milestones for agent-tool sub-agents
(#1758, rfc-detached-agent-tools §progress, phases 4a + 4b).A sub-agent running as an agent tool (awaited or detached/background) can now
report mid-run progress:// Inside the child sub-agent (e.g. from a tool's execute): await this.reportProgress({ fraction: 0.6, phase: "deploying", message: "Generating menu page…" });
These signals ride the child's own turn stream as a transient
data-agent-progresspart, so they re-broadcast to the parent's connected
clients and surface onAgentToolRunState.progressviauseAgentToolEvents— a
background-runs tray can render a live bar / phase / status line without drilling
in. Highlights:reportProgress({ fraction?, message?, phase?, data? }, { persist? })on
chat agents (@cloudflare/think,AIChatAgent); a no-op with a dev warning on
the baseAgentand when called outside an active agent-tool run. The framework
resolves the run id from the active turn — no threading required. Bursts are
coalesced (latest-wins; afraction >= 1"done" frame always flushes).data
is live-only unless{ persist: true }.onProgress(run, progress)parent hook, fired best-effort from the tail
for both awaited and detached runs.- Latest-snapshot persistence + recovery inspect. The child stores a
progress_json+last_signal_aton its run row and surfaces it through
inspectAgentToolRun().progress, so a rehydrated parent reconstructs progress
after eviction. - Resetting no-progress budget for detached runs. Once a detached child has
reported at least one signal, the backbone gives up if it then goes silent for
detachedNoProgressBudgetMs(default 1h; per-run override via
detached: { noProgressBudgetMs }), surfaced asinterruptedwith the
no-progressreason. A child that never reports is bounded only by the absolute
detachedMaxBudgetMsceiling — we never give up on a run merely for being slow.
Durable milestones (phase 4b)
Naming a
milestonepromotes a signal from the ephemeral tier to a durable
one — there is still only one emit method:// Inside the child sub-agent: await this.reportProgress({ milestone: "sources-gathered", data: { sources: 2 } });
-
Persisted + replayable. Each milestone is one row on the child
(cf_agent_tool_milestones/cf_ai_chat_agent_tool_milestones) with a
monotonic per-runsequence. It rides the stream as a persisted
data-agent-milestonepart (vs. transient progress), so drill-in replay and a
rehydrated parent both see it. Surfaced viainspectAgentToolRun().milestones
andAgentToolRunState.milestones(deduped bysequence). -
onProgressfires for milestones too — the snapshot carries
progress.milestone, so a consumer can branch on milestone vs. ephemeral. -
detached: { onMilestones }chat convenience (@cloudflare/thinkand
AIChatAgent). When a configured milestone lands, the chat agent surfaces an
idempotent synthetic chat message (keyed/idempotent per(runId, name))
before the run finishes. Delivered from both the warm tail and the cold
backbone reconcile; the deterministic id collapses them to at-most-once. Two
modes (thestring[]shorthand defaults to"narrate"):"narrate"(default) — a synthetic assistant message injected directly
(no inference): a cheap, honest status line that does not trigger a turn."react"— a user-role turn so the model responds to the milestone
(steer, start dependent work). Costs a model turn.
detached: { onMilestones: ["preview-ready"] } // narrate (default) detached: { onMilestones: { names: ["needs-approval"], mode: "react" } }
Override the wording via
formatDetachedMilestone(run, milestone). These
synthetic messages carrymetadata.sourceso clients can render them as an
agent event rather than a human turn (the example does this).
The awaitable join point (
awaitAgentToolMilestone, phase 4c) is intentionally
not included here — it is gated behind a design addendum. -
#1799
3c2afc9Thanks @threepointone! - Stop reconnecting on terminal WebSocket close events and expose terminal connection failures viaconnectionError/onConnectionErroronAgentClient,useAgent, anduseAgentChat. -
#1794
b6ad4d5Thanks @threepointone! - Recover interrupted server-tool calls on resume instead of abandoning them.When a turn is interrupted mid tool call (e.g. a server tool whose
execute()
died with an evicted isolate, leaving aninput-availableorphan that nothing
will ever resolve),AIChatAgentnow repairs the transcript before re-entering
inference on the recovered turn — the same behavior@cloudflare/thinkalready
has. The interrupted tool part is flipped to an errored tool-result through the
sharedagents/chatrepair primitive, so the nextconvertToModelMessagesno
longer 400s withAI_MissingToolResultsErrorand the turn continues.Adds an overridable
repairInterruptedToolPart(part)hook (default: flip to an
output-errorresult) so apps can customize the repaired shape for
client-resolved tools (e.g. preserve an interrupted question tool as text).
Repair only ever reshapes assistant tool parts; the corrected transcript is
persisted and broadcast through the normal write path.Repair runs before EVERY inference chokepoint — live submit, tool
auto-continuation,continueLastTurn,saveMessages/retry, and the chat
recovery callbacks — mirroring how@cloudflare/thinkrepairs before every
inference (the app ownsconvertToModelMessages, so the framework repairs
this.messagesright before handing control toonChatMessage). This closes
the cases a recovery-only repair missed: a mixed client+server orphan whose
client replay drives an auto-continuation, and any agent running with
chatRecoverydisabled. Repair is scoped per-part to dead SERVER orphans: a
part still legitimately awaiting a client (aninput-availableclient tool or an
approval-requestedpart the user may still answer) is left verbatim, so a fresh
dead-server orphan at the leaf is repaired even when an unrelated abandoned client
orphan sits earlier in history. It is a no-op (no write, no broadcast) for a
healthy transcript.The recovery-path stability wait (
waitUntilStable) now gates on the narrower
client-resolvable predicate so a dead server-tool orphan no longer blocks
stability — it is repaired and the turn continues.waitUntilStablegains an
optionalpendingInteractionpredicate; its default (and the documented
semantics for app overrides) is unchanged. -
#1788
3b2af54Thanks @threepointone! -AIChatAgentnow uses an event-driven auto-continuation barrier that parks
indefinitely on an incomplete parallel tool batch instead of force-continuing
after a fixed timeout.Previously, when a turn ended with several parallel client tool calls and only
some results had arrived,AIChatAgentran the completeness barrier inside
the continuation turn and polled for up to 60s
(AUTO_CONTINUATION_PENDING_TOOL_TIMEOUT_MS), after which it continued
inference against whatever results had landed — potentially a half-complete tool
batch. The barrier is now event-driven and runs before the continuation is
enqueued (converging onto@cloudflare/think's model): it fires only once every
result in the batch has arrived, re-arms as each sibling result is applied and
when a streaming turn finalizes, guards against double-fire, and is gated on no
active stream. There is no orphan timeout — a batch with a never-arriving
sibling now parks budget-free until it completes (the same way a turn already
parks on a pending HITL/client interaction) rather than force-continuing with
missing results.This is a behavior change for the rare stuck-tool case: a result that never
arrives no longer triggers a continuation after 60s; it parks until the missing
result lands (or a later user turn / chat recovery repairs the transcript). A
parked continuation leaves the same on-disk signature as a HITL park, so a
deploy/crash mid-park recovers by re-arming rather than terminalizing. -
#1788
3b2af54Thanks @threepointone! -AIChatAgentnow replays the live "recovering…" status on connect (#1620).Previously the
cf_agent_chat_recoveringframe was only broadcast live, ...
@cloudflare/voice@0.3.2
Patch Changes
- #1747
28653b3Thanks @cjol! - Fix audible clicks at audio chunk boundaries during agent speech.VoiceClientplayed each response chunk by starting it atcurrentTimeand waiting for itsendedevent before scheduling the next, so every chunk seam carried a few milliseconds of silence (event-loop latency plus the next chunk's setup) — audible as a periodic click, roughly one per chunk. Chunks are now scheduled back-to-back on the audio clock via a playback cursor (start(Math.max(currentTime, cursor))), so consecutive chunks butt together sample-tight. Because chunks can now be scheduled ahead of playback, the client tracks every scheduled source and stops them all on interrupt/end-call (previously only the single active source needed stopping), and playback counts as active until the last scheduled chunk finishes so barge-in detection keeps working through the scheduled tail.
create-think@0.1.0
Minor Changes
-
#1770
718634fThanks @threepointone! - Addwebhook-agentandbusiness-workflowstarter templates.webhook-agentscaffolds an agent fed by inbound webhooks through durable,
idempotent submissions (a customsrc/server.tsentry +submitMessages).
business-workflowscaffolds a back-office operations agent with
human-in-the-loop approval gates (needsApproval+ an approval UI) and a
scheduled digest. Every starter now also ships astart.mdyou can paste into an
AI coding agent for guided setup.
agents@0.16.2
Patch Changes
-
#1767
f03dee6Thanks @threepointone! - Reduce Think Durable Object SQLite reads during normal wakes and text-only turns.Think now avoids automatic media-eviction scans until hydration has been windowed or an oversized appended message has been observed. The shared resumable stream buffer also avoids per-wake metadata-column introspection by creating new tables with the current columns and lazily migrating legacy tables only when a stream write needs it.
-
#1722
9f8e14bThanks @mattzcarey! - Fix two MCP client OAuth bugs found by the new conformance suite, and add MCP conformance testing.MCPClientConnectionnow finishes OAuth on the transport that received the 401. A fresh transport loses the resource metadata URL from theWWW-Authenticateheader, so token exchange fell back to the default/tokenpath and failed against authorization servers at non-default locations.MCPClientConnection.init()detaches the previous transport before reconnecting. Re-authorizing after a mid-session 401 (scope step-up, token revocation) previously failed permanently with "Already connected to a transport".- Added the official
@modelcontextprotocol/conformancesuite (as used by the MCP TypeScript SDK) running against the MCP client (Agent+MCPClientManager),McpAgent, andcreateMcpHandler+WorkerTransport— all hosted in workerd viawrangler dev. Seepackages/agents/conformance/README.md.
-
#1767
f03dee6Thanks @threepointone! - Cache the active branch tip inAgentSessionProviderso finding the latest leaf no longer scans the whole session on every read and append.latestLeafRow()previously ran an anti-join over every message row (O(rows)) to locate the branch tip — on each hydration AND each auto-parent append, so on long transcripts it dominated a wake's read cost. The tip is now maintained in place on append/delete/clear; a cached tip is re-validated on read with an O(1) existence + still-childless check (so it self-heals if another writer deletes the tip or gives it a child), and the full scan only runs when that check fails or the cache is cold. Per-hydration and per-append tip lookups drop from O(rows) to O(1), and the full scan never runs more often than before.