From cdd05ccd9c21d1640bcf48338f1bbb6e0d1d5e57 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Thu, 6 Aug 2026 19:35:41 +0100 Subject: [PATCH 1/2] feat(eve): bridge evlog context into OpenTelemetry spans --- .changeset/eve-instrumentation-bridge.md | 19 +++++ .github/pull_request_template.md | 1 + .github/workflows/semantic-pull-request.yml | 1 + packages/evlog/src/eve/index.ts | 85 +++++++++++++++++++ packages/evlog/test/eve.test.ts | 78 +++++++++++++++++ .../__snapshots__/api-surface.test.ts.snap | 1 + 6 files changed, 185 insertions(+) create mode 100644 .changeset/eve-instrumentation-bridge.md diff --git a/.changeset/eve-instrumentation-bridge.md b/.changeset/eve-instrumentation-bridge.md new file mode 100644 index 00000000..55bd835d --- /dev/null +++ b/.changeset/eve-instrumentation-bridge.md @@ -0,0 +1,19 @@ +--- +"evlog": minor +--- + +Add `defineEvlogInstrumentation()` to `evlog/eve`, linking eve's OpenTelemetry spans to evlog wide events. + +A wide event and an Agent Runs span describe the same turn, but nothing joined them: you could not jump from a trace in Braintrust, Datadog or the Vercel dashboard to the event in your drain. Export it as the default export of `agent/instrumentation.ts` and every model-call span — and its children — carries `evlog.request_id` and `evlog.session_id`, the same values the wide event reports. + +```ts +// agent/instrumentation.ts +import { defineEvlogInstrumentation } from 'evlog/eve' +import { registerOTel } from '@vercel/otel' + +export default defineEvlogInstrumentation({ + setup: ({ agentName }) => registerOTel({ serviceName: agentName }), +}) +``` + +`setup` is optional: without it, OpenTelemetry export is untouched and eve keeps recording its local traces, with only the runtime context added. `functionId`, `recordInputs`, `recordOutputs` and `traceChannelRequests` pass straight through to eve. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a783e1eb..d73edc04 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -37,6 +37,7 @@ scope. - dx (developer experience improvements) - elysia (Elysia plugin) - eve (eve agent integration) +- eve-extension (@evlog/eve, the installable eve extension) - evi (Evi agent) - express (Express middleware) - fastify (Fastify plugin) diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml index a2e31bfc..b1e9c49b 100644 --- a/.github/workflows/semantic-pull-request.yml +++ b/.github/workflows/semantic-pull-request.yml @@ -46,6 +46,7 @@ jobs: dx elysia eve + eve-extension evi express fastify diff --git a/packages/evlog/src/eve/index.ts b/packages/evlog/src/eve/index.ts index 8f5754aa..6ed224d1 100644 --- a/packages/evlog/src/eve/index.ts +++ b/packages/evlog/src/eve/index.ts @@ -1,5 +1,12 @@ import { AsyncLocalStorage } from 'node:async_hooks' import { defineHook, type HookContext, type HookDefinition } from 'eve/hooks' +import { + defineInstrumentation, + type InstrumentationDefinition, + type InstrumentationSetupContext, + type InstrumentationStepStartedEventInput, + type InstrumentationStepStartedEventResult, +} from 'eve/instrumentation' import type { AuditableLogger } from '../audit' import type { AIToolExecution, AIEventData, ModelCost } from '../ai/index' import { initLogger, isLoggerInitialized, isLoggerLocked } from '../logger' @@ -1333,6 +1340,84 @@ export function defineEvlogHook(options: EvlogEveOptions = {}): HookDefinition { }) } +/** Options for {@link defineEvlogInstrumentation}. */ +export interface EvlogEveInstrumentationOptions { + /** Overrides `ai.telemetry.functionId` on spans. Defaults to the agent name. */ + functionId?: string + /** Whether the AI SDK records full model inputs on spans. eve defaults to `true`. */ + recordInputs?: boolean + /** Whether the AI SDK records model outputs on spans. eve defaults to `true`. */ + recordOutputs?: boolean + /** Whether eve emits the inbound HTTP `SERVER` span wrapping each channel request. */ + traceChannelRequests?: boolean + /** + * Runs at server startup with the resolved agent name — register your OTel + * provider here, exactly as you would in a hand-written + * `defineInstrumentation`. Omit it and eve keeps writing its local traces. + */ + setup?: (context: InstrumentationSetupContext) => void +} + +/** + * Per-model-call context linking an AI SDK span back to the evlog wide event + * for the same turn. Returns `undefined` outside a tracked turn, which + * contributes no context rather than a half-filled one. + */ +function buildInstrumentationContext( + input: InstrumentationStepStartedEventInput, +): InstrumentationStepStartedEventResult | undefined { + const state = getTurnState(input.session.id, input.turn.id) + if (!state) return undefined + + return { + runtimeContext: { + 'evlog.request_id': state.turnId, + 'evlog.session_id': state.sessionId, + }, + } +} + +/** + * Create an eve instrumentation definition that stamps evlog's turn identity + * onto the AI SDK telemetry spans. + * + * Export the result as the default export of `agent/instrumentation.ts`. Every + * model-call span — and its children — then carries `evlog.request_id`, the + * same value the wide event reports as `requestId`, so a trace in Braintrust, + * Datadog or Agent Runs joins to the wide event in your drain, and back. + * + * `eve.` is reserved for framework-owned context, so evlog writes under + * `evlog.`. Passing no `setup` leaves OpenTelemetry export untouched: eve keeps + * recording its local traces and only the runtime context is added. + * + * @example + * ```ts + * // agent/instrumentation.ts + * import { defineEvlogInstrumentation } from 'evlog/eve' + * import { registerOTel } from '@vercel/otel' + * + * export default defineEvlogInstrumentation({ + * setup: ({ agentName }) => registerOTel({ serviceName: agentName }), + * }) + * ``` + */ +export function defineEvlogInstrumentation( + options: EvlogEveInstrumentationOptions = {}, +): InstrumentationDefinition { + return defineInstrumentation({ + ...(options.functionId !== undefined ? { functionId: options.functionId } : {}), + ...(options.recordInputs !== undefined ? { recordInputs: options.recordInputs } : {}), + ...(options.recordOutputs !== undefined ? { recordOutputs: options.recordOutputs } : {}), + ...(options.traceChannelRequests !== undefined + ? { traceChannelRequests: options.traceChannelRequests } + : {}), + ...(options.setup !== undefined ? { setup: options.setup } : {}), + events: { + 'step.started': buildInstrumentationContext, + }, + }) +} + /** @internal Simulates eve tool execution where AsyncLocalStorage did not propagate. */ export function detachActiveTurnLoggerForTests(): void { const logger = turnLoggerStorage.getStore() diff --git a/packages/evlog/test/eve.test.ts b/packages/evlog/test/eve.test.ts index da7c4114..6d61a36a 100644 --- a/packages/evlog/test/eve.test.ts +++ b/packages/evlog/test/eve.test.ts @@ -4,6 +4,7 @@ import { initLogger } from '../src/logger' import { resetEvlogEveForTests, defineEvlogHook, + defineEvlogInstrumentation, useLogger, detachActiveTurnLoggerForTests, } from '../src/eve/index' @@ -1549,3 +1550,80 @@ describe('evlog/eve', () => { initSpy.mockRestore() }) }) + +describe('defineEvlogInstrumentation', () => { + beforeEach(() => { + resetEvlogEveForTests() + initLogger({ env: { service: 'eve-test' } }) + }) + + afterEach(() => { + resetEvlogEveForTests() + }) + + function stepStartedInput(overrides: { sessionId?: string, turnId?: string } = {}) { + return { + channel: { kind: 'http' }, + modelInput: { instructions: undefined, messages: [] }, + session: { id: overrides.sessionId ?? SESSION_ID, auth: { current: null, initiator: null } }, + step: { index: 0 }, + turn: { id: overrides.turnId ?? TURN_ID, sequence: 0 }, + } as Parameters['events']>['step.started']>>[0] + } + + it('links the model-call span to the wide event of the active turn', () => { + const hook = defineEvlogHook({}) + const instrumentation = defineEvlogInstrumentation() + const ctx = hookContext() + + hook.events!['turn.started']!({ + type: 'turn.started', + data: { sequence: 0, turnId: TURN_ID }, + }, ctx) + + expect(instrumentation.events!['step.started']!(stepStartedInput())).toEqual({ + runtimeContext: { + 'evlog.request_id': TURN_ID, + 'evlog.session_id': SESSION_ID, + }, + }) + }) + + it('contributes no context outside a tracked turn', () => { + const instrumentation = defineEvlogInstrumentation() + + expect(instrumentation.events!['step.started']!(stepStartedInput())).toBeUndefined() + }) + + it('does not throw when no hook is registered', () => { + const instrumentation = defineEvlogInstrumentation() + + expect(() => instrumentation.events!['step.started']!(stepStartedInput())).not.toThrow() + }) + + it('passes capture settings and setup through to eve', () => { + const setup = vi.fn() + const instrumentation = defineEvlogInstrumentation({ + functionId: 'support-agent', + recordInputs: false, + recordOutputs: false, + traceChannelRequests: true, + setup, + }) + + expect(instrumentation).toMatchObject({ + functionId: 'support-agent', + recordInputs: false, + recordOutputs: false, + traceChannelRequests: true, + }) + instrumentation.setup!({ agentName: 'support-agent' }) + expect(setup).toHaveBeenCalledWith({ agentName: 'support-agent' }) + }) + + it('omits capture settings that were not configured', () => { + const instrumentation = defineEvlogInstrumentation() + + expect(Object.keys(instrumentation)).toEqual(['events']) + }) +}) diff --git a/packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap b/packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap index 46fed6cd..344b476d 100644 --- a/packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap +++ b/packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap @@ -115,6 +115,7 @@ exports[`public API surface > matches snapshot for all subpath exports 1`] = ` ], "./eve": [ "defineEvlogHook", + "defineEvlogInstrumentation", "detachActiveTurnLoggerForTests", "resetEvlogEveForTests", "useLogger", From cfcc20d23ab7651c2c8addf4e120586f64b0cc1d Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Thu, 6 Aug 2026 19:48:24 +0100 Subject: [PATCH 2/2] feat(eve-extension): package evlog as an installable eve extension --- .changeset/eve-extension-package.md | 21 + packages/eve-extension/.gitignore | 9 + packages/eve-extension/AGENTS.md | 35 ++ packages/eve-extension/README.md | 85 ++++ packages/eve-extension/extension/extension.ts | 62 +++ .../extension/hooks/wide-events.ts | 14 + .../eve-extension/extension/instructions.md | 5 + .../eve-extension/extension/lib/adapters.ts | 84 ++++ .../eve-extension/extension/lib/options.ts | 63 +++ .../extension/skills/observability/SKILL.md | 44 ++ .../eve-extension/extension/tools/annotate.ts | 32 ++ packages/eve-extension/package.json | 68 +++ packages/eve-extension/test/adapters.test.ts | 41 ++ packages/eve-extension/test/options.test.ts | 123 ++++++ packages/eve-extension/tsconfig.json | 13 + packages/eve-extension/vitest.config.ts | 7 + packages/evlog/src/eve/index.ts | 12 + packages/evlog/test/eve.test.ts | 12 + pnpm-lock.yaml | 388 +++++++----------- 19 files changed, 872 insertions(+), 246 deletions(-) create mode 100644 .changeset/eve-extension-package.md create mode 100644 packages/eve-extension/.gitignore create mode 100644 packages/eve-extension/AGENTS.md create mode 100644 packages/eve-extension/README.md create mode 100644 packages/eve-extension/extension/extension.ts create mode 100644 packages/eve-extension/extension/hooks/wide-events.ts create mode 100644 packages/eve-extension/extension/instructions.md create mode 100644 packages/eve-extension/extension/lib/adapters.ts create mode 100644 packages/eve-extension/extension/lib/options.ts create mode 100644 packages/eve-extension/extension/skills/observability/SKILL.md create mode 100644 packages/eve-extension/extension/tools/annotate.ts create mode 100644 packages/eve-extension/package.json create mode 100644 packages/eve-extension/test/adapters.test.ts create mode 100644 packages/eve-extension/test/options.test.ts create mode 100644 packages/eve-extension/tsconfig.json create mode 100644 packages/eve-extension/vitest.config.ts diff --git a/.changeset/eve-extension-package.md b/.changeset/eve-extension-package.md new file mode 100644 index 00000000..90772e4b --- /dev/null +++ b/.changeset/eve-extension-package.md @@ -0,0 +1,21 @@ +--- +"@evlog/eve": minor +"evlog": patch +--- + +Add `@evlog/eve`, evlog packaged as an installable eve extension. + +Mounting it replaces writing `agent/hooks/evlog.ts` by hand: + +```ts +// agent/extensions/evlog.ts +import evlog from '@evlog/eve' + +export default evlog({ adapter: 'axiom', sample: 0.1 }) +``` + +The mount config is declarative — an adapter by name (or several, which fan out), a sampling rate, redaction paths, batching — because eve validates extension config synchronously and it therefore cannot carry functions. Agents that need a custom `drain`, `enrich` or `keep` keep using `defineEvlogHook` from `evlog/eve`. + +The extension also contributes what a hook cannot: an `annotate` tool and a skill that teach the agent to record its own business context on the turn's event, and to keep message content and personal data out of it. + +`defineEvlogHook` now warns when it runs more than once in a process. Every hook accumulates into the same turn, so mounting the extension alongside a hand-written hook records token and step counts once per hook. diff --git a/packages/eve-extension/.gitignore b/packages/eve-extension/.gitignore new file mode 100644 index 00000000..fff71c68 --- /dev/null +++ b/packages/eve-extension/.gitignore @@ -0,0 +1,9 @@ +node_modules +.env* +.eve +.vercel +.output +.nitro +dist +.DS_Store +*.tsbuildinfo diff --git a/packages/eve-extension/AGENTS.md b/packages/eve-extension/AGENTS.md new file mode 100644 index 00000000..bef02972 --- /dev/null +++ b/packages/eve-extension/AGENTS.md @@ -0,0 +1,35 @@ +# @evlog/eve + +evlog packaged as an eve extension. Consumers mount it under +`agent/extensions/` instead of writing `agent/hooks/evlog.ts` by hand. + +## This package does not build like the others + +It builds with `eve extension build`, not tsdown, and the output tree +(`dist/extension`) is agent-shaped rather than a bundle. `eve extension build` +requires **Node >= 24**. + +`prepare` and `dev:prepare` are deliberately absent. The release script already +runs `turbo run build --filter='./packages/*'` before `changeset publish`, so +the dist exists when it matters. Wiring the build into `dev:prepare` would put +it on the critical path of `lint`, `typecheck` and `test`, which then fail for +anyone on Node 22 — nothing in this repo consumes this package's dist. + +## Config cannot carry functions + +The mount config is a Standard Schema, validated synchronously, so `drain`, +`enrich` and `keep` cannot cross the mount boundary. Everything consumers +configure has to be declarative — an adapter name, a sampling rate, redaction +paths. Agents that genuinely need a callback use `defineEvlogHook` from +`evlog/eve` directly; keep that escape hatch documented. + +## Layout + +- `extension/extension.ts` — the config schema, and the handle contributions read +- `extension/lib/` — shared code: adapter resolution, config → hook options +- `extension/hooks/` — the wide-event hook +- `extension/tools/` — `annotate`, mounted as `__annotate` +- `extension/skills/` — procedures taught to the host agent + +Tool, skill and connection names come from file paths and the consumer's mount +adds the namespace prefix, so name the file `annotate`, never `evlog_annotate`. diff --git a/packages/eve-extension/README.md b/packages/eve-extension/README.md new file mode 100644 index 00000000..0ebf1730 --- /dev/null +++ b/packages/eve-extension/README.md @@ -0,0 +1,85 @@ +# @evlog/eve + +[evlog](https://evlog.dev) packaged as an [eve](https://eve.dev) extension: one +wide event per agent turn, sent to the destination of your choice, without +writing a hook. + +## Install + +```bash +pnpm add @evlog/eve +``` + +```ts +// agent/extensions/evlog.ts +import evlog from '@evlog/eve' + +export default evlog({ adapter: 'axiom', sample: 0.1 }) +``` + +That is the whole setup. Every turn now produces an event carrying token usage, +cost, tool executions, subagent delegations, approvals, connection +authorizations, compactions and failures. + +## Config + +| Option | Default | What it does | +| --- | --- | --- | +| `adapter` | `'fs'` | Destination by name, or `{ type, options }`. An array fans out. | +| `service` | agent name | Service name on every event. | +| `message` | `'omit'` | `'omit'`, `'preview'` or `'full'` user message capture. | +| `messagePreviewLength` | `500` | Characters kept in `'preview'`. | +| `redact` | `true` | Built-in PII patterns, or `{ paths }`. | +| `sample` | keep all | Fraction of routine turns to keep, 0 to 1. | +| `keepOnFailure` | `true` | Always keep failed, rejected and declined turns. | +| `sessionEvent` | `false` | One extra event per session, rolling up its turns. | +| `batch` | none | `{ size, intervalMs }` buffering before draining. | + +Adapters read their own environment variables, so `adapter: 'axiom'` with +`AXIOM_TOKEN` and `AXIOM_DATASET` set is usually all you need. `options` +overrides what the environment cannot express. + +Available adapters: `axiom`, `better-stack`, `clickhouse`, `datadog`, `fs`, +`hyperdx`, `loki`, `memory`, `otlp`, `posthog`, `sentry`. + +## What the agent can do itself + +The mount contributes an `annotate` tool (namespaced `evlog__annotate` for a +mount named `evlog.ts`) and a skill teaching the agent when to use it. The agent +records business facts on its own turn: + +``` +annotate({ key: "refund", value: { amount: 89.9, reason: "damaged_on_arrival" } }) +``` + +Those fields are what make a turn findable later. The skill also tells the agent +what never to record: message content, credentials, personal data. + +## When to use the hook instead + +The mount config is validated synchronously, so it cannot carry functions. If +you need a custom `drain`, `enrich` or `keep`, skip the extension and use the +hook directly: + +```ts +// agent/hooks/evlog.ts +import { defineEvlogHook } from 'evlog/eve' + +export default defineEvlogHook({ + drain: myDrain, + enrich: ctx => void (ctx.event.region = process.env.VERCEL_REGION), +}) +``` + +Use one or the other. Mounting both makes every hook accumulate into the same +turn, which records token and step counts once per hook. + +## Correlating with OpenTelemetry + +`evlog/eve` also exports `defineEvlogInstrumentation()`, which stamps +`evlog.request_id` onto eve's AI SDK spans so a trace joins back to the wide +event. See the [eve integration docs](https://evlog.dev/use-cases/eve). + +## License + +MIT diff --git a/packages/eve-extension/extension/extension.ts b/packages/eve-extension/extension/extension.ts new file mode 100644 index 00000000..1e9966db --- /dev/null +++ b/packages/eve-extension/extension/extension.ts @@ -0,0 +1,62 @@ +import { defineExtension } from 'eve/extension' +import { z } from 'zod' + +/** Drain adapters the extension can resolve by name. */ +export const ADAPTER_NAMES = [ + 'axiom', + 'better-stack', + 'clickhouse', + 'datadog', + 'fs', + 'hyperdx', + 'loki', + 'memory', + 'otlp', + 'posthog', + 'sentry', +] as const + +const adapter = z.union([ + z.enum(ADAPTER_NAMES), + z.object({ + type: z.enum(ADAPTER_NAMES), + /** Passed straight to the adapter factory, overriding its env defaults. */ + options: z.record(z.string(), z.unknown()).optional(), + }), +]) + +/** + * Consumer-facing settings. Everything is declarative: a Standard Schema is + * validated synchronously at the mount site, so it cannot carry functions. + * Agents needing a custom `drain`, `enrich` or `keep` use `defineEvlogHook` + * from `evlog/eve` in `agent/hooks/` instead. + */ +export default defineExtension({ + config: z.object({ + /** Service name on every event. Defaults to the agent name. */ + service: z.string().optional(), + /** Where events go. Several adapters fan out. */ + adapter: z.union([adapter, z.array(adapter)]).default('fs'), + /** How much of the user message to record. */ + message: z.enum(['omit', 'preview', 'full']).default('omit'), + /** Max characters kept in `preview` mode. */ + messagePreviewLength: z.number().int().positive().optional(), + /** PII auto-redaction: `true` for the built-in patterns, or explicit paths. */ + redact: z + .union([z.boolean(), z.object({ paths: z.array(z.string()) })]) + .default(true), + /** Fraction of routine turns to keep, between 0 and 1. Unset keeps all. */ + sample: z.number().min(0).max(1).optional(), + /** Always keep turns that failed, were rejected, or lost an authorization. */ + keepOnFailure: z.boolean().default(true), + /** Emit one extra wide event per session, rolling up its turns. */ + sessionEvent: z.boolean().default(false), + /** Batch events before draining them. */ + batch: z + .object({ + size: z.number().int().positive(), + intervalMs: z.number().int().positive(), + }) + .optional(), + }), +}) diff --git a/packages/eve-extension/extension/hooks/wide-events.ts b/packages/eve-extension/extension/hooks/wide-events.ts new file mode 100644 index 00000000..5c94a105 --- /dev/null +++ b/packages/eve-extension/extension/hooks/wide-events.ts @@ -0,0 +1,14 @@ +import { defineEvlogHook } from 'evlog/eve' +import extension from '../extension' +import { toHookOptions, type ExtensionConfig } from '../lib/options' + +/** + * One evlog wide event per agent turn, wired from the mount config. + * + * The agent name is not available to a hook file at module scope, so the + * service falls back to the mount's `service` and is otherwise resolved from + * the turn context by evlog itself. + */ +export default defineEvlogHook( + toHookOptions(extension.config as ExtensionConfig, extension.config.service ?? 'eve-agent'), +) diff --git a/packages/eve-extension/extension/instructions.md b/packages/eve-extension/extension/instructions.md new file mode 100644 index 00000000..acd46be9 --- /dev/null +++ b/packages/eve-extension/extension/instructions.md @@ -0,0 +1,5 @@ +Every turn you run is recorded as one observability event. + +Call the `annotate` tool when you learn a business fact that would make this turn +findable later — an identifier you looked up, a decision you made, an amount you +moved. Do not pass user message content, credentials, or personal data. diff --git a/packages/eve-extension/extension/lib/adapters.ts b/packages/eve-extension/extension/lib/adapters.ts new file mode 100644 index 00000000..81e38bbe --- /dev/null +++ b/packages/eve-extension/extension/lib/adapters.ts @@ -0,0 +1,84 @@ +import type { DrainContext } from 'evlog' +import { createAxiomDrain } from 'evlog/axiom' +import { createBetterStackDrain } from 'evlog/better-stack' +import { createClickHouseDrain } from 'evlog/clickhouse' +import { createDatadogDrain } from 'evlog/datadog' +import { createFsDrain } from 'evlog/fs' +import { createHyperDXDrain } from 'evlog/hyperdx' +import { createLokiDrain } from 'evlog/loki' +import { createMemoryDrain } from 'evlog/memory' +import { createOTLPDrain } from 'evlog/otlp' +import { createPostHogDrain } from 'evlog/posthog' +import { createSentryDrain } from 'evlog/sentry' +import { createDrainPipeline } from 'evlog/pipeline' +import type { ADAPTER_NAMES } from '../extension' + +type AdapterName = (typeof ADAPTER_NAMES)[number] +/** Adapters accept a single context or a batch, which is what lets them sit behind a pipeline. */ +type AdapterDrain = (ctx: DrainContext | DrainContext[]) => void | Promise +/** What `BaseEvlogOptions.drain` expects: one event at a time. */ +type HookDrain = (ctx: DrainContext) => void | Promise +type DrainFactory = (overrides?: Record) => AdapterDrain + +/** + * Every adapter factory reads its own env vars, so a bare name is enough for + * the common case and `options` only overrides what the environment cannot. + */ +const FACTORIES: Record = { + 'axiom': createAxiomDrain as DrainFactory, + 'better-stack': createBetterStackDrain as DrainFactory, + 'clickhouse': createClickHouseDrain as DrainFactory, + 'datadog': createDatadogDrain as DrainFactory, + 'fs': createFsDrain as DrainFactory, + 'hyperdx': createHyperDXDrain as DrainFactory, + 'loki': createLokiDrain as DrainFactory, + 'memory': createMemoryDrain as DrainFactory, + 'otlp': createOTLPDrain as DrainFactory, + 'posthog': createPostHogDrain as DrainFactory, + 'sentry': createSentryDrain as DrainFactory, +} + +export type AdapterConfig = + | AdapterName + | { type: AdapterName, options?: Record } + +export interface ResolveDrainOptions { + adapter: AdapterConfig | AdapterConfig[] + batch?: { size: number, intervalMs: number } +} + +function createDrain(config: AdapterConfig): AdapterDrain { + const { type, options } = typeof config === 'string' ? { type: config, options: undefined } : config + return FACTORIES[type](options) +} + +/** Each destination runs independently so a failing one cannot starve the others. */ +async function fanOut( + drains: AdapterDrain[], + payload: DrainContext | DrainContext[], +): Promise { + await Promise.all(drains.map(async (send) => { + try { + await send(payload) + } catch (err) { + console.error('[evlog] eve extension drain failed:', err) + } + })) +} + +/** + * Build the single drain the hook receives. With `batch`, the adapters sit + * behind a pipeline that buffers events and hands them over as an array — + * which every evlog adapter accepts. + */ +export function resolveDrain(options: ResolveDrainOptions): HookDrain { + const configs = Array.isArray(options.adapter) ? options.adapter : [options.adapter] + const drains = configs.map(createDrain) + + if (options.batch) { + return createDrainPipeline({ batch: options.batch })( + (batch: DrainContext[]) => fanOut(drains, batch), + ) + } + return (ctx: DrainContext) => fanOut(drains, ctx) +} diff --git a/packages/eve-extension/extension/lib/options.ts b/packages/eve-extension/extension/lib/options.ts new file mode 100644 index 00000000..6e960ed1 --- /dev/null +++ b/packages/eve-extension/extension/lib/options.ts @@ -0,0 +1,63 @@ +import type { EvlogEveOptions } from 'evlog/eve' +import type { TailSamplingContext } from 'evlog' +import { resolveDrain, type AdapterConfig } from './adapters' + +export interface ExtensionConfig { + service?: string + adapter: AdapterConfig | AdapterConfig[] + message: 'omit' | 'preview' | 'full' + messagePreviewLength?: number + redact: boolean | { paths: string[] } + sample?: number + keepOnFailure: boolean + sessionEvent: boolean + batch?: { size: number, intervalMs: number } +} + +/** Turn outcomes worth keeping whatever the sampling rate says. */ +function isNotableTurn(ctx: TailSamplingContext): boolean { + const status = ctx.status ?? 200 + if (status >= 400 && status !== 499) return true + + const eve = ctx.context.eve as { + phase?: string + authorizations?: Array<{ outcome?: string }> + stepFailures?: unknown[] + } | undefined + if (!eve) return false + + if (eve.phase === 'rejected' || eve.phase === 'failed') return true + if (eve.stepFailures?.length) return true + return eve.authorizations?.some(a => a.outcome && a.outcome !== 'authorized') ?? false +} + +/** + * Translate the declarative mount config into the options `defineEvlogHook` + * takes. `sample` is head sampling — the only place a turn can actually be + * dropped — while `keepOnFailure` is tail sampling, which can force a turn back + * in but never drop one. + */ +export function toHookOptions(config: ExtensionConfig, agentName: string): EvlogEveOptions { + const sampleRate = config.sample === undefined ? undefined : Math.round(config.sample * 100) + + return { + init: { + env: { service: config.service ?? agentName }, + ...(sampleRate !== undefined ? { sampling: { rates: { info: sampleRate } } } : {}), + }, + drain: resolveDrain({ adapter: config.adapter, batch: config.batch }), + redact: typeof config.redact === 'boolean' ? config.redact : { paths: config.redact.paths }, + message: config.message, + ...(config.messagePreviewLength !== undefined + ? { messagePreviewLength: config.messagePreviewLength } + : {}), + sessionEvent: config.sessionEvent, + ...(config.keepOnFailure + ? { + keep: (ctx: TailSamplingContext) => { + if (isNotableTurn(ctx)) ctx.shouldKeep = true + }, + } + : {}), + } +} diff --git a/packages/eve-extension/extension/skills/observability/SKILL.md b/packages/eve-extension/extension/skills/observability/SKILL.md new file mode 100644 index 00000000..bfa040c0 --- /dev/null +++ b/packages/eve-extension/extension/skills/observability/SKILL.md @@ -0,0 +1,44 @@ +--- +name: observability +description: Record business context on the current turn's observability event. Use when the agent has looked up an entity, made a decision, or moved an amount, and that fact should be queryable later. +--- + +# Annotating a turn + +Each turn produces one wide event: an entry carrying token usage, tool calls, +durations and outcome. What it cannot know is your business context — which +customer, which order, which decision. That is what `annotate` adds. + +## When to annotate + +Annotate once you hold a fact that someone would later filter on: + +- an entity you resolved — `customer`, `order`, `invoice`, `ticket` +- a decision you took and why — `refund` with its amount and reason +- an outcome that is not an error but matters — a fallback you used, a limit + you hit + +Annotate as soon as you know the fact. A turn that fails after the annotation +still carries it, which is exactly when it is most useful. + +## When not to annotate + +- **Never** user message content, credentials, tokens, or personal data such as + emails, phone numbers, or addresses. The event leaves the agent. +- Not tool inputs and outputs, token counts, durations, or errors — those are + already recorded automatically. +- Not free-form prose. One `annotate` call with a flat object beats three calls + with a sentence each. + +## Shape + +`key` is a lowercase field name; `value` is a flat object of strings, numbers or +booleans. + +``` +annotate({ key: "order", value: { id: "4821", total: 89.9, expedited: true } }) +annotate({ key: "refund", value: { amount: 89.9, reason: "damaged_on_arrival" } }) +``` + +Prefer one key per domain entity. Re-annotating the same key replaces its +previous value, so record the final state rather than each intermediate step. diff --git a/packages/eve-extension/extension/tools/annotate.ts b/packages/eve-extension/extension/tools/annotate.ts new file mode 100644 index 00000000..c745dac2 --- /dev/null +++ b/packages/eve-extension/extension/tools/annotate.ts @@ -0,0 +1,32 @@ +import { defineTool } from 'eve/tools' +import { never } from 'eve/tools/approval' +import { useLogger } from 'evlog/eve' +import { z } from 'zod' + +/** + * Lets the agent write business context onto the wide event for the turn it is + * running in — the order it looked up, the decision it made, the amount it + * refunded. Those fields are what turn a trace into something you can query. + * + * Approval is `never()`: the write is in-process and has no external effect. + */ +export default defineTool({ + description: + 'Record a business fact about the current turn on its observability event. ' + + 'Use it for identifiers, decisions and amounts that would make this turn ' + + 'findable later. Never record user message content or personal data.', + inputSchema: z.object({ + key: z + .string() + .regex(/^[a-z][a-z0-9_]*$/, 'lowercase identifier, e.g. order or customer') + .describe('Field name to set on the event, e.g. "order".'), + value: z + .record(z.string(), z.union([z.string(), z.number(), z.boolean()])) + .describe('Flat object of facts, e.g. { "id": "4821", "amount": 89 }.'), + }), + approval: never(), + execute({ key, value }, ctx) { + useLogger(ctx).set({ [key]: value }) + return { recorded: key } + }, +}) diff --git a/packages/eve-extension/package.json b/packages/eve-extension/package.json new file mode 100644 index 00000000..b8bf64f9 --- /dev/null +++ b/packages/eve-extension/package.json @@ -0,0 +1,68 @@ +{ + "name": "@evlog/eve", + "version": "0.1.0", + "description": "evlog as an installable eve extension — wide events, drains, and agent-authored annotations for eve agents", + "author": "HugoRCD ", + "homepage": "https://evlog.dev", + "repository": { + "type": "git", + "url": "git+https://github.com/HugoRCD/evlog.git", + "directory": "packages/eve-extension" + }, + "bugs": { + "url": "https://github.com/HugoRCD/evlog/issues" + }, + "keywords": [ + "evlog", + "eve", + "eve-extension", + "agent", + "observability", + "wide-events" + ], + "license": "MIT", + "type": "module", + "eve": { + "extension": { + "source": "./extension", + "dist": "./dist/extension" + } + }, + "files": [ + "dist" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.mjs" + }, + "./tools": { + "types": "./dist/tools/index.d.ts", + "default": "./dist/tools/index.mjs" + } + }, + "scripts": { + "build": "eve extension build", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "test": "vitest run", + "test:watch": "vitest watch", + "typecheck": "tsc" + }, + "dependencies": { + "evlog": "workspace:*", + "zod": "^4.4.3" + }, + "devDependencies": { + "@types/node": "^24.13.3", + "eve": "^0.30.8", + "typescript": "^5.9.0", + "vitest": "^4.1.10" + }, + "peerDependencies": { + "eve": "*" + }, + "engines": { + "node": ">=24" + } +} diff --git a/packages/eve-extension/test/adapters.test.ts b/packages/eve-extension/test/adapters.test.ts new file mode 100644 index 00000000..1ad4b844 --- /dev/null +++ b/packages/eve-extension/test/adapters.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it, vi } from 'vitest' +import type { DrainContext } from 'evlog' +import { ADAPTER_NAMES } from '../extension/extension' +import { resolveDrain } from '../extension/lib/adapters' + +function drainContext(): DrainContext { + return { + event: { level: 'info', timestamp: new Date().toISOString(), message: 'turn' }, + request: { method: 'EVE', path: '/sessions/sess_1/turns/turn_0' }, + } as DrainContext +} + +describe('resolveDrain', () => { + it('resolves every advertised adapter name', () => { + for (const name of ADAPTER_NAMES) { + expect(typeof resolveDrain({ adapter: name })).toBe('function') + } + }) + + it('accepts the object form with adapter options', () => { + expect(typeof resolveDrain({ adapter: { type: 'memory', options: { limit: 10 } } })).toBe('function') + }) + + it('fans out to every configured adapter', async () => { + const drain = resolveDrain({ adapter: ['memory', 'memory'] }) + const ctx = drainContext() + + await expect(drain(ctx)).resolves.not.toThrow() + }) + + it('keeps draining the other adapters when one fails', async () => { + const error = vi.spyOn(console, 'error').mockImplementation(() => {}) + const drain = resolveDrain({ + adapter: [{ type: 'otlp', options: { endpoint: 'http://127.0.0.1:1' } }, 'memory'], + }) + + await drain(drainContext()) + + error.mockRestore() + }) +}) diff --git a/packages/eve-extension/test/options.test.ts b/packages/eve-extension/test/options.test.ts new file mode 100644 index 00000000..9cea672a --- /dev/null +++ b/packages/eve-extension/test/options.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, it } from 'vitest' +import type { TailSamplingContext } from 'evlog' +import extension from '../extension/extension' +import { toHookOptions, type ExtensionConfig } from '../extension/lib/options' + +function parseConfig(input: Record = {}): ExtensionConfig { + const result = extension.schema['~standard'].validate(input) + if (result instanceof Promise) throw new Error('config schema must validate synchronously') + if (result.issues) throw new Error(JSON.stringify(result.issues)) + return result.value as ExtensionConfig +} + +function tailContext(overrides: Partial = {}): TailSamplingContext { + return { + status: 200, + duration: 10, + path: '/sessions/sess_1/turns/turn_0', + method: 'EVE', + context: {}, + shouldKeep: false, + ...overrides, + } +} + +describe('extension config', () => { + it('validates synchronously, as eve requires', () => { + expect(() => parseConfig()).not.toThrow() + }) + + it('applies defaults so a bare mount is usable', () => { + expect(parseConfig()).toMatchObject({ + adapter: 'fs', + message: 'omit', + redact: true, + keepOnFailure: true, + sessionEvent: false, + }) + }) + + it('rejects an unknown adapter', () => { + expect(() => parseConfig({ adapter: 'splunk' })).toThrow() + }) + + it('rejects a sample rate outside 0..1', () => { + expect(() => parseConfig({ sample: 1.5 })).toThrow() + }) +}) + +describe('toHookOptions', () => { + it('names the service after the agent when the mount does not', () => { + const options = toHookOptions(parseConfig(), 'support-agent') + + expect(options.init?.env?.service).toBe('support-agent') + }) + + it('prefers the configured service over the agent name', () => { + const options = toHookOptions(parseConfig({ service: 'billing' }), 'support-agent') + + expect(options.init?.env?.service).toBe('billing') + }) + + it('maps the 0..1 sample rate onto evlog head sampling percentages', () => { + const options = toHookOptions(parseConfig({ sample: 0.1 }), 'agent') + + expect(options.init?.sampling?.rates).toEqual({ info: 10 }) + }) + + it('configures no sampling when the mount sets no rate', () => { + const options = toHookOptions(parseConfig(), 'agent') + + expect(options.init?.sampling).toBeUndefined() + }) + + it('passes redact paths through', () => { + const options = toHookOptions(parseConfig({ redact: { paths: ['order.card'] } }), 'agent') + + expect(options.redact).toEqual({ paths: ['order.card'] }) + }) + + it('installs no keep callback when keepOnFailure is off', () => { + const options = toHookOptions(parseConfig({ keepOnFailure: false }), 'agent') + + expect(options.keep).toBeUndefined() + }) +}) + +describe('keepOnFailure', () => { + function keepFor(ctx: TailSamplingContext): boolean { + const { keep } = toHookOptions(parseConfig(), 'agent') + keep!(ctx) + return ctx.shouldKeep + } + + it('keeps a failed turn', () => { + expect(keepFor(tailContext({ status: 500 }))).toBe(true) + }) + + it('keeps a rejected turn even though it succeeded', () => { + expect(keepFor(tailContext({ context: { eve: { phase: 'rejected' } } }))).toBe(true) + }) + + it('keeps a turn whose authorization was declined', () => { + expect(keepFor(tailContext({ + context: { eve: { authorizations: [{ outcome: 'declined' }] } }, + }))).toBe(true) + }) + + it('keeps a turn that retried a failed model step', () => { + expect(keepFor(tailContext({ + context: { eve: { stepFailures: [{ code: 'RATE_LIMIT' }] } }, + }))).toBe(true) + }) + + it('leaves a cancelled turn to the sampling rate', () => { + expect(keepFor(tailContext({ status: 499 }))).toBe(false) + }) + + it('leaves a routine turn to the sampling rate', () => { + expect(keepFor(tailContext({ + context: { eve: { authorizations: [{ outcome: 'authorized' }] } }, + }))).toBe(false) + }) +}) diff --git a/packages/eve-extension/tsconfig.json b/packages/eve-extension/tsconfig.json new file mode 100644 index 00000000..ba1682b7 --- /dev/null +++ b/packages/eve-extension/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["extension/**/*.ts"] +} diff --git a/packages/eve-extension/vitest.config.ts b/packages/eve-extension/vitest.config.ts new file mode 100644 index 00000000..3e427974 --- /dev/null +++ b/packages/eve-extension/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['test/**/*.test.ts'], + }, +}) diff --git a/packages/evlog/src/eve/index.ts b/packages/evlog/src/eve/index.ts index 6ed224d1..bc368b80 100644 --- a/packages/evlog/src/eve/index.ts +++ b/packages/evlog/src/eve/index.ts @@ -430,6 +430,7 @@ interface EveGlobalState { sessionAuthorizationStarts: Map> maxSessions: number initialized: boolean + hookCount: number } const EVE_GLOBAL_STATE = Symbol.for('evlog.eve.state') @@ -451,6 +452,7 @@ function getEveGlobalState(): EveGlobalState { sessionAuthorizationStarts: new Map(), maxSessions: DEFAULT_MAX_SESSIONS, initialized: false, + hookCount: 0, } } return host[EVE_GLOBAL_STATE] @@ -972,6 +974,16 @@ export function defineEvlogHook(options: EvlogEveOptions = {}): HookDefinition { const messageMode = resolveMessageMode(options) const previewLength = options.messagePreviewLength ?? DEFAULT_MESSAGE_PREVIEW_LENGTH + const state = getEveGlobalState() + state.hookCount += 1 + if (state.hookCount > 1) { + console.warn( + '[evlog] defineEvlogHook() ran more than once in this process. Every hook ' + + 'accumulates into the same turn, so token and step counts are recorded ' + + 'once per hook. Mount @evlog/eve or write agent/hooks/evlog.ts, not both.', + ) + } + return defineHook({ events: { 'session.started'(event, ctx) { diff --git a/packages/evlog/test/eve.test.ts b/packages/evlog/test/eve.test.ts index 6d61a36a..bb444c93 100644 --- a/packages/evlog/test/eve.test.ts +++ b/packages/evlog/test/eve.test.ts @@ -900,6 +900,18 @@ describe('evlog/eve', () => { expect(findEventViaDrain(spies.drain, e => e.path === `/sessions/${SESSION_ID}`)).toBeUndefined() }) + it('warns when a second hook would double-count the same turn', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + + defineEvlogHook({}) + expect(warn).not.toHaveBeenCalled() + + defineEvlogHook({}) + expect(warn).toHaveBeenCalledWith(expect.stringContaining('more than once')) + + warn.mockRestore() + }) + it('does not throw when an internal handler fails', async () => { const hook = defineEvlogHook({ enrich: () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ed8ce20..57b4c44a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -226,7 +226,7 @@ importers: version: link:../../packages/evlog next: specifier: ^16.2.10 - version: 16.2.10(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 16.2.10(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: specifier: ^19.2.7 version: 19.2.7 @@ -353,7 +353,7 @@ importers: version: 2.3.1(magic-string@1.0.0)(magicast@0.5.3)(oxc-parser@0.140.0)(rolldown@1.2.3)(unplugin@3.3.0(esbuild@0.28.0)(rolldown@1.2.3)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))(vue@3.5.40(typescript@6.0.3)) '@nuxt/ui': specifier: ^4.10.0 - version: 4.10.0(49c80827ab65d931c15e1cb70fe2764b) + version: 4.10.0(b26e47f06ceb7c4353abfdfe3720a9c9) '@nuxthub/core': specifier: ^0.10.8 version: 0.10.8(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(db0@0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)))(ioredis@5.10.1)(magicast@0.5.3)(typescript@6.0.3)(vue-tsc@3.2.7(typescript@6.0.3)) @@ -905,6 +905,28 @@ importers: specifier: ^4.1.10 version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.6)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)) + packages/eve-extension: + dependencies: + evlog: + specifier: workspace:* + version: link:../evlog + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@types/node': + specifier: ^24.13.3 + version: 24.13.3 + eve: + specifier: ^0.30.8 + version: 0.30.8(@opentelemetry/api@1.9.1)(ai@7.0.51(zod@4.4.3))(dotenv@17.4.2)(giget@3.3.0)(jiti@2.7.0)(vite@8.1.4(@types/node@24.13.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)) + typescript: + specifier: ^5.9.0 + version: 5.9.3 + vitest: + specifier: ^4.1.10 + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.6)(vite@8.1.4(@types/node@24.13.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)) + packages/evlog: dependencies: '@nuxt/kit': @@ -21974,7 +21996,7 @@ snapshots: semver: 7.8.5 tinyglobby: 0.2.17 ufo: 1.6.4 - unctx: 3.0.0(magic-string@0.30.21)(oxc-parser@0.140.0)(rolldown@1.2.3) + unctx: 3.0.0(magic-string@0.30.21)(oxc-parser@0.140.0)(rolldown@1.2.3)(unplugin@3.3.0(esbuild@0.28.0)(rolldown@1.2.3)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))) untyped: 2.0.0 transitivePeerDependencies: - magic-string @@ -23436,134 +23458,6 @@ snapshots: - vue - webpack - '@nuxt/ui@4.10.0(49c80827ab65d931c15e1cb70fe2764b)': - dependencies: - '@floating-ui/dom': 1.8.0 - '@iconify/vue': 5.0.1(vue@3.5.40(typescript@6.0.3)) - '@nuxt/fonts': 0.14.0(db0@0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)))(ioredis@5.10.1)(magic-string@0.30.21)(magicast@0.5.3)(oxc-parser@0.140.0)(rolldown@1.2.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)) - '@nuxt/icon': 2.3.1(magic-string@0.30.21)(magicast@0.5.3)(oxc-parser@0.140.0)(rolldown@1.2.3)(unplugin@3.3.0(esbuild@0.28.0)(rolldown@1.2.3)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))(vue@3.5.40(typescript@6.0.3)) - '@nuxt/kit': 4.5.0(magic-string@0.30.21)(magicast@0.5.3)(oxc-parser@0.140.0)(rolldown@1.2.3)(unplugin@3.3.0(esbuild@0.28.0)(rolldown@1.2.3)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))) - '@nuxt/schema': 4.5.0 - '@nuxtjs/color-mode': 4.0.1(magic-string@0.30.21)(magicast@0.5.3)(oxc-parser@0.140.0)(rolldown@1.2.3)(unplugin@3.3.0(esbuild@0.28.0)(rolldown@1.2.3)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))) - '@standard-schema/spec': 1.1.0 - '@tailwindcss/postcss': 4.3.2 - '@tailwindcss/vite': 4.3.2(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)) - '@tanstack/vue-table': 8.21.3(vue@3.5.40(typescript@6.0.3)) - '@tanstack/vue-virtual': 3.13.32(vue@3.5.40(typescript@6.0.3)) - '@tiptap/core': 3.22.5(@tiptap/pm@3.22.5) - '@tiptap/extension-bubble-menu': 3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5) - '@tiptap/extension-code': 3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5)) - '@tiptap/extension-collaboration': 3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(@tiptap/y-tiptap@3.0.3(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.8)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30) - '@tiptap/extension-drag-handle': 3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/extension-collaboration@3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(@tiptap/y-tiptap@3.0.3(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.8)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(@tiptap/y-tiptap@3.0.3(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.8)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30)) - '@tiptap/extension-drag-handle-vue-3': 3.22.5(@tiptap/extension-drag-handle@3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/extension-collaboration@3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(@tiptap/y-tiptap@3.0.3(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.8)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(@tiptap/y-tiptap@3.0.3(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.8)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30)))(@tiptap/pm@3.22.5)(@tiptap/vue-3@3.22.5(@floating-ui/dom@1.8.0)(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(vue@3.5.40(typescript@6.0.3)))(vue@3.5.40(typescript@6.0.3)) - '@tiptap/extension-floating-menu': 3.22.5(@floating-ui/dom@1.8.0)(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5) - '@tiptap/extension-horizontal-rule': 3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5) - '@tiptap/extension-image': 3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5)) - '@tiptap/extension-mention': 3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(@tiptap/suggestion@3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)) - '@tiptap/extension-node-range': 3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5) - '@tiptap/extension-placeholder': 3.22.5(@tiptap/extensions@3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)) - '@tiptap/markdown': 3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5) - '@tiptap/pm': 3.22.5 - '@tiptap/starter-kit': 3.22.5 - '@tiptap/suggestion': 3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5) - '@tiptap/vue-3': 3.22.5(@floating-ui/dom@1.8.0)(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(vue@3.5.40(typescript@6.0.3)) - '@unhead/vue': 3.2.1(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(crossws@0.4.10(srvx@0.12.4))(esbuild@0.28.0)(lightningcss@1.32.0)(rolldown@1.2.3)(rollup@4.60.2)(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))(vue@3.5.40(typescript@6.0.3)) - '@vueuse/core': 14.3.0(vue@3.5.40(typescript@6.0.3)) - '@vueuse/integrations': 14.3.0(axios@1.16.0)(fuse.js@7.5.0)(vue@3.5.40(typescript@6.0.3)) - '@vueuse/shared': 14.3.0(vue@3.5.40(typescript@6.0.3)) - colortranslator: 5.0.0 - consola: 3.4.2 - defu: 6.1.7 - embla-carousel-auto-height: 8.6.0(embla-carousel@8.6.0) - embla-carousel-auto-scroll: 8.6.0(embla-carousel@8.6.0) - embla-carousel-autoplay: 8.6.0(embla-carousel@8.6.0) - embla-carousel-class-names: 8.6.0(embla-carousel@8.6.0) - embla-carousel-fade: 8.6.0(embla-carousel@8.6.0) - embla-carousel-vue: 8.6.0(vue@3.5.40(typescript@6.0.3)) - embla-carousel-wheel-gestures: 8.1.0(embla-carousel@8.6.0) - fuse.js: 7.5.0 - hookable: 6.1.1 - knitwork: 1.3.0 - magic-string: 0.30.21 - mlly: 1.8.2 - motion-v: 2.3.0(@vueuse/core@14.3.0(vue@3.5.40(typescript@6.0.3)))(vue@3.5.40(typescript@6.0.3)) - ohash: 2.0.11 - pathe: 2.0.3 - reka-ui: 2.10.1(vue@3.5.40(typescript@6.0.3)) - scule: 1.3.0 - tailwind-merge: 3.6.0 - tailwind-variants: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.2) - tailwindcss: 4.3.2 - tinyglobby: 0.2.17 - typescript: 6.0.3 - ufo: 1.6.4 - unplugin: 3.3.0(esbuild@0.28.0)(rolldown@1.2.3)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)) - unplugin-auto-import: 21.0.0(@nuxt/kit@4.5.0(magic-string@0.30.21)(magicast@0.5.3)(oxc-parser@0.140.0)(rolldown@1.2.3)(unplugin@3.3.0(esbuild@0.28.0)(rolldown@1.2.3)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))))(@vueuse/core@14.3.0(vue@3.5.40(typescript@6.0.3))) - unplugin-vue-components: 32.1.0(@nuxt/kit@4.5.0(magic-string@0.30.21)(magicast@0.5.3)(oxc-parser@0.140.0)(rolldown@1.2.3)(unplugin@3.3.0(esbuild@0.28.0)(rolldown@1.2.3)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))))(esbuild@0.28.0)(rolldown@1.2.3)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))(vue@3.5.40(typescript@6.0.3)) - vaul-vue: 0.4.1(reka-ui@2.10.1(vue@3.5.40(typescript@6.0.3)))(vue@3.5.40(typescript@6.0.3)) - vue-component-type-helpers: 3.3.7 - optionalDependencies: - '@internationalized/date': 3.12.1 - '@internationalized/number': 3.6.6 - '@nuxt/content': 3.15.0(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@6.0.3)))(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9))(esbuild@0.28.0)(magic-string@1.0.0)(magicast@0.5.3)(oxc-parser@0.140.0)(rolldown@1.2.3)(rollup@4.60.2)(valibot@1.4.2(typescript@6.0.3))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)) - ai: 7.0.51(zod@4.4.3) - valibot: 1.4.2(typescript@6.0.3) - vue-router: 5.2.0(@vue/compiler-sfc@3.5.40)(esbuild@0.28.0)(rolldown@1.2.3)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))(vue@3.5.40(typescript@6.0.3)) - zod: 4.4.3 - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@deno/kv' - - '@emotion/is-prop-valid' - - '@farmfe/core' - - '@modelcontextprotocol/sdk' - - '@netlify/blobs' - - '@planetscale/database' - - '@rspack/core' - - '@unhead/cli' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - '@vue/composition-api' - - async-validator - - aws4fetch - - axios - - bufferutil - - bun-types-no-globals - - change-case - - crossws - - db0 - - drauu - - embla-carousel - - esbuild - - focus-trap - - idb-keyval - - ioredis - - jwt-decode - - lightningcss - - magicast - - nprogress - - oxc-parser - - qrcode - - react - - react-dom - - rolldown - - rollup - - sortablejs - - universal-cookie - - unloader - - uploadthing - - utf-8-validate - - vite - - vue - - webpack - '@nuxt/ui@4.10.0(8e8b8fa5f2e2e2649ce2bbcae51ee801)': dependencies: '@floating-ui/dom': 1.8.0 @@ -29556,12 +29450,12 @@ snapshots: '@types/better-sqlite3@7.6.13': dependencies: - '@types/node': 22.19.17 + '@types/node': 24.13.3 '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 22.19.17 + '@types/node': 24.13.3 '@types/braces@3.0.5': {} @@ -29576,7 +29470,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 22.19.17 + '@types/node': 24.13.3 '@types/cookie@0.6.0': {} @@ -29727,7 +29621,7 @@ snapshots: '@types/express-serve-static-core@5.1.1': dependencies: - '@types/node': 22.19.17 + '@types/node': 24.13.3 '@types/qs': 6.15.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -29825,18 +29719,18 @@ snapshots: '@types/send@1.2.1': dependencies: - '@types/node': 22.19.17 + '@types/node': 24.13.3 '@types/serve-static@2.2.0': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 22.19.17 + '@types/node': 24.13.3 '@types/superagent@8.1.9': dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 22.19.17 + '@types/node': 24.13.3 form-data: 4.0.5 '@types/supercluster@5.0.3': @@ -29895,7 +29789,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 22.19.17 + '@types/node': 24.13.3 '@typescript-eslint/eslint-plugin@8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': dependencies: @@ -31032,7 +30926,6 @@ snapshots: magic-string: 0.30.21 optionalDependencies: vite: 8.1.4(@types/node@24.13.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0) - optional: true '@vitest/mocker@4.1.10(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0))': dependencies: @@ -32072,7 +31965,7 @@ snapshots: buffer-image-size@0.6.4: dependencies: - '@types/node': 22.19.17 + '@types/node': 24.13.3 buffer@5.7.1: dependencies: @@ -32086,7 +31979,7 @@ snapshots: bun-types@1.3.14: dependencies: - '@types/node': 22.19.17 + '@types/node': 24.13.3 bundle-name@4.1.0: dependencies: @@ -32267,7 +32160,7 @@ snapshots: chrome-launcher@1.2.1: dependencies: - '@types/node': 22.19.17 + '@types/node': 24.13.3 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 2.0.2 @@ -32530,8 +32423,6 @@ snapshots: dependencies: uncrypto: 0.1.3 - crossws@0.4.10: {} - crossws@0.4.10(srvx@0.11.15): optionalDependencies: srvx: 0.11.15 @@ -34146,6 +34037,53 @@ snapshots: - xml2js - zephyr-agent + eve@0.30.8(@opentelemetry/api@1.9.1)(ai@7.0.51(zod@4.4.3))(dotenv@17.4.2)(giget@3.3.0)(jiti@2.7.0)(vite@8.1.4(@types/node@24.13.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)): + dependencies: + ai: 7.0.51(zod@4.4.3) + nitro: 3.0.260610-beta(dotenv@17.4.2)(giget@3.3.0)(jiti@2.7.0)(vite@8.1.4(@types/node@24.13.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)) + undici: 8.9.0 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@electric-sql/pglite' + - '@libsql/client' + - '@netlify/blobs' + - '@netlify/runtime' + - '@planetscale/database' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - '@vercel/queue' + - aws4fetch + - better-sqlite3 + - chokidar + - dotenv + - drizzle-orm + - giget + - idb-keyval + - ioredis + - jiti + - lru-cache + - miniflare + - mongodb + - mysql2 + - rollup + - sqlite3 + - uploadthing + - vite + - wrangler + - xml2js + - zephyr-agent + event-target-shim@5.0.1: {} eventemitter3@4.0.7: {} @@ -34623,12 +34561,6 @@ snapshots: fraction.js@5.3.4: {} - framer-motion@12.41.0: - dependencies: - motion-dom: 12.41.0 - motion-utils: 12.39.0 - tslib: 2.8.1 - framer-motion@12.41.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: motion-dom: 12.41.0 @@ -34917,7 +34849,7 @@ snapshots: happy-dom@20.10.6: dependencies: - '@types/node': 22.19.17 + '@types/node': 24.13.3 '@types/whatwg-mimetype': 3.0.2 '@types/ws': 8.18.1 buffer-image-size: 0.6.4 @@ -35753,29 +35685,6 @@ snapshots: linkifyjs@4.3.2: {} - listhen@1.10.0: - dependencies: - '@parcel/watcher': 2.5.6 - '@parcel/watcher-wasm': 2.5.6 - citty: 0.2.2 - consola: 3.4.2 - crossws: 0.4.10 - defu: 6.1.7 - get-port-please: 3.2.0 - h3: 1.15.11 - http-shutdown: 1.2.2 - jiti: 2.7.0 - mlly: 1.8.2 - node-forge: 1.4.0 - pathe: 2.0.3 - std-env: 4.2.0 - tinyclip: 0.1.15 - ufo: 1.6.4 - untun: 0.1.3 - uqr: 0.1.3 - transitivePeerDependencies: - - srvx - listhen@1.10.0(srvx@0.11.15): dependencies: '@parcel/watcher': 2.5.6 @@ -36740,19 +36649,6 @@ snapshots: - react - react-dom - motion-v@2.3.0(@vueuse/core@14.3.0(vue@3.5.40(typescript@6.0.3)))(vue@3.5.40(typescript@6.0.3)): - dependencies: - '@vueuse/core': 14.3.0(vue@3.5.40(typescript@6.0.3)) - framer-motion: 12.41.0 - hey-listen: 1.0.8 - motion-dom: 12.41.0 - motion-utils: 12.39.0 - vue: 3.5.40(typescript@6.0.3) - transitivePeerDependencies: - - '@emotion/is-prop-valid' - - react - - react-dom - motion@12.40.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: framer-motion: 12.41.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -36851,31 +36747,6 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@16.2.10(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@next/env': 16.2.10 - '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.11.0 - caniuse-lite: 1.0.30001806 - postcss: 8.4.31 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - styled-jsx: 5.1.6(react@19.2.7) - optionalDependencies: - '@next/swc-darwin-arm64': 16.2.10 - '@next/swc-darwin-x64': 16.2.10 - '@next/swc-linux-arm64-gnu': 16.2.10 - '@next/swc-linux-arm64-musl': 16.2.10 - '@next/swc-linux-x64-gnu': 16.2.10 - '@next/swc-linux-x64-musl': 16.2.10 - '@next/swc-win32-arm64-msvc': 16.2.10 - '@next/swc-win32-x64-msvc': 16.2.10 - '@opentelemetry/api': 1.9.1 - sharp: 0.34.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - next@16.2.4(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: '@next/env': 16.2.4 @@ -37162,7 +37033,7 @@ snapshots: rolldown: 1.2.3 srvx: 0.11.16 unenv: 2.0.0-rc.24 - unstorage: 2.0.0-alpha.7(db0@0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)))(ioredis@5.10.1)(ofetch@2.0.0-alpha.3) + unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)))(ioredis@5.10.1)(lru-cache@11.3.5)(ofetch@2.0.0-alpha.3) optionalDependencies: dotenv: 17.4.2 giget: 3.2.0 @@ -37216,7 +37087,60 @@ snapshots: rolldown: 1.2.3 srvx: 0.11.16 unenv: 2.0.0-rc.24 - unstorage: 2.0.0-alpha.7(db0@0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)))(ofetch@2.0.0-alpha.3) + unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)))(ioredis@5.10.1)(lru-cache@11.3.5)(ofetch@2.0.0-alpha.3) + optionalDependencies: + dotenv: 17.4.2 + giget: 3.3.0 + jiti: 2.7.0 + vite: 8.1.4(@types/node@24.13.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@electric-sql/pglite' + - '@libsql/client' + - '@netlify/blobs' + - '@netlify/runtime' + - '@planetscale/database' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - better-sqlite3 + - chokidar + - drizzle-orm + - idb-keyval + - ioredis + - lru-cache + - miniflare + - mongodb + - mysql2 + - sqlite3 + - uploadthing + - wrangler + + nitro@3.0.260610-beta(dotenv@17.4.2)(giget@3.3.0)(jiti@2.7.0)(vite@8.1.4(@types/node@24.13.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)): + dependencies: + consola: 3.4.2 + crossws: 0.4.6(srvx@0.11.16) + db0: 0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)) + env-runner: 0.1.14 + h3: 2.0.1-rc.22(crossws@0.4.6(srvx@0.11.16)) + hookable: 6.1.1 + nf3: 0.3.17 + ocache: 0.1.5 + ofetch: 2.0.0-alpha.3 + ohash: 2.0.11 + rolldown: 1.2.3 + srvx: 0.11.16 + unenv: 2.0.0-rc.24 + unstorage: 2.0.0-alpha.7(db0@0.3.4)(ofetch@2.0.0-alpha.3) optionalDependencies: dotenv: 17.4.2 giget: 3.3.0 @@ -37959,7 +37883,7 @@ snapshots: jiti: 2.7.0 klona: 2.0.6 knitwork: 1.3.0 - listhen: 1.10.0 + listhen: 1.10.0(srvx@0.12.4) magic-string: 0.30.21 magicast: 0.5.3 mime: 4.1.0 @@ -37974,7 +37898,7 @@ snapshots: pretty-bytes: 7.1.0 radix3: 1.1.2 rollup: 4.60.2 - rollup-plugin-visualizer: 7.0.1(rollup@4.60.2) + rollup-plugin-visualizer: 7.0.1(rolldown@1.2.3)(rollup@4.60.2) scule: 1.3.0 semver: 7.8.5 serve-placeholder: 2.0.2 @@ -41458,15 +41382,6 @@ snapshots: rolldown: 1.2.3 rollup: 4.60.2 - rollup-plugin-visualizer@7.0.1(rollup@4.60.2): - dependencies: - open: 11.0.0 - picomatch: 4.0.5 - source-map: 0.7.6 - yargs: 18.0.0 - optionalDependencies: - rollup: 4.60.2 - rollup-pluginutils@2.8.2: dependencies: estree-walker: 0.6.1 @@ -42137,11 +42052,6 @@ snapshots: client-only: 0.0.1 react: 19.2.5 - styled-jsx@5.1.6(react@19.2.7): - dependencies: - client-only: 0.0.1 - react: 19.2.7 - stylehacks@7.0.11(postcss@8.5.19): dependencies: browserslist: 4.28.7 @@ -42706,12 +42616,6 @@ snapshots: rolldown: 1.2.0 unplugin: 3.3.0(esbuild@0.28.0)(rolldown@1.2.0)(rollup@4.60.2)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)) - unctx@3.0.0(magic-string@0.30.21)(oxc-parser@0.140.0)(rolldown@1.2.3): - optionalDependencies: - magic-string: 0.30.21 - oxc-parser: 0.140.0 - rolldown: 1.2.3 - unctx@3.0.0(magic-string@0.30.21)(oxc-parser@0.140.0)(rolldown@1.2.3)(unplugin@3.0.0): optionalDependencies: magic-string: 0.30.21 @@ -43442,13 +43346,7 @@ snapshots: lru-cache: 11.3.5 ofetch: 2.0.0-alpha.3 - unstorage@2.0.0-alpha.7(db0@0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)))(ioredis@5.10.1)(ofetch@2.0.0-alpha.3): - optionalDependencies: - db0: 0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)) - ioredis: 5.10.1 - ofetch: 2.0.0-alpha.3 - - unstorage@2.0.0-alpha.7(db0@0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)))(ofetch@2.0.0-alpha.3): + unstorage@2.0.0-alpha.7(db0@0.3.4)(ofetch@2.0.0-alpha.3): optionalDependencies: db0: 0.3.4(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260503.1)(@electric-sql/pglite@0.5.4)(@libsql/client@0.17.4)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.28.17)(postgres@3.4.9)) ofetch: 2.0.0-alpha.3 @@ -44131,7 +44029,6 @@ snapshots: terser: 5.46.2 tsx: 4.23.1 yaml: 2.9.0 - optional: true vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0): dependencies: @@ -44250,7 +44147,6 @@ snapshots: happy-dom: 20.10.6 transitivePeerDependencies: - msw - optional: true vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.6)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.2)(tsx@4.23.1)(yaml@2.9.0)): dependencies: