diff --git a/apps/docs/src/content/docs/ecosystem/deploy/cloudflare.md b/apps/docs/src/content/docs/ecosystem/deploy/cloudflare.md index 97650ed49..1c566984d 100644 --- a/apps/docs/src/content/docs/ecosystem/deploy/cloudflare.md +++ b/apps/docs/src/content/docs/ecosystem/deploy/cloudflare.md @@ -215,7 +215,7 @@ export const cloudflare = extend({ }); ``` -This is an advanced Cloudflare-only extension point. Flue applies `base` first, then defines its own Durable Object subclass with the generated binding and class identity. For the `Heartbeat` agent, authored Worker code can access the namespace as `env.FLUE_HEARTBEAT_AGENT`, and Wrangler binds that name to `FlueHeartbeatAgent`. Use `base` for native SDK lifecycle hooks and additional named methods. Do not override `fetch()`, `onRequest()`, `onFiberRecovered()`, or `alarm()`: Flue and the Agents SDK use those methods for routing, interruption recovery, and alarm multiplexing. +This is an advanced Cloudflare-only extension point. Flue applies `base` first, then defines its own Durable Object subclass with the generated binding and class identity. For the `Heartbeat` agent, authored Worker code can access the namespace as `env.FLUE_HEARTBEAT_AGENT`, and Wrangler binds that name to `FlueHeartbeatAgent`. Use `base` for native SDK lifecycle hooks and additional named methods. Do not override `fetch()`, `onRequest()`, `onError()`, `alarm()`, or `taskDefinitions`: Flue and the Agents SDK use them for routing, Task failure recovery, alarm multiplexing, and the durable execution definitions. A `base` that declares its own `taskDefinitions` keeps them; Flue merges its two definitions over them. Use `wrap` when an integration needs to wrap the final Flue-generated Durable Object class: diff --git a/apps/docs/src/content/docs/guide/cloudflare-target.md b/apps/docs/src/content/docs/guide/cloudflare-target.md index a3116b31a..307434f7d 100644 --- a/apps/docs/src/content/docs/guide/cloudflare-target.md +++ b/apps/docs/src/content/docs/guide/cloudflare-target.md @@ -107,10 +107,10 @@ dispatch(...) input ──────────────────── The submitting connection observes the work but does not own it. If a client disconnects after admission, backend work can continue. Agent events are durably stored and can be replayed from any offset via the Durable Streams protocol. -Accepted work runs inside the Durable Object itself, as one platform-visible unit per response: after admission answers, Flue schedules a zero-delay wake, and the object's alarm invocation claims the queued submission and awaits the full response — every model turn and tool call — before completing. Admission always returns first; the response begins on the next alarm tick, typically within tens of milliseconds. Three consequences: +Accepted work runs inside the Durable Object itself, as one durable state machine on the Agents SDK's [Tasks](https://github.com/cloudflare/agents/blob/main/docs/agents/tasks.md) engine. Every conversation has one `flue:conversation@v1` run at a fixed address: its `idle` phase parks on the run's mailbox — holding no alarm at all — until admission sends it a wake, then claims the next runnable submission from the durable queue; its `turn` phase awaits that submission's full response — every model turn and tool call — and settles it, and the machine returns to `idle`, where the next queued head is claimed in the same invocation. Admission always returns first; the response begins in the same invocation when the object is warm, or on the next wake otherwise. The SDK owns the durable claim while a turn is live (a backstop that survives eviction), replays the turn on a fresh isolate after an interruption, and bounds a turn that stops making progress with its transition watchdog; the submission's own [durability timeout](/docs/guide/durability/#retry-budget-and-timeout) aborts the turn's work from inside it. Three consequences: - **Platform observability sees agent work.** Workers Logs, Workers Traces, and invocation-wrapping integrations (the Sentry `wrap` pattern in [Extending Agents](#extending-agents-on-cloudflare)) attribute the whole response to the one invocation that ran it. [`createCloudflareTracing()`](#createcloudflaretracing) (installed by default) adds agent-level spans to those traces. See [Observability](/docs/guide/observability/#cloudflare) for the two views and [Deploy on Cloudflare](/docs/ecosystem/deploy/cloudflare/#observability) for enabling them. -- **Scheduled callbacks wait for a running response.** A Durable Object runs one alarm at a time, so `schedule()`/`scheduleEvery()` callbacks that come due mid-response fire after it settles — delivery is durable; timeliness is not guaranteed while the agent is busy. `queue()` is not alarm-driven and is unaffected. Steering is also unaffected: a message that arrives mid-response still joins it at the next turn boundary. +- **Scheduled callbacks share the alarm with running responses.** The SDK drives due work from one alarm loop and hands a long attempt off after a short dispatch budget, so `schedule()`/`scheduleEvery()` callbacks that come due mid-response still fire — delivery is durable, and timeliness is best-effort while the agent is busy. `queue()` is not alarm-driven and is unaffected. Steering is also unaffected: a message that arrives mid-response still joins it at the next turn boundary. - **CPU limits are per invocation.** Agent responses are I/O-bound and sit far below the default 30-second active-CPU limit; raise [`limits.cpu_ms`](https://developers.cloudflare.com/durable-objects/platform/limits/) in `wrangler.jsonc` if an agent computes heavily. When a Durable Object resumes after interruption, Flue decides what to do next from the stored input and canonical conversation progress. It requeues only when it can prove the input was not applied, recognizes already-completed output, and records an interruption instead of blindly repeating uncertain model or tool work. diff --git a/apps/docs/src/content/docs/guide/durability.md b/apps/docs/src/content/docs/guide/durability.md index 632d779eb..f5c9ff8b5 100644 --- a/apps/docs/src/content/docs/guide/durability.md +++ b/apps/docs/src/content/docs/guide/durability.md @@ -44,7 +44,7 @@ A recovered conversation always comes to rest in a state where the next message Each interruption consumes one attempt. When a submission exhausts its attempts, or exceeds its wall-clock timeout, retrying stops: the conversation is settled to a rest state, a `submission_interrupted` advisory lands in the timeline, and the submission settles `failed` — waiters reject with the structured error, including which tool calls were left with unknown outcomes. -The timeout is enforced preemptively, not just between attempts. The coordinator supervises running attempts on its wake cadence: at the deadline it fires the attempt's abort signal, so work suspended on a signal-aware await (a provider call, a sandbox command, any tool — a `run` that ignores its signal is abandoned rather than awaited) unwinds and settles through the normal paths — and an attempt hung below the abandonable layer is settled `failed` over the hung fiber after a short grace, its late writes fenced off. A hang delays settlement by at most the deadline plus that grace; it can never strand the submission. Most stalls never reach the deadline at all: a model stream that goes silent past its [idle timeout](/docs/reference/provider-api/#cloudflarebindingprovider) fails as a transient provider error and the turn retries under the [error budget](#recovery-after-an-interruption). +The timeout is enforced preemptively, not just between attempts. At the deadline the running attempt's abort signal fires, so work suspended on a signal-aware await (a provider call, a sandbox command, any tool — a `run` that ignores its signal is abandoned rather than awaited) unwinds and settles through the normal paths — and an attempt hung below the abandonable layer is settled `failed` over it, its late writes fenced off. A hang delays settlement by at most the deadline plus one wake; it can never strand the submission. Most stalls never reach the deadline at all: a model stream that goes silent past its [idle timeout](/docs/reference/provider-api/#cloudflarebindingprovider) fails as a transient provider error and the turn retries under the [error budget](#recovery-after-an-interruption). The defaults are 10 attempts and one hour per submission. Override them per agent with the `durability` static: @@ -134,10 +134,10 @@ See the [Node.js target guide](/docs/guide/node-target/#state-and-durability) fo ### Cloudflare recovery -On Cloudflare, every agent conversation is a Durable Object with its own SQLite storage, so ownership is structural — the platform guarantees one live instance per conversation, and there is no lease protocol to operate. Recovery is wake-driven: +On Cloudflare, every agent conversation is a Durable Object with its own SQLite storage, so ownership is structural — the platform guarantees one live instance per conversation, and there is no lease protocol to operate. Execution is one durable state machine per conversation on the Agents SDK's Tasks engine, and recovery is wake-driven: -- **Wake on start.** Whenever the Durable Object starts — after an eviction, a code deploy, or a platform reset — Flue immediately flags any attempt that was running when the previous instance died and reconciles it before serving new work. The platform's fiber-recovery callback triggers the same reconciliation path. -- **A durable wake schedule.** While unsettled work exists, the object keeps a short self-renewing wake scheduled, so an interrupted submission recovers promptly even if no external request ever arrives to wake the object. Each wake runs a bounded supervision pass — reconcile, enforce deadlines, start work — and re-arms its successor before doing anything that can fail, so a hung attempt or a failed pass can delay supervision by at most one wake, never break it. Attempt execution runs detached from the wake that started it. +- **Wake on start.** Whenever the Durable Object starts — after an eviction, a code deploy, or a platform reset — Flue wakes the conversation machine if any submission is unsettled. Its `idle` pass reconciles first: an attempt that was running when the previous instance died is classified from the canonical stream and settled or given a replacement attempt before new work is claimed. A `turn` interrupted mid-response replays on the same start and reconciles from the same evidence. +- **A durable claim.** While a turn is live, the SDK keeps a claim deadline in storage and wakes the object at it, so an interrupted submission recovers promptly even if no external request ever arrives. The submission's wall-clock timeout aborts the turn's work from inside it; a turn that then stops making progress trips the SDK's transition watchdog, which abandons that attempt — its later writes to the conversation are refused — and returns the machine to `idle`, where the submission settles through the timeout classification above. Abort intents, attempt bookkeeping, and settlement records all live in the object's own storage, so an abort requested while the object was evicted is honored on the next wake. See the [Cloudflare target guide](/docs/guide/cloudflare-target/#durable-agent-execution) for the target's execution model. diff --git a/packages/runtime/src/cloudflare/agent-coordinator.ts b/packages/runtime/src/cloudflare/agent-coordinator.ts index 82acba452..cc621e2cd 100644 --- a/packages/runtime/src/cloudflare/agent-coordinator.ts +++ b/packages/runtime/src/cloudflare/agent-coordinator.ts @@ -58,20 +58,29 @@ import { export const CLOUDFLARE_AGENT_INTERNAL_DISPATCH_PATH = '/__flue/internal/dispatch'; export const CLOUDFLARE_AGENT_INTERNAL_INSTANCE_INFO_PATH = '/__flue/internal/instance-info'; -const FLUE_AGENT_SUBMISSION_WAKE_CALLBACK = '__flueWakeAgentSubmissions'; -const FLUE_AGENT_SUBMISSION_WAKE_SECONDS = 30; -const FLUE_AGENT_SUBMISSION_ATTEMPT_FIBER = 'flue:submission-attempt'; /** - * How long past a deadline (durability timeout, or a durable abort intent) a - * live attempt fiber gets to unwind through its own settle path after its - * controller is aborted, before the supervisor force-settles the submission - * over it. Signal-aware awaits (provider fetches, exec) unwind well inside - * this; only a signal-deaf hang (a sandbox RPC pending against a - * healthy-reporting container, a wedged stream reader) reaches the force - * path. Two heartbeat periods, so the abort is guaranteed at least one full - * pass to land before force-settlement. + * The one durable state machine that runs a conversation: an `idle` phase + * that waits on the mailbox for a submission or an abort, and a `turn` phase + * that processes one claimed submission to settlement. The run's address is + * fixed per conversation, it is never expected to complete, and the + * submission ledger stays the record of what was accepted and how it + * settled; the machine is its executor. */ -const FLUE_AGENT_SUBMISSION_SETTLE_GRACE_MS = 2 * FLUE_AGENT_SUBMISSION_WAKE_SECONDS * 1000; +export const FLUE_CONVERSATION_TASK = 'flue:conversation@v1'; +const FLUE_CONVERSATION_RUN_ID = 'flue:conversation'; +/** How often a live turn refreshes the engine's transition watchdog. */ +const TURN_HEARTBEAT_MS = 30_000; + +/** Backoff between idle passes that found unsettled work nothing could claim yet. */ +function deferralBackoffMs(deferrals: number): number { + return Math.min(60_000, 1_000 * 2 ** Math.min(deferrals, 6)); +} + +type ConversationState = + | { readonly phase: 'idle'; readonly deferrals: number } + | { readonly phase: 'turn'; readonly submissionId: string; readonly attemptId: string }; + +const IDLE: ConversationState = { phase: 'idle', deferrals: 0 }; import type { SqlStorage } from '../sql-storage.ts'; @@ -80,6 +89,29 @@ interface CloudflareAgentStorage { transactionSync?(closure: () => T): T; } +type CloudflareTaskRunState = + | 'pending' + | 'running' + | 'waiting' + | 'completed' + | 'failed' + | 'cancelled'; + +/** The slice of the Agents SDK `Tasks` capability this coordinator uses. */ +interface CloudflareAgentTasks { + run( + definition: string, + input: unknown, + options: { runId: string }, + ): Promise<{ accepted: boolean; state: CloudflareTaskRunState }>; + send( + runId: string, + payload: unknown, + options: { kind: string; requestId: string }, + ): Promise<{ accepted: boolean }>; + reopen(runId: string): Promise; +} + interface CloudflareAgentInstance { readonly name: string; readonly env: Record; @@ -87,39 +119,36 @@ interface CloudflareAgentInstance { readonly id: { toString(): string }; readonly storage: CloudflareAgentStorage; /** - * DurableObjectState.waitUntil. Optional because test fakes and older - * SDK surfaces may omit it; a detached attempt fiber survives without - * it (the durable keepAlive alarm chain re-enters through fiber - * recovery), waitUntil just tells the platform the work is deliberate. + * DurableObjectState.waitUntil. Optional because test fakes may omit + * it; it only tells the platform that fire-and-forget event delivery + * left behind by an invocation is deliberate. */ waitUntil?(promise: Promise): void; }; - schedule( - delaySeconds: number, - callback: string, - payload: undefined, - options: { idempotent: boolean }, - ): Promise; - runFiber( - name: string, - callback: (ctx: { stash(snapshot: unknown): void }) => Promise, - ): Promise; + readonly tasks: CloudflareAgentTasks; } -interface CloudflareAgentRecoveredFiberContext { - readonly name?: string; - readonly snapshot?: Record; +/** The slice of the Agents SDK machine context the conversation's phases use. */ +export interface CloudflareMachineContext { + /** Aborts for the whole invocation: on `cancel()` and when the watchdog fires. */ + readonly signal: AbortSignal; + /** The abort mark inside `onCancel`; null in a phase handler. */ + readonly cancelling: string | null; + receiveAll(filter?: { within?: number }): Promise; + peekAll(): ReadonlyArray<{ readonly key: string }>; + withdraw(key: string): boolean; + heartbeat(): void; + aborted(reason?: string): unknown; } -/** - * Handle for a started attempt. `running` is the guarded fiber promise - * (never rejects; resolves after settlement AND cleanup). Wrapped in an - * object so async plumbing can hand it around without the runtime flattening - * a returned promise into an inline await of the whole attempt. - */ -interface StartedSubmissionAttempt { - readonly submissionId: string; - readonly running: Promise; +/** The machine definition the generated agent class declares for the conversation. */ +export interface CloudflareConversationDefinition { + readonly initial: ConversationState; + readonly phases: { + readonly idle: (state: ConversationState, ctx: CloudflareMachineContext) => Promise; + readonly turn: (state: ConversationState, ctx: CloudflareMachineContext) => Promise; + }; + readonly onCancel: (state: ConversationState, ctx: CloudflareMachineContext) => Promise; } interface CloudflareAgentPreparedCoordinator { @@ -160,24 +189,25 @@ export interface CloudflareAgentRuntime { inherited: () => Promise | unknown, ): Promise; /** - * The single place submission attempts start. Dispatched by the - * `__flueWakeAgentSubmissions` schedule target from inside the Durable - * Object's alarm invocation as one bounded, storage-only supervisor - * pass: it reconciles durable state, enforces deadlines, and starts - * attempt fibers detached — the pass returns without awaiting agent - * execution, so a hung attempt can never block supervision, and the - * pass arms its successor heartbeat before doing any failable work. - * Attempt fibers ride the SDK's runFiber keepAlive/recovery machinery - * across invocations and isolates. Every other boundary only records - * durable intent and arms this drain. + * The conversation machine the generated class declares under + * `FLUE_CONVERSATION_TASK`: its `idle` phase waits for work and claims + * one submission, its `turn` phase processes it to settlement, and its + * `onCancel` abandons a hung turn to the ledger's reconciliation. + */ + conversationDefinition(instance: CloudflareAgentInstance): CloudflareConversationDefinition; + /** + * The SDK recorded a terminal failure of the conversation run — a fault, + * a missing definition after a deploy. The ledger still holds unsettled + * work: bring the run back and wake it so the idle pass reconciles. + */ + /** + * The SDK recorded a terminal Task failure without running (or over) a + * handler — a deadline, an exhausted budget, a missing definition. The + * submission row it owned is still unsettled: ensure a drive run so the + * reconcile pass settles it from evidence. */ - drainSubmissions(instance: CloudflareAgentInstance): Promise; + onTaskError(instance: CloudflareAgentInstance, error: unknown): Promise; onRequest(instance: CloudflareAgentInstance, request: Request): Promise; - onFiberRecovered( - instance: CloudflareAgentInstance, - ctx: CloudflareAgentRecoveredFiberContext, - inherited: () => Promise | unknown, - ): Promise; /** * Run the Agents SDK alarm handler inside the instance context. Alarms * dispatch `schedule`/`scheduleEvery`/`queue` callbacks to methods on the @@ -220,15 +250,25 @@ export function createCloudflareAgentRuntime( onStart(instance, inherited) { return getCoordinator(instance).onStart(inherited); }, - drainSubmissions(instance) { - return getCoordinator(instance).drainSubmissions(); + conversationDefinition(instance) { + // Declared from the generated class's field initializer, which runs + // before `attach` — so the coordinator is looked up when a phase + // runs, never when the definition is built. + return { + initial: IDLE, + phases: { + idle: (state, ctx) => getCoordinator(instance).idle(state, ctx), + turn: (state, ctx) => getCoordinator(instance).turn(state, ctx), + }, + onCancel: (state, ctx) => getCoordinator(instance).onCancel(state, ctx), + }; + }, + onTaskError(instance, error) { + return getCoordinator(instance).onTaskError(error); }, onRequest(instance, request) { return getCoordinator(instance).onRequest(request); }, - onFiberRecovered(instance, ctx, inherited) { - return getCoordinator(instance).onFiberRecovered(ctx, inherited); - }, onAlarm(instance, inherited) { return getCoordinator(instance).onAlarm(inherited); }, @@ -267,169 +307,167 @@ class CloudflareAgentCoordinator { */ private readonly mcpConnections = createMcpConnectionCache(); /** - * Abort controllers for in-flight attempt fibers in this isolate, keyed by - * submissionId, so an incoming cancel request can abort the running attempt. - * The DO is single-threaded but interleaves at `await` points, so a cancel - * request can set the controller while the fiber is suspended on provider - * I/O. If the isolate is evicted the controller is gone and the abort - * falls back to the durable `abortRequestedAt` + reconcile path. + * Abort controllers for attempts live in this isolate, keyed by + * submissionId, so an incoming cancel request can abort the running + * attempt. The DO is single-threaded but interleaves at `await` points, + * so a cancel request can fire the controller while the attempt is + * suspended on provider I/O. If the isolate is evicted the controller is + * gone and the abort falls back to the durable `abortRequestedAt` + + * reconcile path. */ private activeControllers = new Map(); /** - * When each live attempt's controller was first fired by deadline - * enforcement, keyed by the controller so a replacement attempt starts - * its own clock. In-memory is exactly coextensive with the enforcement - * itself: both only apply to fibers live in this isolate. + * Attempt ids this isolate claimed and handed to an attempt run that has + * not yet entered its handler body. The run's first execution takes the + * id out and processes the claim as-is; a body that finds no entry is a + * replay on a fresh isolate (or after its previous execution ended) and + * must reconcile the row from durable evidence before doing anything. */ - private attemptDeadlineSignaledAt = new WeakMap(); + private readonly startedAttempts = new Set(); - // Instance context is established at exactly two boundaries: the public - // coordinator entry points below (onStart/drainSubmissions/onRequest/ - // onFiberRecovered/onAlarm) and the durable submission fiber in - // startSubmissionAttempt. These are the only ways execution enters the - // Durable Object, and a recovered fiber resumes with no ambient context, so - // each must (re)establish it. onAlarm wraps the Agents SDK alarm handler, - // covering every scheduled callback it dispatches — including - // extension-authored schedule/scheduleEvery/queue targets (#437); Flue's - // own wake target still self-wraps via drainSubmissions, which simply - // nests. Everything reachable from these boundaries — dispatch admission, - // reconciliation, materialization, submission processing — assumes the - // context is already present and never re-wraps. + // Instance context is established at the boundaries where execution + // enters the Durable Object: onStart, onRequest, onAlarm, and the two + // Task handler bodies (drive, attempt). A Task handler may run on a fresh + // isolate with no ambient context, so each body (re)establishes it. + // onAlarm wraps the Agents SDK alarm handler, covering every scheduled + // callback it dispatches — including extension-authored + // schedule/scheduleEvery/queue targets (#437). Everything reachable from + // these boundaries — dispatch admission, reconciliation, materialization, + // submission processing — assumes the context is already present and + // never re-wraps. // - // Execution ownership: attempts start ONLY inside drainSubmissions' - // supervisor pass, which runs as an alarm-dispatched schedule callback. - // The pass is bounded and storage-only — it never awaits agent execution. - // Attempt fibers run detached: the SDK's runFiber keepAlive holds a ≤30s - // durable alarm chain for a fiber's whole lifetime, and fiber recovery - // re-enters through onFiberRecovered if the isolate dies with the fiber. - // Supervision therefore never inherits an attempt's liveness: a hung - // await inside a fiber cannot block deadline enforcement, and the - // heartbeat is armed BEFORE the pass does any failable work, so no - // single pass failure (throw, platform cancellation, code-update reset) - // can break the wake chain — a lost wake costs one heartbeat of latency, - // never settlement. All other boundaries (admission, abort, onStart, - // onFiberRecovered) record durable intent and arm the drain. The SDK - // deletes a one-shot schedule row only AFTER its callback returns, so an - // armed row doubles as durable recovery on isolate death. + // Execution ownership: attempts start ONLY from the drive run's + // reconcile pass, each as its own `flue:attempt@v1` Task run whose + // handler body awaits the submission to settlement. The Agents SDK owns + // the durable wake (a claim backstop while the run is held, a replay on + // a fresh isolate after an interruption), the deadline (the submission's + // durability timeout, settled over a hung attempt with its later writes + // fenced out), and the attempt-wide abort signal. All other boundaries + // (admission, abort, onStart, Task failures) record durable intent and + // ensure the drive run exists; joining an existing one is free. onStart(inherited: () => Promise | unknown): Promise { return this.runWithInstanceContext(async () => { - // A fresh isolate has no live attempt by definition, so unsettled - // work needs nothing beyond a drain: its reconcile pass classifies - // interrupted attempts directly. Arm before the (possibly - // extension-authored) inherited onStart — the durable driver must be - // in place even if extension startup throws. - await this.armDrainIfUnsettled(); + await this.wakeIfUnsettled('start'); await inherited(); }); } - /** - * In-isolate serialization of supervisor passes. Alarm-dispatched wakes - * are serialized by the platform (one `alarm()` at a time), but a direct - * call while a pass is live must not interleave with it. A promise chain - * instead of a boolean guard: a wake that arrives mid-pass queues its own - * full pass rather than being silently consumed — no code path can eat a - * wake without doing (or scheduling) the work it promised. Passes are - * bounded and storage-only, so the chain drains in bounded time. + * Wait for work, then claim one submission. Mailbox items are wakes, not + * the queue: the ledger decides what runs next, so every item is taken + * and discarded before the ledger is read. With nothing unsettled the run + * parks with no alarm at all; with unsettled work nothing can claim yet + * — a materialization deferred, a settlement pending — it parks on a + * growing backoff that any send cuts short. */ - private supervisorChain: Promise = Promise.resolve(); - - drainSubmissions(): Promise { - const pass = this.supervisorChain.then(() => - this.runWithInstanceContext(() => this.supervisorPass()), - ); - // The chain absorbs rejections so one failed pass can't wedge every - // later one; the caller's `pass` still rejects, preserving the SDK's - // schedule-callback deferral semantics (code-update resets and - // transient platform errors rethrow so the preserved row re-runs). - this.supervisorChain = pass.then( - () => undefined, - () => undefined, - ); - return pass; + idle(state: ConversationState, ctx: CloudflareMachineContext): Promise { + return this.runWithInstanceContext(async () => { + for (const item of ctx.peekAll()) ctx.withdraw(item.key); + if (!(await this.submissions.hasUnsettledSubmissions())) { + await ctx.receiveAll(); + return IDLE; + } + const claims = await interceptExecution( + { type: 'coordinator', phase: 'reconcile' }, + { instanceId: this.instance.name, agentName: this.agentName }, + () => this.reconcileSubmissions(), + ); + this.instance.ctx.waitUntil?.(drainGlobalEventDeliveries()); + const head = claims[0]; + if (head?.attemptId) { + this.startedAttempts.add(head.attemptId); + return { phase: 'turn' as const, submissionId: head.submissionId, attemptId: head.attemptId }; + } + const deferrals = state.phase === 'idle' ? state.deferrals + 1 : 1; + await ctx.receiveAll({ within: deferralBackoffMs(deferrals) }); + return { phase: 'idle' as const, deferrals }; + }); } - /** - * One bounded supervisor pass: arm the heartbeat, reconcile durable - * state (settlement finalization, interrupted-attempt recovery, deadline - * enforcement), claim runnable work, and start attempt fibers WITHOUT - * awaiting them — fibers settle their submissions themselves and re-enter - * through onFiberSettled. The pass never waits on agent execution, so - * supervision stays live no matter what any attempt is doing. + * Process the claimed submission to settlement. The claim was made by the + * idle pass in this isolate; a turn re-dispatched without that memory is + * a replay after an interruption, and the ledger and canonical stream + * decide whether the submission settled, needs a replacement attempt, or + * is spent. */ - private async supervisorPass(): Promise { - if (!(await this.submissions.hasUnsettledSubmissions())) return; - // Heartbeat-first: the successor wake is armed before any failable - // work, so a pass that throws, or an invocation the platform cancels, - // leaves a live wake behind. The extra rows this arms are cheap no-op - // passes. Non-idempotent — an idempotent arm from inside this callback - // could dedupe onto the row being executed, which the SDK deletes - // after return, losing the wake. - await this.armBackstop(); - // The reconcile pass is storage-only and runs under the - // `flue.coordinator` interception so tracing backends can group - // its platform-instrumented storage spans. Attempt fibers start - // AFTER the interception settles, deliberately: `runFiber` runs - // its callback in the caller's async context, so a fiber started - // inside the span's activation would re-parent its invoke_agent - // span under the coordinator span. A claim whose start is - // preempted here (crash, code-update reset) is an interrupted - // attempt with no live controller — the next pass's - // running-recovery loop reconciles it, and a durable abort in the - // claim-to-start gap resolves through the existing - // abortRequestedAt path. - const claims = await interceptExecution( - { type: 'coordinator', phase: 'reconcile' }, - { instanceId: this.instance.name, agentName: this.agentName }, - () => this.reconcileSubmissions(), - ); - for (const claimed of claims) { - try { - const attempt = await this.startSubmissionAttempt(claimed); - if (attempt) this.watchAttempt(attempt); - } catch (error) { - this.logSubmissionReconciliationFailure(claimed, 'start_submission', error); + turn(state: ConversationState, ctx: CloudflareMachineContext): Promise { + return this.runWithInstanceContext(async () => { + if (state.phase !== 'turn') return IDLE; + const row = await this.submissions.getSubmission(state.submissionId); + if (row?.status !== 'running' || row.attemptId !== state.attemptId) return IDLE; + if (!this.startedAttempts.delete(state.attemptId)) { + const replacement = await this.reconcileInterruptedSubmission(row); + if (!replacement?.attemptId) return IDLE; + this.startedAttempts.add(replacement.attemptId); + return { + phase: 'turn' as const, + submissionId: replacement.submissionId, + attemptId: replacement.attemptId, + }; } - } - // observe() deliveries are fire-and-forget on the emit path; hand - // whatever this pass emitted (deadline signals, force settlements) to - // the platform so the invocation's end can't tear them down mid-POST. - this.instance.ctx.waitUntil?.(drainGlobalEventDeliveries()); + await this.runAttempt(row, ctx); + return IDLE; + }); } - /** - * Detach an attempt fiber from the pass that started it. `running` is - * catch-guarded and finally-cleaned in startSubmissionAttempt, so the - * tail here cannot reject. When the fiber settles its submission, any - * queued work behind it becomes runnable — arm a zero-delay wake so the - * queue progresses promptly instead of waiting out the heartbeat. A - * fiber that ends with its submission still unsettled (a failure inside - * settlement itself) deliberately does NOT arm: the heartbeat owns that - * class at polling cadence, which throttles a pathological - * claim/crash/reclaim cycle instead of hot-looping it. + * The abort protocol reached the conversation. A cancel of the run itself + * ends it; the transition watchdog or the memory breaker interrupting a + * hung turn abandons that attempt — its controller aborted, its writer + * rotated so a zombie's appends are refused — and the machine resumes at + * `idle`, where the ledger reconciles the abandoned attempt. */ - private watchAttempt(attempt: StartedSubmissionAttempt): void { - const tail = attempt.running.then(async () => { - try { - const settled = - (await this.submissions.getSubmission(attempt.submissionId))?.status === 'settled'; - if (settled && (await this.submissions.hasUnsettledSubmissions())) { - await this.armDrain(); + onCancel(state: ConversationState, ctx: CloudflareMachineContext): Promise { + return this.runWithInstanceContext(async () => { + if (state.phase === 'turn') { + const controller = this.activeControllers.get(state.submissionId); + if (controller) { + controller.abort( + ctx.cancelling === 'cancel' ? new SubmissionAbortedError() : new SubmissionTimeoutError(), + ); + this.orphanEnforcedAttempt(state.submissionId, controller); } - } catch { - // Wake arming is best-effort here: the heartbeat armed by the - // starting pass (and every pass since) covers the work. } - // The fiber's settlement events (submission_settled and any - // subscriber bridge work they trigger) ride this waitUntil too. - await drainGlobalEventDeliveries(); + if (ctx.cancelling === 'cancel' || ctx.cancelling === 'seal') { + return ctx.aborted(ctx.cancelling); + } + return IDLE; + }); + } + private async runAttempt(submission: AgentSubmission, ctx: CloudflareMachineContext): Promise { + const controller = new AbortController(); + this.activeControllers.set(submission.submissionId, controller); + const onRunAbort = () => controller.abort(submissionAbortReason(ctx.signal.reason)); + if (ctx.signal.aborted) onRunAbort(); + else ctx.signal.addEventListener('abort', onRunAbort, { once: true }); + const deadline = submission.timeoutAt > 0 ? submission.timeoutAt : undefined; + const deadlineTimer = + deadline === undefined + ? undefined + : setTimeout( + () => controller.abort(new SubmissionTimeoutError()), + Math.max(0, deadline - Date.now()), + ); + // The engine's transition watchdog is the safety net for a turn that + // hangs past its own timeout; until then, a live turn keeps it fed. + const heartbeat = setInterval(() => { + if (deadline !== undefined && Date.now() >= deadline) return; + ctx.heartbeat(); + }, TURN_HEARTBEAT_MS); + try { + await this.processSubmissionEntry(submission, controller.signal); + } finally { + clearInterval(heartbeat); + if (deadlineTimer !== undefined) clearTimeout(deadlineTimer); + ctx.signal.removeEventListener('abort', onRunAbort); + this.deleteControllerIfCurrent(submission.submissionId, controller); + this.instance.ctx.waitUntil?.(drainGlobalEventDeliveries()); + } + } + onTaskError(error: unknown): Promise { + return this.runWithInstanceContext(async () => { + if (!isTaskRecordedFailure(error)) return; + await this.wakeIfUnsettled('recover'); }); - // Tell the platform the invocation intentionally leaves work running. - // Without waitUntil the fiber still survives: runFiber's keepAlive - // alarm chain re-wakes the DO and fiber recovery re-enters the drain. - this.instance.ctx.waitUntil?.(tail); } - onRequest(request: Request): Promise { return this.runWithInstanceContext(async () => { try { @@ -500,24 +538,6 @@ class CloudflareAgentCoordinator { }); } - /** - * The SDK detected one of our attempt fibers interrupted (crash replay on - * a fresh isolate, or a survived run row after a reset). The durable - * submission row already carries everything recovery needs — the drain's - * reconcile pass classifies it — so the only job here is ensuring a drain - * runs. Resolving (not throwing) tells the SDK the recovery is handled, - * which deletes its run row. - */ - onFiberRecovered( - ctx: CloudflareAgentRecoveredFiberContext, - inherited: () => Promise | unknown, - ): Promise { - return this.runWithInstanceContext(async () => { - if (ctx.name !== FLUE_AGENT_SUBMISSION_ATTEMPT_FIBER) return inherited(); - await this.armDrain(); - }); - } - private get agentName(): string { return this.prepared.agentName; } @@ -576,46 +596,43 @@ class CloudflareAgentCoordinator { return ctx; } - private assertAgentsDurabilityApi(method: 'runFiber' | 'schedule'): void { - if (typeof this.instance[method] !== 'function') { + private get tasks(): CloudflareAgentTasks { + const tasks = this.instance.tasks; + if (!tasks || typeof tasks.run !== 'function') { throw new Error( - `[flue] The installed "agents" package does not provide the required Cloudflare Agents SDK method "${method}". Upgrade @flue/vite (which supplies the Cloudflare Agents SDK), or remove the "agents" dependency from your project if it declares an older one.`, + '[flue] The installed "agents" package does not provide the Cloudflare Agents SDK Tasks capability on Agent. Upgrade @flue/vite (which supplies the Cloudflare Agents SDK), or remove the "agents" dependency from your project if it declares an older one.', ); } + return tasks; } /** - * Arm a zero-delay drain. Always non-idempotent: an idempotent arm can - * dedupe onto a drain row that is currently executing — the SDK keeps the - * row in `cf_agents_schedules` until its callback returns, then deletes - * it — so an admission landing in the tail of a running drain would lose - * its wake entirely. A fresh row per arm closes that race; extra rows - * fire cheap no-op drains. + * The conversation's run: one fixed address, joined when it exists and + * brought back to `pending` when a fault ended it, so an admission never + * finds a dead executor. */ - private armDrain(): Promise { - this.assertAgentsDurabilityApi('schedule'); - return this.instance.schedule(0, FLUE_AGENT_SUBMISSION_WAKE_CALLBACK, undefined, { - idempotent: false, + private async ensureConversation(): Promise { + const receipt = await this.tasks.run(FLUE_CONVERSATION_TASK, undefined, { + runId: FLUE_CONVERSATION_RUN_ID, }); + if (!receipt.accepted && (receipt.state === 'failed' || receipt.state === 'cancelled')) { + await this.tasks.reopen(FLUE_CONVERSATION_RUN_ID); + } } - - /** 30s backstop wake onto the same schedule target (see armDrain for why non-idempotent). */ - private armBackstop(): Promise { - this.assertAgentsDurabilityApi('schedule'); - return this.instance.schedule( - FLUE_AGENT_SUBMISSION_WAKE_SECONDS, - FLUE_AGENT_SUBMISSION_WAKE_CALLBACK, - undefined, - { idempotent: false }, + /** Wake the conversation: one mailbox item, deduplicated by its key. */ + private async wake(kind: 'submission' | 'abort' | 'wake', key: string): Promise { + await this.ensureConversation(); + await this.tasks.send( + FLUE_CONVERSATION_RUN_ID, + { kind, key }, + { kind, requestId: `${kind}:${key}` }, ); } - - private async armDrainIfUnsettled(): Promise { + private async wakeIfUnsettled(reason: string): Promise { if (!(await this.submissions.hasUnsettledSubmissions())) return false; - await this.armDrain(); + await this.wake('wake', `${reason}:${Date.now()}`); return true; } - /** * One reconcile pass: materialize unready submissions, finalize pending * settlements, recover interrupted attempts, enforce deadlines on live @@ -712,43 +729,62 @@ class CloudflareAgentCoordinator { } } for (const submission of await this.submissions.listRunningSubmissions()) { - // Attempt fibers run detached from supervisor passes, so a running - // row here is live exactly when its attempt controller is still - // registered in this isolate. No controller means the attempt is - // dead (isolate replacement, fiber crash past its cleanup, or a - // start preempted in the claim-to-start gap) and the row goes to - // interrupted-attempt reconciliation. A live row is left alone - // until a deadline demands enforcement — see - // enforceLiveAttemptDeadline. A zombie fiber that outlives either - // path is fenced by the claim CAS and attempt-id ownership checks - // on every durable write. - const liveController = this.activeControllers.get(submission.submissionId); - if (liveController && !this.enforceLiveAttemptDeadline(submission)) { - continue; - } + // A running row whose attempt run the SDK still holds belongs to + // that run: it is either live in this isolate, or interrupted and + // due for the SDK's replay, whose handler body reconciles it. + // Reconciling here too would race that replay for the claim. + // A running row with NO attempt run is this pass's to recover: + // the run settled over a hung attempt (its deadline), failed + // without running (definition missing), or the claim-to-start + // gap was preempted. When the hung attempt is still live here it + // is signaled and orphaned first, so its late writes lose the + // settlement CAS and attempt-id fences and it can no longer + // append through the shared writer. try { + const liveController = this.activeControllers.get(submission.submissionId); + if (liveController) { + liveController.abort( + submission.abortRequestedAt !== undefined + ? new SubmissionAbortedError() + : new SubmissionTimeoutError(), + ); + console.error('[flue:submission-reconciliation]', { + agentName: this.agentName, + instanceId: this.instance.name, + submissionId: submission.submissionId, + sessionKey: submission.sessionKey, + attemptId: submission.attemptId, + operation: 'enforce_deadline', + outcome: 'terminated', + reason: + submission.abortRequestedAt !== undefined ? 'abort_unhonored' : 'exceeded_timeout', + }); + } const replacement = await this.reconcileInterruptedSubmission(submission); - // The attempt fiber starts after the reconcile pass returns — - // see supervisorPass for why starts must escape the pass's - // tracing activation. + // The attempt run starts after the reconcile pass returns — + // see drive for why starts must escape the pass's tracing + // activation. if (replacement) toStart.push(replacement); if (liveController) this.orphanEnforcedAttempt(submission.submissionId, liveController); } catch (error) { this.logSubmissionReconciliationFailure(submission, 'reconcile_submission', error); } } - for (const submission of await this.submissions.listRunnableSubmissions()) { - // Cloudflare DOs are single-threaded per instance — leases are - // advisory-only. Set to 0 so reconciliation never misidentifies - // an active submission as expired. The Node coordinator uses real - // lease expiry with heartbeat renewal for multi-process safety. - const claimed = await this.submissions.claimSubmission({ - submissionId: submission.submissionId, - attemptId: generateAttemptId(), - ownerId: this.instance.ctx.id.toString(), - leaseExpiresAt: 0, - }); - if (claimed) toStart.push(claimed); + // One turn at a time: the first runnable head is claimed; the rest + // wait for the idle pass that follows this turn. + if (toStart.length === 0) { + for (const submission of await this.submissions.listRunnableSubmissions()) { + const claimed = await this.submissions.claimSubmission({ + submissionId: submission.submissionId, + attemptId: generateAttemptId(), + ownerId: this.instance.ctx.id.toString(), + leaseExpiresAt: 0, + }); + if (claimed) { + toStart.push(claimed); + break; + } + } } } catch (error) { console.error( @@ -845,71 +881,6 @@ class CloudflareAgentCoordinator { ); } - /** - * Deadline enforcement for an attempt fiber that is still live in this - * isolate. When a deadline has passed — the durability timeout, or a - * durable abort intent the fiber has not honored — fire the attempt's - * abort controller so a signal-aware await unwinds through the fiber's - * own settle path (`processSubmission` settles failed/aborted durably - * and emits the live event). Returns true — demand interrupted-attempt - * reconciliation NOW, settling over the live fiber — only once the - * deadline is more than FLUE_AGENT_SUBMISSION_SETTLE_GRACE_MS old and - * the fiber still has not settled: a signal-deaf hang (a sandbox RPC - * pending against a healthy-reporting container, a wedged stream - * reader). The hung fiber is orphaned; its late writes lose the - * settlement CAS and attempt-id fences. - */ - private enforceLiveAttemptDeadline(submission: AgentSubmission): boolean { - const now = Date.now(); - const timedOut = submission.timeoutAt > 0 && now >= submission.timeoutAt; - const abortRequested = submission.abortRequestedAt !== undefined; - if (!timedOut && !abortRequested) return false; - const controller = this.activeControllers.get(submission.submissionId); - if (!controller) return true; - // Abort intent wins over timeout, mirroring the settle-order in - // reconcileInterruptedSubmission. AbortController.abort is idempotent, - // so re-signaling on every pass is safe. - controller.abort(abortRequested ? new SubmissionAbortedError() : new SubmissionTimeoutError()); - // The grace is anchored to when the fiber was first SIGNALED, not to - // the deadline itself: a delayed first pass (late alarms) must not - // abort and force-settle in the same breath. Abort intents were - // signaled at request time (abortInstance fires the controller in - // this isolate), so they seed from abortRequestedAt. - const signaledAt = this.attemptDeadlineSignaledAt.get(controller); - const deadline = - signaledAt ?? - (abortRequested && submission.abortRequestedAt !== undefined - ? Math.min(submission.abortRequestedAt, now) - : now); - if (signaledAt === undefined) this.attemptDeadlineSignaledAt.set(controller, deadline); - if (now < deadline + FLUE_AGENT_SUBMISSION_SETTLE_GRACE_MS) { - this.emitCoordinatorEvent({ - type: 'submission_recovery', - submissionId: submission.submissionId, - kind: submission.kind, - operation: 'enforce_deadline', - outcome: 'deferred', - attemptCount: submission.attemptCount, - maxAttempts: submission.maxAttempts, - error: serializeSubmissionError( - abortRequested ? new SubmissionAbortedError() : new SubmissionTimeoutError(), - ), - }); - return false; - } - console.error('[flue:submission-reconciliation]', { - agentName: this.agentName, - instanceId: this.instance.name, - submissionId: submission.submissionId, - sessionKey: submission.sessionKey, - attemptId: submission.attemptId, - operation: 'enforce_deadline', - outcome: 'terminated', - reason: abortRequested ? 'abort_unhonored' : 'exceeded_timeout', - }); - return true; - } - /** * Recover one interrupted attempt. Returns the claimed replacement * submission (if recovery produced one) for the caller's reconcile pass @@ -934,79 +905,10 @@ class CloudflareAgentCoordinator { return replacement ?? undefined; } - /** - * Start one attempt fiber and return a handle carrying its guarded - * promise for the drain to await. The promise never rejects (failures - * settle durably in processSubmission and are logged here) and resolves - * only after the abort-controller cleanup, so the drain's next reconcile - * pass observes consistent state. - * The handle object exists because returning the promise directly from - * this async method would flatten it: callers would await the whole - * attempt instead of receiving something awaitable. - */ - private async startSubmissionAttempt( - submission: AgentSubmission, - ): Promise { - if (submission.status !== 'running' || !submission.attemptId) return undefined; - this.assertAgentsDurabilityApi('runFiber'); - const controller = new AbortController(); - this.activeControllers.set(submission.submissionId, controller); - let running: Promise; - try { - running = this.instance.runFiber(FLUE_AGENT_SUBMISSION_ATTEMPT_FIBER, async (fiberCtx) => { - fiberCtx.stash({ submissionId: submission.submissionId, attemptId: submission.attemptId }); - // The fiber is the second context boundary: it may resume on a - // fresh isolate (via the SDK's crash replay) with no ambient - // context, so it establishes its own. - await this.runWithInstanceContext(() => - this.processSubmissionEntry(submission, controller.signal), - ); - }); - } catch (error) { - this.deleteControllerIfCurrent(submission.submissionId, controller); - throw error; - } - return { - running: running - .catch((error) => { - console.error( - '[flue:submission-processing]', - { - agentName: this.agentName, - instanceId: this.instance.name, - submissionId: submission.submissionId, - operation: 'process', - outcome: 'failed', - }, - error, - ); - // Usually post-settlement (processSubmission settles failed - // durably and emits the live submission_settled before - // rethrowing); when the failure struck settlement itself the - // row is still unsettled and the backstop owns it. - this.emitCoordinatorEvent( - { - type: 'submission_recovery', - submissionId: submission.submissionId, - kind: submission.kind, - operation: 'process_submission', - outcome: 'deferred', - error: serializeSubmissionError(error), - }, - { errorInfo: classifyError(error) }, - ); - }) - .finally(() => { - this.deleteControllerIfCurrent(submission.submissionId, controller); - }), - submissionId: submission.submissionId, - }; - } - /** * Controllers are keyed by submissionId and shared across attempts, so a - * late cleanup from a superseded attempt (its fiber promise settling after - * a replacement attempt already registered its own controller) must not + * late cleanup from a superseded attempt (its body settling after a + * replacement attempt already registered its own controller) must not * delete the replacement's controller — that would sever the abort path * for a live attempt. */ @@ -1017,7 +919,7 @@ class CloudflareAgentCoordinator { } /** - * After deadline enforcement settled over a live-but-hung fiber, orphan + * After the reconcile pass settled over a live-but-hung attempt, orphan * it: drop its controller entry (its own finally-cleanup is unreachable) * and rotate the cached conversation writer so later sessions acquire a * fresh producer — a waking zombie's rejected append then fails only the @@ -1041,17 +943,17 @@ class CloudflareAgentCoordinator { ); const affected = await this.submissions.requestSessionAbort(sessionKey); if (affected.length === 0) return false; - // Abort any of those attempt fibers live in this isolate — - // processSubmission's catch settles them aborted and the fiber tail - // arms the next wake. Queued ones settle via the pre-execution abort - // check once a pass claims them; an evicted running attempt is driven - // by the durable flag through reconciliation, and a signal-deaf live - // fiber is force-settled by enforceLiveAttemptDeadline once the abort - // intent outlives the settle grace. + // Abort any of those attempts live in this isolate — + // processSubmission's catch settles them aborted and the attempt body + // ensures the next drive. Queued ones settle via the pre-execution + // abort check once a drive claims them; an evicted running attempt is + // driven by the durable flag through reconciliation, and a signal-deaf + // live attempt is settled over by its run deadline (the submission's + // durability timeout), after which the drive reconciles it aborted. for (const submissionId of affected) { this.activeControllers.get(submissionId)?.abort(new SubmissionAbortedError()); + await this.wake('abort', submissionId); } - await this.armDrain(); return true; } @@ -1127,7 +1029,7 @@ class CloudflareAgentCoordinator { const adoptedReceipt = async (submissionId: string) => { const reducedUid = (await loadReducedState()).uid; if (reducedUid === undefined) return undefined; - await this.armDrain(); + await this.wake('submission', submissionId); return { submissionId, offset: '-1', @@ -1212,7 +1114,7 @@ class CloudflareAgentCoordinator { ...(deduplicated ? { deduplicated: true as const } : {}), }; } finally { - await this.armDrain(); + await this.wake('submission', admitted.submissionId); } } @@ -1262,7 +1164,7 @@ class CloudflareAgentCoordinator { const adopted = await adoptKeyedSubmissionReplay(this.submissions, submissionInput); const adoptedUid = adopted ? (await loadReducedState()).uid : undefined; if (adopted && adoptedUid !== undefined) { - await this.armDrain(); + await this.wake('submission', adopted.submissionId); return Response.json({ submissionId: adopted.submissionId, acceptedAt: adopted.input.acceptedAt, @@ -1329,7 +1231,7 @@ class CloudflareAgentCoordinator { ...(deduplicated ? { deduplicated: true } : {}), }); } finally { - await this.armDrain(); + await this.wake('submission', submission.submissionId); } } catch (error) { // Structured body so the dispatch() caller's enqueue can rehydrate the @@ -1360,6 +1262,41 @@ class CloudflareAgentCoordinator { } } +/** + * Translate the Agents SDK's attempt-wide abort reason into the submission + * error `processSubmission` keys its settlement on: the run deadline is the + * submission's durability timeout; anything else (an SDK cancellation) is an + * abort. + */ +function submissionAbortReason(reason: unknown): Error { + return isTaskError(reason, 'TaskDeadlineExceededError') + ? new SubmissionTimeoutError() + : new SubmissionAbortedError(); +} + +/** + * Whether an error the Agents SDK reported through `onError` is one it + * recorded against a Task run without (or over) its handler — the cases + * that leave a running submission row behind with no run to finish it. + * Matched by name: `@flue/runtime` does not import `agents`. + */ +function isTaskRecordedFailure(error: unknown): boolean { + return ( + isTaskError(error, 'TaskDeadlineExceededError') || + isTaskError(error, 'TaskAttemptsExhaustedError') || + isTaskError(error, 'MissingTaskDefinitionError') + ); +} + +function isTaskError(error: unknown, name: string): boolean { + return ( + typeof error === 'object' && + error !== null && + 'name' in error && + (error as { name: unknown }).name === name + ); +} + function isInternalDispatchRequest(request: Request): boolean { return ( request.method === 'POST' && diff --git a/packages/runtime/src/cloudflare/flue-agent-class.ts b/packages/runtime/src/cloudflare/flue-agent-class.ts index a2281592e..a65496aff 100644 --- a/packages/runtime/src/cloudflare/flue-agent-class.ts +++ b/packages/runtime/src/cloudflare/flue-agent-class.ts @@ -8,15 +8,20 @@ * - `runtime.prepare(...)` runs BEFORE `super(ctx, env)` so the coordinator's * stores exist before the Agents SDK constructor can schedule work, then * `runtime.attach(this, prepared)` binds the coordinator to the instance. - * - `onStart` / `onRequest` / `onFiberRecovered` / the - * `__flueWakeAgentSubmissions` schedule target delegate to the shared - * Cloudflare agent runtime; `onStart`/`onFiberRecovered` forward to an - * inherited implementation when the (possibly extended) base defines one. + * - Submission execution runs on the Agents SDK `Tasks` capability the + * `Agent` base installs as `this.tasks`: `taskDefinitions` declares the two + * Flue definitions (a drive pass and a per-submission attempt), whose + * handlers delegate to the shared Cloudflare agent runtime. The SDK + * resolves the names against this field on every wake, so in-flight runs + * always find their handler. + * - `onStart` / `onRequest` / `alarm` / `onError` delegate to the runtime and + * forward to an inherited implementation when the (possibly extended) base + * defines one. * - The module's `extend({ base, wrap })` export is resolved via * `resolveCloudflareExtension`: `base` reshapes the superclass, `wrap` * wraps the final class, and the wrapped class is what gets exported. */ -import type { CloudflareAgentRuntime } from './agent-coordinator.ts'; +import { type CloudflareAgentRuntime, FLUE_CONVERSATION_TASK } from './agent-coordinator.ts'; import { type ExtensionClass, resolveCloudflareExtension } from './extension.ts'; type CloudflareAgentInstance = Parameters[0]; @@ -45,6 +50,15 @@ export interface CreateFlueAgentClassOptions { readonly extension?: unknown; } +/** + * Task definitions an extension `base` declared as an instance field. Base + * class fields are assigned before a subclass's own initializers run, so + * reading the property at that point sees exactly the inherited value. + */ +function inheritedTaskDefinitions(instance: object): Record { + return (instance as { taskDefinitions?: Record }).taskDefinitions ?? {}; +} + /** * Build the final (possibly extension-wrapped) Durable Object class for one * agent module. @@ -69,25 +83,25 @@ export function createFlueAgentClass(options: CreateFlueAgentClassOptions): Exte runtime.attach(this as unknown as CloudflareAgentInstance, prepared); } + /** + * Flue's Task definitions, merged over any the extension `base` + * declared. The Agents SDK reads this field lazily and re-resolves it + * on every wake, which is what makes replay of an in-flight run + * correct by construction. + */ + readonly taskDefinitions: Record = { + ...inheritedTaskDefinitions(this), + [FLUE_CONVERSATION_TASK]: runtime.conversationDefinition( + this as unknown as CloudflareAgentInstance, + ), + }; + onStart(props?: Record) { return runtime.onStart(this as unknown as CloudflareAgentInstance, () => typeof super.onStart === 'function' ? super.onStart(props) : undefined, ); } - /** - * Durable schedule target that owns submission supervision: armed at - * zero delay by admission/abort/recovery/fiber-settle boundaries and - * at 30s as the heartbeat while unsettled work exists. Dispatched - * from the Durable Object's alarm invocation as one bounded, - * storage-only pass that reconciles, enforces deadlines, and starts - * attempt fibers detached — the fibers outlive the invocation on the - * SDK's runFiber keepAlive/recovery machinery. - */ - __flueWakeAgentSubmissions() { - return runtime.drainSubmissions(this as unknown as CloudflareAgentInstance); - } - onRequest(request: Request) { return runtime.onRequest(this as unknown as CloudflareAgentInstance, request); } @@ -104,10 +118,22 @@ export function createFlueAgentClass(options: CreateFlueAgentClassOptions): Exte ); } - onFiberRecovered(ctx: { readonly name?: string; readonly snapshot?: Record }) { - return runtime.onFiberRecovered(this as unknown as CloudflareAgentInstance, ctx, () => - typeof super.onFiberRecovered === 'function' ? super.onFiberRecovered(ctx) : undefined, - ); + /** + * The SDK reports terminal Task failures here (`onError(error)`; the + * `(connection, error)` overload is WebSocket-only and never Flue's). + * A failure the SDK recorded without running a handler — a deadline + * settled over a hung attempt, a definition missing after a deploy — + * leaves a submission row for the runtime to reconcile. + */ + async onError(...args: unknown[]) { + try { + if (typeof super.onError === 'function') await super.onError(...args); + } finally { + await runtime.onTaskError( + this as unknown as CloudflareAgentInstance, + args.length >= 2 ? args[1] : args[0], + ); + } } } diff --git a/packages/vite/package.json b/packages/vite/package.json index 6251b5a7a..5014d3925 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -37,7 +37,7 @@ "dependencies": { "@flue/runtime": "workspace:*", "@hono/node-server": "^2.0.3", - "agents": "^0.20.1", + "agents": "^0.23.0", "magic-string": "^1.0.0", "tinyglobby": "^0.2.15", "ulidx": "^2.4.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ffed321f0..63c47ea8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -164,7 +164,7 @@ importers: version: 19.2.3(@types/react@19.2.17) '@vitejs/plugin-react': specifier: ^6.0.2 - version: 6.0.4(@rolldown/plugin-babel@0.2.3(@babel/core@8.0.1)(@babel/runtime@7.29.7)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 6.0.4(@rolldown/plugin-babel@0.2.4(@babel/core@8.0.5)(@babel/runtime@7.29.7)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) oxlint: specifier: ^1.69.0 version: 1.76.0 @@ -1846,8 +1846,8 @@ importers: specifier: ^2.0.3 version: 2.0.12(hono@4.12.32) agents: - specifier: ^0.20.1 - version: 0.20.1(@babel/core@8.0.1)(@babel/runtime@7.29.7)(@cloudflare/workers-types@5.20260731.1)(@modelcontextprotocol/client@2.0.0)(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.4.3))(@modelcontextprotocol/server@2.0.0)(chat@4.35.0(zod@4.4.3))(just-bash@3.2.0)(react@19.2.8)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))(zod@4.4.3) + specifier: ^0.23.0 + version: 0.23.0(@babel/core@8.0.5)(@babel/runtime@7.29.7)(@modelcontextprotocol/client@2.0.0)(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.4.3))(@modelcontextprotocol/server@2.0.0)(chat@4.35.0(zod@4.4.3))(just-bash@3.2.0)(react@19.2.8)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))(zod@4.4.3) magic-string: specifier: ^1.0.0 version: 1.1.0 @@ -2170,28 +2170,28 @@ packages: resolution: {integrity: sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/compat-data@8.0.0': - resolution: {integrity: sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==} + '@babel/compat-data@8.0.5': + resolution: {integrity: sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/core@8.0.1': - resolution: {integrity: sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==} + '@babel/core@8.0.5': + resolution: {integrity: sha512-2/oWkgTbBYoqioCWAE4XJobOrzwxTDa5/XjDP3tJ1BhDr/owcd9qnXBp4xc3/2G5X4bvXM04nrxbRJxFcXAxFQ==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/generator@8.0.0': - resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} + '@babel/generator@8.0.5': + resolution: {integrity: sha512-f/TuhuMAxJqhwxEGNsJrswuG9VHmh0oNFoQoo6TbpgtFAz9wYZXcTAcWZMHfp7ljesr0RG04bp3Aos9GI59L7w==} engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-annotate-as-pure@8.0.0': resolution: {integrity: sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-compilation-targets@8.0.0': - resolution: {integrity: sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==} + '@babel/helper-compilation-targets@8.0.5': + resolution: {integrity: sha512-Qk8ahMGooH5mz6uuhoDvfZGkUf/Mf3RTBucVVl4MKx4LKMTv872TeW8O92h15iVtlN8wAROBIpI1aV6x1z0LCQ==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-create-class-features-plugin@8.0.1': - resolution: {integrity: sha512-++t3ZktzlLmASAxIlxeXQK9Z2YwUafYGYcvGBFevqOqt16HozVHStUoQvWD09fzAZOb/uJGpUTBuGK41AJAuOA==} + '@babel/helper-create-class-features-plugin@8.0.5': + resolution: {integrity: sha512-ckUE7tmolBbW4QV02lD874sWSPy8da2A09i/h0qS+IWolR+WiNhpq7C/rHZm00uOIkUb0Cp6+0Cy1ku/wj5uQQ==} engines: {node: ^22.18.0 || >=24.11.0} peerDependencies: '@babel/core': ^8.0.0 @@ -2200,8 +2200,8 @@ packages: resolution: {integrity: sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-member-expression-to-functions@8.0.0': - resolution: {integrity: sha512-xkXrMbtk87Gk7+oKBVmBc6EORg/Qwx++AHESldmHkpvG8wgccdhJJFwrzqlF382Fk8wfXhJHWE/g/43QvEGNPQ==} + '@babel/helper-member-expression-to-functions@8.0.5': + resolution: {integrity: sha512-GLe05QD98BkNFTkjaqeqF9QSRoHLKrrB4tpoplyQuuPrQJ72rtmkM4wvqJLX/sjZPqkbK5MUYSsuUTqdJoOVjA==} engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-optimise-call-expression@8.0.0': @@ -2244,8 +2244,8 @@ packages: resolution: {integrity: sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helpers@8.0.0': - resolution: {integrity: sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==} + '@babel/helpers@8.0.5': + resolution: {integrity: sha512-fQtPOXjYOYv85PIdwotp2TJGVYOycX0PQq+l844fFAxOULtBy8BVF35GyeueX0r4KvDthqPH5xAI1clQPk/2uA==} engines: {node: ^22.18.0 || >=24.11.0} '@babel/parser@7.29.7': @@ -2253,8 +2253,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@8.0.4': - resolution: {integrity: sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==} + '@babel/parser@8.0.5': + resolution: {integrity: sha512-51RXvQNFakaS0bTpYiGkxNbUVwkPO4kONv6EVLorZABxsx+KZ6Z7uSYvi/wmKS/+X+rfj9RvOw0/ZNh+cmI0Rw==} engines: {node: ^22.18.0 || >=24.11.0} hasBin: true @@ -2282,16 +2282,16 @@ packages: resolution: {integrity: sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/traverse@8.0.4': - resolution: {integrity: sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==} + '@babel/traverse@8.0.5': + resolution: {integrity: sha512-XFfnuvapSc/vJOcUO7kwORSvpBIvraofKEZ2dhT0PjiF21BRCD7YbAFC8UEeDJNeLoQz82/gVqzgX5hCzkCbdg==} engines: {node: ^22.18.0 || >=24.11.0} '@babel/types@7.29.7': resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} - '@babel/types@8.0.4': - resolution: {integrity: sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==} + '@babel/types@8.0.5': + resolution: {integrity: sha512-eVdMqi3ej5aHhyQ2Si6yD2cAWeV8FJK9UrhK5aL0Sd8hu5GhT+YswhVNbVheOGVYMg8kuGuMaUpkB3stjj4z8A==} engines: {node: ^22.18.0 || >=24.11.0} '@biomejs/biome@2.5.6': @@ -2594,7 +2594,7 @@ packages: vitest: ^4.1.0 '@cloudflare/workerd-darwin-64@1.20260730.1': - resolution: {integrity: sha512-+MBHmPaiTe2KajryW0T24rZvWFxb41hD3d8anNzQqHzft6vSEb18+sp0znSwxgij7ApPhSM1+vhkNg4f3YMguA==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260730.1.tgz} + resolution: {integrity: sha512-+MBHmPaiTe2KajryW0T24rZvWFxb41hD3d8anNzQqHzft6vSEb18+sp0znSwxgij7ApPhSM1+vhkNg4f3YMguA==} engines: {node: '>=16'} cpu: [x64] os: [darwin] @@ -2606,19 +2606,19 @@ packages: os: [darwin] '@cloudflare/workerd-linux-64@1.20260730.1': - resolution: {integrity: sha512-ouyPOSMbiKPeSwUJUvxtMcxGAXs2J4aPE4T5ABIYX5ClcQx5j5bbHTmnqOQEY8sAuLTPjH7dY+iB6UI5ISlwwA==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260730.1.tgz} + resolution: {integrity: sha512-ouyPOSMbiKPeSwUJUvxtMcxGAXs2J4aPE4T5ABIYX5ClcQx5j5bbHTmnqOQEY8sAuLTPjH7dY+iB6UI5ISlwwA==} engines: {node: '>=16'} cpu: [x64] os: [linux] '@cloudflare/workerd-linux-arm64@1.20260730.1': - resolution: {integrity: sha512-YQ+Mi78U3TPdgBPtwq+Sm6rJU+Ihl2y0pjYtuuKkdmUbYzL7oLR6Xqq9wljhasnuCFICssDJaqhMep5WizYoEQ==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260730.1.tgz} + resolution: {integrity: sha512-YQ+Mi78U3TPdgBPtwq+Sm6rJU+Ihl2y0pjYtuuKkdmUbYzL7oLR6Xqq9wljhasnuCFICssDJaqhMep5WizYoEQ==} engines: {node: '>=16'} cpu: [arm64] os: [linux] '@cloudflare/workerd-windows-64@1.20260730.1': - resolution: {integrity: sha512-27fAN+vUECW1oYVc1KOcHYpkL8COM2Uxtxql7TL595kxbjoqS5yckw7NLz7bTf2pALFCZWjqXDjZGJ/xbG4ZKQ==, tarball: https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260730.1.tgz} + resolution: {integrity: sha512-27fAN+vUECW1oYVc1KOcHYpkL8COM2Uxtxql7TL595kxbjoqS5yckw7NLz7bTf2pALFCZWjqXDjZGJ/xbG4ZKQ==} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -3256,6 +3256,9 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/gen-mapping@0.4.0-beta.0': + resolution: {integrity: sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==} + '@jridgewell/remapping@2.3.5': resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} @@ -3266,6 +3269,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -5096,8 +5102,8 @@ packages: cpu: [x64] os: [win32] - '@rolldown/plugin-babel@0.2.3': - resolution: {integrity: sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==} + '@rolldown/plugin-babel@0.2.4': + resolution: {integrity: sha512-ygmm2j/mN+aCvstHo0x3IOu4uc6LqXT9SJ3q4kpfG/+Yhfz0B+E47FITcC0FljZADwUFARP6E2kiE+Lc26D8wg==} engines: {node: '>=22.12.0 || ^24.0.0'} peerDependencies: '@babel/core': ^7.29.0 || ^8.0.0-rc.1 @@ -6012,9 +6018,8 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - agents@0.20.1: - resolution: {integrity: sha512-HQRYMeZpD3k8djYBH7atRPojZMee3NvmXkzsmMWXfdHZ94vMljmWqSsD1XZd70LovHyQrw6/R81AZZIsRiFM6Q==} - hasBin: true + agents@0.23.0: + resolution: {integrity: sha512-V6K9vSaUn9AWQUKj4rQl6Q+4Q2EJ7+TYwaakY1uBXb9RJvSqIwv25SPjZKZA5Mjy0lCUnBoExJlzUeFERKYrUQ==} peerDependencies: '@ai-sdk/react': ^3.0.0 || ^4.0.0 '@cloudflare/codemode': '>=0.5.0' @@ -6047,6 +6052,8 @@ packages: optional: true just-bash: optional: true + react: + optional: true vite: optional: true @@ -6069,10 +6076,6 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -6081,10 +6084,6 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - ansis@4.3.1: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} @@ -6158,8 +6157,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.11.7: - resolution: {integrity: sha512-APw5YuIQAg6L9w4sHDI6j26DGFJI6RpYOhnkMPdC9lWbkKvsyPHzDsve1yd73lk21yz7Y09Kci8B2Pp9FonzWA==} + baseline-browser-mapping@2.11.23: + resolution: {integrity: sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -6206,8 +6205,8 @@ packages: peerDependencies: zod: ^3.25.34 || ^4.0 - browserslist@4.28.7: - resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} + browserslist@4.28.9: + resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -6248,8 +6247,11 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - caniuse-lite@1.0.30001806: - resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + + capnweb@0.12.0: + resolution: {integrity: sha512-jgZ/LMtMVTi+RVlooIslH78Gg23XbDTdvcLghWx3oz17D6u5GuJARt+uhZk/wcTo+f81eeabfnow4d3zLBj+JQ==} capnweb@0.8.0: resolution: {integrity: sha512-BK/TuXUiyfLSKsmjojn70yN7oYG/JJzoURZ3tckjg5Zj2KcygPm0A5jyOlswK7SYB4f0Gh9tt+RZ132b80iLfA==} @@ -6327,10 +6329,6 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - cliui@9.0.1: - resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} - engines: {node: '>=20'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -6417,8 +6415,8 @@ packages: resolution: {integrity: sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==} engines: {node: '>=22'} - core-js-pure@3.49.0: - resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==} + core-js-pure@3.50.0: + resolution: {integrity: sha512-6GP3Pxz4IKyWjAfa747vIu/jilB5z29JWROLqH/b+pXVcpgh6tM06ZIBwSuglgVqzDYURhOK6oEzTrG0bCHitA==} cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} @@ -6752,11 +6750,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.398: - resolution: {integrity: sha512-AsvhAxopJGh6museTDMIjn6JpDYOfgu4RLlygomt87MUwBUqTfd/1EiPtx10/LZE8xpTvkP2E9Gafq7lkLtodQ==} - - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + electron-to-chromium@1.5.427: + resolution: {integrity: sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -6919,8 +6914,8 @@ packages: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} - express-rate-limit@8.6.1: - resolution: {integrity: sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==} + express-rate-limit@8.7.0: + resolution: {integrity: sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' @@ -7066,10 +7061,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.6.0: - resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} - engines: {node: '>=18'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -7280,8 +7271,8 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - ip-address@10.3.1: - resolution: {integrity: sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==} + ip-address@10.7.0: + resolution: {integrity: sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -8058,8 +8049,8 @@ packages: node-mock-http@1.0.5: resolution: {integrity: sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==} - node-releases@2.0.51: - resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + node-releases@2.0.55: + resolution: {integrity: sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==} engines: {node: '>=18'} normalize-path@3.0.0: @@ -8190,11 +8181,6 @@ packages: partial-json@0.1.7: resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} - partyserver@0.5.9: - resolution: {integrity: sha512-0T1OGQD9Ggllrlp8Bt8XLOglEEktH4bui28Z1dbSGEX/iFmZnEWR148Qmvc1Od3rl64OAOfVtr2sG4XOMI9Y6Q==} - peerDependencies: - '@cloudflare/workers-types': ^4.20260424.1 || ^5.20260703.1 - partysocket@1.3.0: resolution: {integrity: sha512-1zToNyolZFK/7nuAw/K2bZrNzFqaZyRoCEkS+9vG6WSC5ikrN6qWRe96q6ImU51uptz2r+dAwSkwhJVdQi4LiA==} peerDependencies: @@ -8754,14 +8740,6 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - string-width@8.2.2: - resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} - engines: {node: '>=20'} - string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -8772,10 +8750,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -9135,8 +9109,8 @@ packages: uploadthing: optional: true - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.3.3: + resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -9198,6 +9172,10 @@ packages: resolution: {integrity: sha512-w2Eo8LSIIoW7qxNBzT7/17k+bh8plXo7G3dHjEIDqPlnluhzaxr9JX8F28VSYEtDvc1/a3WBDih6xNUZseebXg==} engines: {node: '>=18.12.0'} + verkit@0.3.2: + resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} + engines: {node: '>=18.12.0'} + vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -9374,10 +9352,6 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} - wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -9445,10 +9419,6 @@ packages: resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} engines: {node: '>=12'} - yargs@18.1.0: - resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yocto-queue@1.2.2: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} @@ -9913,18 +9883,18 @@ snapshots: '@babel/helper-validator-identifier': 8.0.4 js-tokens: 10.0.0 - '@babel/compat-data@8.0.0': {} + '@babel/compat-data@8.0.5': {} - '@babel/core@8.0.1': + '@babel/core@8.0.5': dependencies: '@babel/code-frame': 8.0.0 - '@babel/generator': 8.0.0 - '@babel/helper-compilation-targets': 8.0.0 - '@babel/helpers': 8.0.0 - '@babel/parser': 8.0.4 + '@babel/generator': 8.0.5 + '@babel/helper-compilation-targets': 8.0.5 + '@babel/helpers': 8.0.5 + '@babel/parser': 8.0.5 '@babel/template': 8.0.0 - '@babel/traverse': 8.0.4 - '@babel/types': 8.0.4 + '@babel/traverse': 8.0.5 + '@babel/types': 8.0.5 '@types/gensync': 1.0.5 convert-source-map: 2.0.0 empathic: 2.0.1 @@ -9932,66 +9902,66 @@ snapshots: import-meta-resolve: 4.2.0 json5: 2.2.3 obug: 2.1.4 - semver: 7.8.5 + verkit: 0.3.2 - '@babel/generator@8.0.0': + '@babel/generator@8.0.5': dependencies: - '@babel/parser': 8.0.4 - '@babel/types': 8.0.4 - '@jridgewell/gen-mapping': 0.3.13 + '@babel/parser': 8.0.5 + '@babel/types': 8.0.5 + '@jridgewell/gen-mapping': 0.4.0-beta.0 '@jridgewell/trace-mapping': 0.3.31 '@types/jsesc': 2.5.1 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@8.0.0': dependencies: - '@babel/types': 8.0.4 + '@babel/types': 8.0.5 - '@babel/helper-compilation-targets@8.0.0': + '@babel/helper-compilation-targets@8.0.5': dependencies: - '@babel/compat-data': 8.0.0 + '@babel/compat-data': 8.0.5 '@babel/helper-validator-option': 8.0.0 - browserslist: 4.28.7 + browserslist: 4.28.9 lru-cache: 11.5.2 - semver: 7.8.5 + verkit: 0.3.2 - '@babel/helper-create-class-features-plugin@8.0.1(@babel/core@8.0.1)': + '@babel/helper-create-class-features-plugin@8.0.5(@babel/core@8.0.5)': dependencies: - '@babel/core': 8.0.1 + '@babel/core': 8.0.5 '@babel/helper-annotate-as-pure': 8.0.0 - '@babel/helper-member-expression-to-functions': 8.0.0 + '@babel/helper-member-expression-to-functions': 8.0.5 '@babel/helper-optimise-call-expression': 8.0.0 - '@babel/helper-replace-supers': 8.0.1(@babel/core@8.0.1) + '@babel/helper-replace-supers': 8.0.1(@babel/core@8.0.5) '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 - '@babel/traverse': 8.0.4 - semver: 7.8.5 + '@babel/traverse': 8.0.5 + verkit: 0.3.2 '@babel/helper-globals@8.0.0': {} - '@babel/helper-member-expression-to-functions@8.0.0': + '@babel/helper-member-expression-to-functions@8.0.5': dependencies: - '@babel/traverse': 8.0.4 - '@babel/types': 8.0.4 + '@babel/traverse': 8.0.5 + '@babel/types': 8.0.5 '@babel/helper-optimise-call-expression@8.0.0': dependencies: - '@babel/types': 8.0.4 + '@babel/types': 8.0.5 - '@babel/helper-plugin-utils@8.0.1(@babel/core@8.0.1)': + '@babel/helper-plugin-utils@8.0.1(@babel/core@8.0.5)': dependencies: - '@babel/core': 8.0.1 + '@babel/core': 8.0.5 - '@babel/helper-replace-supers@8.0.1(@babel/core@8.0.1)': + '@babel/helper-replace-supers@8.0.1(@babel/core@8.0.5)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-member-expression-to-functions': 8.0.0 + '@babel/core': 8.0.5 + '@babel/helper-member-expression-to-functions': 8.0.5 '@babel/helper-optimise-call-expression': 8.0.0 - '@babel/traverse': 8.0.4 + '@babel/traverse': 8.0.5 '@babel/helper-skip-transparent-expression-wrappers@8.0.0': dependencies: - '@babel/traverse': 8.0.4 - '@babel/types': 8.0.4 + '@babel/traverse': 8.0.5 + '@babel/types': 8.0.5 '@babel/helper-string-parser@7.29.7': {} @@ -10003,51 +9973,51 @@ snapshots: '@babel/helper-validator-option@8.0.0': {} - '@babel/helpers@8.0.0': + '@babel/helpers@8.0.5': dependencies: '@babel/template': 8.0.0 - '@babel/types': 8.0.4 + '@babel/types': 8.0.5 '@babel/parser@7.29.7': dependencies: '@babel/types': 7.29.7 - '@babel/parser@8.0.4': + '@babel/parser@8.0.5': dependencies: - '@babel/types': 8.0.4 + '@babel/types': 8.0.5 - '@babel/plugin-proposal-decorators@8.0.2(@babel/core@8.0.1)': + '@babel/plugin-proposal-decorators@8.0.2(@babel/core@8.0.5)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-class-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-syntax-decorators': 8.0.1(@babel/core@8.0.1) + '@babel/core': 8.0.5 + '@babel/helper-create-class-features-plugin': 8.0.5(@babel/core@8.0.5) + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.5) + '@babel/plugin-syntax-decorators': 8.0.1(@babel/core@8.0.5) - '@babel/plugin-syntax-decorators@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-syntax-decorators@8.0.1(@babel/core@8.0.5)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 8.0.5 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.5) '@babel/runtime-corejs3@7.29.7': dependencies: - core-js-pure: 3.49.0 + core-js-pure: 3.50.0 '@babel/runtime@7.29.7': {} '@babel/template@8.0.0': dependencies: '@babel/code-frame': 8.0.0 - '@babel/parser': 8.0.4 - '@babel/types': 8.0.4 + '@babel/parser': 8.0.5 + '@babel/types': 8.0.5 - '@babel/traverse@8.0.4': + '@babel/traverse@8.0.5': dependencies: '@babel/code-frame': 8.0.0 - '@babel/generator': 8.0.0 + '@babel/generator': 8.0.5 '@babel/helper-globals': 8.0.0 - '@babel/parser': 8.0.4 + '@babel/parser': 8.0.5 '@babel/template': 8.0.0 - '@babel/types': 8.0.4 + '@babel/types': 8.0.5 obug: 2.1.4 '@babel/types@7.29.7': @@ -10055,7 +10025,7 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/types@8.0.4': + '@babel/types@8.0.5': dependencies: '@babel/helper-string-parser': 8.0.0 '@babel/helper-validator-identifier': 8.0.4 @@ -10927,6 +10897,11 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/gen-mapping@0.4.0-beta.0': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/remapping@2.3.5': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -10936,6 +10911,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -11114,7 +11091,7 @@ snapshots: eventsource: 3.0.7 eventsource-parser: 3.1.0 express: 5.2.1 - express-rate-limit: 8.6.1(express@5.2.1) + express-rate-limit: 8.7.0(express@5.2.1) hono: 4.12.32 jose: 6.2.5 json-schema-typed: 8.0.2 @@ -12694,10 +12671,10 @@ snapshots: '@rolldown/binding-win32-x64-msvc@1.2.1': optional: true - '@rolldown/plugin-babel@0.2.3(@babel/core@8.0.1)(@babel/runtime@7.29.7)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))': + '@rolldown/plugin-babel@0.2.4(@babel/core@8.0.5)(@babel/runtime@7.29.7)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))': dependencies: - '@babel/core': 8.0.1 - picomatch: 4.0.5 + '@babel/core': 8.0.5 + picomatch: 4.0.7 rolldown: 1.2.1 optionalDependencies: '@babel/runtime': 7.29.7 @@ -13326,12 +13303,12 @@ snapshots: optionalDependencies: '@aws-sdk/credential-provider-web-identity': 3.972.71 - '@vitejs/plugin-react@6.0.4(@rolldown/plugin-babel@0.2.3(@babel/core@8.0.1)(@babel/runtime@7.29.7)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))': + '@vitejs/plugin-react@6.0.4(@rolldown/plugin-babel@0.2.4(@babel/core@8.0.5)(@babel/runtime@7.29.7)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 vite: 8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) optionalDependencies: - '@rolldown/plugin-babel': 0.2.3(@babel/core@8.0.1)(@babel/runtime@7.29.7)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + '@rolldown/plugin-babel': 0.2.4(@babel/core@8.0.5)(@babel/runtime@7.29.7)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) '@vitest-evals/core@0.15.0': dependencies: @@ -13485,33 +13462,32 @@ snapshots: agent-base@7.1.4: {} - agents@0.20.1(@babel/core@8.0.1)(@babel/runtime@7.29.7)(@cloudflare/workers-types@5.20260731.1)(@modelcontextprotocol/client@2.0.0)(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.4.3))(@modelcontextprotocol/server@2.0.0)(chat@4.35.0(zod@4.4.3))(just-bash@3.2.0)(react@19.2.8)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))(zod@4.4.3): + agents@0.23.0(@babel/core@8.0.5)(@babel/runtime@7.29.7)(@modelcontextprotocol/client@2.0.0)(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.4.3))(@modelcontextprotocol/server@2.0.0)(chat@4.35.0(zod@4.4.3))(just-bash@3.2.0)(react@19.2.8)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))(zod@4.4.3): dependencies: - '@babel/plugin-proposal-decorators': 8.0.2(@babel/core@8.0.1) + '@babel/plugin-proposal-decorators': 8.0.2(@babel/core@8.0.5) '@cfworker/json-schema': 4.1.1 '@modelcontextprotocol/client': 2.0.0 '@modelcontextprotocol/sdk': 1.30.0(@cfworker/json-schema@4.1.1)(zod@4.4.3) '@modelcontextprotocol/server': 2.0.0 - '@rolldown/plugin-babel': 0.2.3(@babel/core@8.0.1)(@babel/runtime@7.29.7)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + '@rolldown/plugin-babel': 0.2.4(@babel/core@8.0.5)(@babel/runtime@7.29.7)(rolldown@1.2.1)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + capnweb: 0.12.0 cron-schedule: 6.0.0 esbuild: 0.28.1 mimetext: 3.0.28 nanoid: 5.1.16 - partyserver: 0.5.9(@cloudflare/workers-types@5.20260731.1) partysocket: 1.3.0(react@19.2.8) - react: 19.2.8 + postal-mime: 2.7.5 yaml: 2.9.0 - yargs: 18.1.0 zod: 4.4.3 optionalDependencies: chat: 4.35.0(zod@4.4.3) just-bash: 3.2.0 + react: 19.2.8 vite: 8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) transitivePeerDependencies: - '@babel/core' - '@babel/plugin-transform-runtime' - '@babel/runtime' - - '@cloudflare/workers-types' - rolldown ajv-formats@3.0.1(ajv@8.20.0): @@ -13531,16 +13507,12 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.2.2: {} - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 ansi-styles@5.2.0: {} - ansi-styles@6.2.3: {} - ansis@4.3.1: {} anymatch@3.1.3: @@ -13685,7 +13657,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.11.7: {} + baseline-browser-mapping@2.11.23: {} before-after-hook@4.0.0: {} @@ -13781,13 +13753,13 @@ snapshots: - '@aws-sdk/credential-provider-web-identity' - supports-color - browserslist@4.28.7: + browserslist@4.28.9: dependencies: - baseline-browser-mapping: 2.11.7 - caniuse-lite: 1.0.30001806 - electron-to-chromium: 1.5.398 - node-releases: 2.0.51 - update-browserslist-db: 1.2.3(browserslist@4.28.7) + baseline-browser-mapping: 2.11.23 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.427 + node-releases: 2.0.55 + update-browserslist-db: 1.3.3(browserslist@4.28.9) bson@7.3.1: {} @@ -13826,7 +13798,9 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - caniuse-lite@1.0.30001806: {} + caniuse-lite@1.0.30001810: {} + + capnweb@0.12.0: {} capnweb@0.8.0: {} @@ -13895,12 +13869,6 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - cliui@9.0.1: - dependencies: - string-width: 7.2.0 - strip-ansi: 7.2.0 - wrap-ansi: 9.0.2 - clsx@2.1.1: {} cluster-key-slot@1.1.2: {} @@ -13951,7 +13919,7 @@ snapshots: cookie@2.0.1: {} - core-js-pure@3.49.0: {} + core-js-pure@3.50.0: {} cors@2.8.6: dependencies: @@ -14290,9 +14258,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.398: {} - - emoji-regex@10.6.0: {} + electron-to-chromium@1.5.427: {} emoji-regex@8.0.0: {} @@ -14470,11 +14436,11 @@ snapshots: expect-type@1.4.0: {} - express-rate-limit@8.6.1(express@5.2.1): + express-rate-limit@8.7.0(express@5.2.1): dependencies: debug: 4.4.3 express: 5.2.1 - ip-address: 10.3.1 + ip-address: 10.7.0 transitivePeerDependencies: - supports-color @@ -14658,8 +14624,6 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.6.0: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -14985,7 +14949,7 @@ snapshots: internmap@2.0.3: {} - ip-address@10.3.1: {} + ip-address@10.7.0: {} ipaddr.js@1.9.1: {} @@ -15949,7 +15913,7 @@ snapshots: node-mock-http@1.0.5: {} - node-releases@2.0.51: {} + node-releases@2.0.55: {} normalize-path@3.0.0: {} @@ -16131,11 +16095,6 @@ snapshots: partial-json@0.1.7: {} - partyserver@0.5.9(@cloudflare/workers-types@5.20260731.1): - dependencies: - '@cloudflare/workers-types': 5.20260731.1 - nanoid: 5.1.16 - partysocket@1.3.0(react@19.2.8): dependencies: event-target-polyfill: 0.0.4 @@ -16950,17 +16909,6 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@7.2.0: - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - - string-width@8.2.2: - dependencies: - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -16974,10 +16922,6 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - strip-json-comments@2.0.1: optional: true @@ -17314,9 +17258,9 @@ snapshots: optionalDependencies: aws4fetch: 1.0.20 - update-browserslist-db@1.2.3(browserslist@4.28.7): + update-browserslist-db@1.3.3(browserslist@4.28.9): dependencies: - browserslist: 4.28.7 + browserslist: 4.28.9 escalade: 3.2.0 picocolors: 1.1.1 @@ -17355,6 +17299,8 @@ snapshots: verkit@0.3.1: {} + verkit@0.3.2: {} + vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -17491,12 +17437,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@9.0.2: - dependencies: - ansi-styles: 6.2.3 - string-width: 7.2.0 - strip-ansi: 7.2.0 - wrappy@1.0.2: {} ws@8.21.0: {} @@ -17531,15 +17471,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yargs@18.1.0: - dependencies: - cliui: 9.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - string-width: 8.2.2 - y18n: 5.0.8 - yargs-parser: 22.0.0 - yocto-queue@1.2.2: {} youch-core@0.3.3: