From 68731891c48b8fb5a6595b94ccaa2c2521015601 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Fri, 22 May 2026 18:00:59 -0600 Subject: [PATCH 1/4] feat(0.16.0): yank durable substrate, add AgentExecutionHandle contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Long-running execution durability lives in the substrate (sandbox SDK + orchestrator already buffer the stream by executionId and replay strictly after lastEventId). The Worker-hosted SessionSupervisorDO, runSupervisedTurn, runDurableTurn, runDurable, runOnWorkflowStep, DurableRunStore (+ D1 / FS / in-memory impls), schema, and supervisor adapter all competed with primitives that already exist below — and adoption proved it: SessionSupervisorDO had zero callers, runDurable / runOnWorkflowStep had zero callers, runSupervisedTurn had one (gtm-agent). Replaced with two surfaces: - AgentExecutionHandle + deriveExecutionId — the typed pointer products persist so a client retry lands on the same substrate execution. - ChatTurnEngine / chatTurnEngine — the framework-neutral chat-turn lifecycle (NDJSON, session.run.* envelope, persist / post-process / trace-flush hook ordering). No longer hosts execution state. Build: 179 tests pass (was 251 — store-matrix tests removed). pnpm-lock drops 110 packages (workerd test pool, better-sqlite3, wrangler). Consumers (tax / legal / creative / gtm) bump on a separate PR; they only use DurableChatTurnEngine.runTurn(...) and D1DurableRunStore — both adopted shallow, easy migration. --- README.md | 111 +- docs/concepts.md | 93 +- examples/README.md | 4 +- examples/chat-handler/README.md | 34 +- examples/chat-handler/chat-handler.ts | 61 +- examples/durable-supervisor/README.md | 22 - .../durable-supervisor/durable-supervisor.ts | 96 - package.json | 14 +- pnpm-lock.yaml | 2034 +---------------- src/durable/chat-engine.ts | 205 +- src/durable/d1-store.ts | 531 ----- src/durable/execution-handle.ts | 80 + src/durable/file-system-store.ts | 426 ---- src/durable/identity.ts | 103 - src/durable/in-memory-store.ts | 330 --- src/durable/index.ts | 97 +- src/durable/runner.ts | 357 --- src/durable/schema.sql | 96 - src/durable/schema.ts | 116 - src/durable/session-supervisor-do.ts | 178 -- src/durable/supervisor.ts | 229 -- src/durable/tests/chat-engine.test.ts | 369 ++- src/durable/tests/durable-runs.test.ts | 331 --- src/durable/tests/durable-turn.test.ts | 302 --- src/durable/tests/execution-handle.test.ts | 29 + src/durable/tests/schema-sync.test.ts | 24 - .../tests/session-supervisor-do.test.ts | 161 -- src/durable/tests/sqlite-d1-adapter.ts | 69 - src/durable/tests/supervisor.test.ts | 247 -- src/durable/turn.ts | 170 -- src/durable/types.ts | 310 --- src/durable/workflows.ts | 162 -- tests/workers/entry.ts | 89 - .../session-supervisor-do.workers.test.ts | 31 - vitest.config.ts | 6 +- vitest.workers.config.ts | 15 - wrangler.workers.toml | 17 - 37 files changed, 565 insertions(+), 6984 deletions(-) delete mode 100644 examples/durable-supervisor/README.md delete mode 100644 examples/durable-supervisor/durable-supervisor.ts delete mode 100644 src/durable/d1-store.ts create mode 100644 src/durable/execution-handle.ts delete mode 100644 src/durable/file-system-store.ts delete mode 100644 src/durable/identity.ts delete mode 100644 src/durable/in-memory-store.ts delete mode 100644 src/durable/runner.ts delete mode 100644 src/durable/schema.sql delete mode 100644 src/durable/schema.ts delete mode 100644 src/durable/session-supervisor-do.ts delete mode 100644 src/durable/supervisor.ts delete mode 100644 src/durable/tests/durable-runs.test.ts delete mode 100644 src/durable/tests/durable-turn.test.ts create mode 100644 src/durable/tests/execution-handle.test.ts delete mode 100644 src/durable/tests/schema-sync.test.ts delete mode 100644 src/durable/tests/session-supervisor-do.test.ts delete mode 100644 src/durable/tests/sqlite-d1-adapter.ts delete mode 100644 src/durable/tests/supervisor.test.ts delete mode 100644 src/durable/turn.ts delete mode 100644 src/durable/types.ts delete mode 100644 src/durable/workflows.ts delete mode 100644 tests/workers/entry.ts delete mode 100644 tests/workers/session-supervisor-do.workers.test.ts delete mode 100644 vitest.workers.config.ts delete mode 100644 wrangler.workers.toml diff --git a/README.md b/README.md index 45cc0786..1e2b0795 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,9 @@ pnpm add @tangle-network/agent-runtime @tangle-network/agent-eval |---|---| | `runAgentTask` | Single-shot adapter-driven task with eval/verification | | `runAgentTaskStream` | Streaming product loop with session resume + backends | -| `runDurableTurn` | Checkpoint+replay chat turn — survives a worker crash *after* completion | -| `runSupervisedTurn` | Always-attached durable turn — re-attaches an in-flight sandbox run *during* a crash | -| `SessionSupervisorDO` | Cloudflare Durable Object host for `runSupervisedTurn` (with alarm-driven orphan re-attach) | -| `DurableChatTurnEngine` | Framework-neutral chat-turn orchestrator (durable turn + NDJSON + session lifecycle + product hooks) | +| `ChatTurnEngine` / `chatTurnEngine` | Framework-neutral chat-turn orchestrator (NDJSON + `session.run.*` envelope + product hooks) | +| `AgentExecutionHandle` + `deriveExecutionId` | Typed pointer to a substrate-owned execution — the contract for cross-process replay/reconnect | | `startRuntimeRun` | Canonical production-run row + cost ledger | -| `runDurable` + `*DurableRunStore` | General durable-step substrate (in-memory / file-system / D1) | | `defineAgent` | Declarative per-vertical agent manifest — surfaces, knowledge, rubric, run fn | | `resolveChatModel` / `validateChatModelId` / `getModels` | Router catalog fetch + fail-closed admission + precedence resolver | | `createTraceBridge` | Map `RuntimeStreamEvent` → `agent-eval` `TraceEvent` | @@ -53,64 +50,65 @@ const result = await runAgentTask({ console.log(result.status, result.runRecords) ``` -## Durable chat turns +## Chat turns -A 15-minute agentic turn must survive a Cloudflare worker isolate dying. -`runDurableTurn` replays a *completed* turn from cache (worker died after -the turn finished). `runSupervisedTurn` closes the harder gap — a turn -interrupted *mid-stream* — by relocating the durability boundary off the -ephemeral worker: - -- The supervisor drains every event into the substrate's own ordered log - (`appendStreamEvent`, idempotent on `eventId`). -- It persists the substrate `RunHandle` the instant the sandbox yields it. -- A fresh supervisor reads the log for its cursor and resumes via - `adapter.attach(handle, cursor)` — no event lost, none delivered twice. - -The reconnect glue is one typed contract — `SandboxReconnectAdapter` — -implemented once per substrate, not per product. +`ChatTurnEngine` wraps a product `produce()` hook with the +`session.run.*` lifecycle envelope, drains the producer stream through +the NDJSON line protocol, and calls the persist / post-process hooks +after drain. Framework-neutral: the engine takes already-resolved +values, never a `Request` or `Context`. ```ts -import { runSupervisedTurn, InMemoryDurableRunStore } from '@tangle-network/agent-runtime' +import { chatTurnEngine, deriveExecutionId } from '@tangle-network/agent-runtime' -const store = new InMemoryDurableRunStore() -const supervised = runSupervisedTurn({ - store, runId: `chat:${threadId}:${turnIndex}`, manifest, workerId, - adapter: mySandboxAdapter, +const executionId = deriveExecutionId({ + projectId: 'gtm-agent', + sessionId: threadId, + turnIndex, }) -for await (const event of supervised.stream) sendToClient(event) -// supervised.mode() === 'fresh' | 'resumed' | 'replayed' -``` -Full runnable: [`examples/durable-supervisor/`](./examples/durable-supervisor/). +const result = chatTurnEngine.runTurn({ + identity: { tenantId: workspaceId, sessionId: threadId, userId, turnIndex }, + hooks: { + produce: () => ({ + stream: box.streamPrompt(prompt, { executionId, lastEventId, ...sandboxOptions }), + finalText: () => assembled, + }), + persistAssistantMessage: async ({ identity, finalText }) => db.insert(messages).values(...), + onTurnComplete: async ({ identity, finalText }) => extractProposals(finalText), + traceFlush: () => traceSink.flush(), + }, + waitUntil: ctx.waitUntil, +}) +return new Response(result.body, { headers: { 'content-type': result.contentType } }) +``` -### Cloudflare Durable Object host +## Execution continuity -`SessionSupervisorDO` hosts the supervisor on a real DO — `fetch` streams the -turn, `alarm()` re-attaches a run a dropped response stream abandoned. +Long-running execution durability — reconnect, replay, dedup — lives in +the substrate. `@tangle-network/sandbox`'s `box.streamPrompt({ +executionId, lastEventId })` buffers the stream by `executionId`, +replays strictly after `lastEventId` on reconnect, and never spawns a +duplicate execution. agent-runtime owns the typed pointer: ```ts -import { createSessionSupervisorDO } from '@tangle-network/agent-runtime' +import { type AgentExecutionHandle, deriveExecutionId } from '@tangle-network/agent-runtime' -export const SessionSupervisor = createSessionSupervisorDO({ - resolveRun(request, env, state) { /* return RunSupervisorOptions */ }, - resolveOrphan(runId, env, state) { /* same, for the alarm path */ }, - encodeEvent(event) { return `data: ${JSON.stringify(event)}\n\n` }, -}) -``` - -```toml -# wrangler.toml -[[durable_objects.bindings]] -name = "SESSION_SUPERVISOR" -class_name = "SessionSupervisor" -[[migrations]] -tag = "v1" -new_classes = ["SessionSupervisor"] +const handle: AgentExecutionHandle = { + executionId: deriveExecutionId({ projectId, sessionId, turnIndex }), + sessionId, + // lastEventId set on retry from the client's last-seen id +} +for await (const event of box.streamPrompt(prompt, { + executionId: handle.executionId, + lastEventId: handle.lastEventId, +})) { ... } ``` -CF types are structural (`DurableObjectStateLike`) — no -`@cloudflare/workers-types` runtime dep. +The product persists `executionId` on the session row so a client retry +of the same turn lands on the same substrate execution — the +orchestrator's buffer replays the stream instead of starting a second +prompt. ## Chat-model resolution @@ -157,7 +155,7 @@ export const myAgent = defineAgent({ knowledge: { /* requirements + provider */ }, rubric: { /* dimensions + weights */ }, run: async (ctx) => { - /* product-specific run — typically wraps runSupervisedTurn or runAgentTaskStream */ + /* product-specific run — typically wraps chatTurnEngine.runTurn or runAgentTaskStream */ }, }) ``` @@ -213,9 +211,6 @@ for await (const event of runAgentTaskStream({ task, backend, input })) { | `BackendTransportError` | Backend HTTP / IPC call returned non-success | | `SessionMismatchError` | Resume requested against a different backend | | `RuntimeRunStateError` | `RuntimeRunHandle` lifecycle methods called out of order | -| `DurableRunLeaseHeldError` | Another worker holds a live lease on the run | -| `DurableRunInputMismatchError` | A `runId` exists with a different manifest hash | -| `DurableRunDivergenceError` | A step's intent changed across replays | All extend `AgentEvalError` (re-exported from `@tangle-network/agent-eval`) and carry a stable `code` so cross-package handlers pattern-match @@ -240,7 +235,7 @@ console.log(telemetry.events, telemetry.summary()) | Package | Owns | |---|---| -| `agent-runtime` | Lifecycle, adapters, backends, durable substrate, supervisor + DO, model resolution, trace bridge, `defineAgent` | +| `agent-runtime` | Task lifecycle, adapters, backends, chat-turn engine, execution-handle contract, model resolution, trace bridge, `defineAgent`. **Does not** own long-running execution state — that lives in `@tangle-network/sandbox` + orchestrator. | | `agent-runtime/platform` | Cross-site SSO (`PlatformAuthClient`) + integrations hub (`PlatformHubClient`) | | `agent-runtime/agent` | `defineAgent` + surfaces / outcome adapters | | `agent-runtime/analyst-loop` | `runAnalystLoop` — analyst registry driver | @@ -263,16 +258,14 @@ Runnable in [`examples/`](./examples/). Every example imports from - [`openai-stream-backend/`](./examples/openai-stream-backend/) — `createOpenAICompatibleBackend` - [`runtime-run/`](./examples/runtime-run/) — production-run row + cost ledger - [`model-resolution/`](./examples/model-resolution/) — router catalog + fail-closed admission -- [`durable-supervisor/`](./examples/durable-supervisor/) — cross-worker resume keystone - [`agent-into-reviewer/`](./examples/agent-into-reviewer/) — pipe one runtime's stream into a reviewer agent -- [`chat-handler/`](./examples/chat-handler/) — `DurableChatTurnEngine.runTurn` (the centerpiece production pattern) +- [`chat-handler/`](./examples/chat-handler/) — `chatTurnEngine.runTurn` + `deriveExecutionId` (the centerpiece production pattern) - [`production-trace-sink/`](./examples/production-trace-sink/) — `createProductionTraceSink` data capture ## Tests ```bash -pnpm test # full Node suite (251 tests) -pnpm test:workers # real workerd DO integration test +pnpm test pnpm typecheck pnpm lint pnpm build diff --git a/docs/concepts.md b/docs/concepts.md index 98577974..ec7f2b8d 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -21,8 +21,14 @@ rest. Read this file once and the rest of the API falls into place. └───────────────────────────────────────┬─────────────────┘ │ ┌───────────────────────────────────────┴─────────────────┐ - │ Durability ─ runDurableTurn / runSupervisedTurn │ - │ + DurableRunStore + stream-event log + RunHandle │ + │ Chat-turn engine ─ ChatTurnEngine.runTurn(...) │ + │ NDJSON + session.run.* envelope + persist/trace hooks │ + └───────────────────────────────────────┬─────────────────┘ + │ + ┌───────────────────────────────────────┴─────────────────┐ + │ Execution continuity (substrate-owned) │ + │ box.streamPrompt({ executionId, lastEventId }) │ + │ agent-runtime provides: AgentExecutionHandle + deriveExecutionId └───────────────────────────────────────┬─────────────────┘ │ ┌───────────────────────────────────────┴─────────────────┐ @@ -34,7 +40,7 @@ rest. Read this file once and the rest of the API falls into place. Each layer composes the one below it. You can use the bottom layers alone (a raw backend + the model catalog), or the whole stack -(`defineAgent` → `runSupervisedTurn`) — they're the same primitives +(`defineAgent` → `chatTurnEngine`) — they're the same primitives nested. ## The task lifecycle @@ -51,52 +57,55 @@ The adapter is *yours*. The lifecycle, the eval lift, the stop semantics, the cost ledger — all substrate. Streaming is the same shape: `runAgentTaskStream` yields `RuntimeStreamEvent`s as the loop progresses. -## Durability — three levels +## Execution continuity — substrate-owned -A turn that "completes" means the response reached the client AND the -side effects landed. A worker isolate can die anywhere in between. Pick -the level that matches your turn length and substrate: +Long-running execution durability — reconnect, replay, dedup — is the +substrate's job, not agent-runtime's. The `@tangle-network/sandbox` SDK ++ orchestrator already implements every primitive a 15-minute turn +needs: -| Level | Survives | When to reach for it | -|---|---|---| -| `runAgentTask` / `runAgentTaskStream` | nothing — a worker crash re-runs from the top | sub-second turns, no sandbox | -| `runDurableTurn` | a worker crash *after* the turn finished (cached replay) | medium turns, no sandbox-reconnect | -| `runSupervisedTurn` + `SessionSupervisorDO` | a worker crash *during* the turn — a fresh supervisor re-attaches to the in-flight sandbox run | long sandbox-backed turns | +- `box.streamPrompt(prompt, { executionId, lastEventId })` buffers the + event stream by `executionId` at the orchestrator. +- On reconnect with the same `executionId` and a known `lastEventId`, + the orchestrator replays strictly after that id without spawning a + duplicate execution. +- The SDK dedupes replayed text deltas and tool completions on the + client side; observers see exactly one of each. -`runSupervisedTurn` works because the sandbox container is -orchestrator-managed and **outlives** the worker. The supervisor: +agent-runtime owns the typed pointer products persist: + +```ts +interface AgentExecutionHandle { + executionId: string + sessionId?: string + lastEventId?: string +} -1. Drains every event into the substrate's own ordered log - (`appendStreamEvent`, idempotent on `eventId`). -2. Persists a `RunHandle` (`setRunHandle`) the moment the substrate - yields a run id. -3. Heartbeats the lease while attached. +deriveExecutionId({ projectId, sessionId, turnIndex }): string +``` -A fresh supervisor reads the log for its cursor and calls -`adapter.attach(handle, cursor)` to resume past it — events through -`cursor` are not re-delivered (the log's idempotency dedups the seam). +The product persists `executionId` on its session row so a client retry +of the same turn lands on the same substrate execution — the +orchestrator replays its buffer instead of starting a second prompt. +A retry with a stale `lastEventId` (or none) replays from the start of +the buffer. -## The reconnect adapter contract +What lives in the Worker: -`SandboxReconnectAdapter` is one typed interface. Implement it **once -per substrate** (the Tangle sandbox SDK, an OpenAI Assistants thread, -whatever), never per product. +- auth / access control +- product DB writes (the assistant message, run row, side effects) +- prompt / profile composition +- routing (which backend handles this turn) -```ts -interface SandboxReconnectAdapter { - start(): AsyncIterable> - attach(handle: RunHandle, afterEventId: string | undefined): - AsyncIterable> -} -``` +What lives in the substrate: -`SupervisedEvent` carries an `eventId` (cursor + dedup key), a `payload` -(your event type), and an optional `handle` (carried on the first frame -once the substrate yields the run id). +- the long-running execution +- event buffering keyed by `executionId` +- replay-on-reconnect +- dedup across the reconnect seam -Conformance assertions live in `src/durable/tests/supervisor.test.ts` — -copy them into your adapter's tests so substrate quirks surface there, -not in a 15-minute production turn. +The Worker stays a routing + persistence layer. It does not host +execution state. ## The agent manifest @@ -150,8 +159,8 @@ agents because nothing in this list is baked into it. 1. `examples/basic-task/` — the smallest end-to-end. 2. `examples/sandbox-stream-backend/` — what streaming looks like. -3. `examples/runtime-run/` — the production-run row + cost ledger. -4. `examples/model-resolution/` — pick + validate a model. -5. `examples/durable-supervisor/` — the cross-worker resume keystone. +3. `examples/chat-handler/` — `chatTurnEngine` + `deriveExecutionId` — the centerpiece chat handler. +4. `examples/runtime-run/` — the production-run row + cost ledger. +5. `examples/model-resolution/` — pick + validate a model. 6. `examples/agent-into-reviewer/` — pipe one runtime's stream into a reviewer agent. 7. The `README.md` entry-point table — every other primitive, one row each. diff --git a/examples/README.md b/examples/README.md index c869bc89..68e3825c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -15,9 +15,8 @@ which needs an `OPENAI_API_KEY`. | [`openai-stream-backend/`](./openai-stream-backend/) | `runAgentTaskStream` with `createOpenAICompatibleBackend` (real endpoint required) | | [`runtime-run/`](./runtime-run/) | `startRuntimeRun` + cost ledger + persistence adapter | | [`model-resolution/`](./model-resolution/) | `resolveChatModel` + `validateChatModelId` (fail-closed) + `getModels` | -| [`durable-supervisor/`](./durable-supervisor/) | `runSupervisedTurn` — cross-worker resume keystone (fresh / resumed / replayed) | | [`agent-into-reviewer/`](./agent-into-reviewer/) | Pipe one runtime's stream into a reviewer agent (the "2-runtime" pattern) | -| [`chat-handler/`](./chat-handler/) | `DurableChatTurnEngine.runTurn` — the centerpiece production chat handler (fresh / replay paths) | +| [`chat-handler/`](./chat-handler/) | `chatTurnEngine.runTurn` + `deriveExecutionId` — the centerpiece production chat handler | | [`production-trace-sink/`](./production-trace-sink/) | `createProductionTraceSink` — production data capture (RunRecord + OTLP + feedback) | ## Conventions @@ -45,7 +44,6 @@ pnpm tsx examples/sse-stream/sse-stream.ts pnpm tsx examples/sandbox-stream-backend/sandbox-stream-backend.ts pnpm tsx examples/runtime-run/runtime-run.ts pnpm tsx examples/model-resolution/model-resolution.ts -pnpm tsx examples/durable-supervisor/durable-supervisor.ts pnpm tsx examples/agent-into-reviewer/agent-into-reviewer.ts pnpm tsx examples/chat-handler/chat-handler.ts pnpm tsx examples/production-trace-sink/production-trace-sink.ts diff --git a/examples/chat-handler/README.md b/examples/chat-handler/README.md index 91cf28c1..2d8e0cc1 100644 --- a/examples/chat-handler/README.md +++ b/examples/chat-handler/README.md @@ -1,28 +1,22 @@ -# Durable chat handler +# Chat handler -The centerpiece production pattern every product chat handler -implements. `DurableChatTurnEngine.runTurn` composes the substrate -stack: +The centerpiece production pattern every product chat handler implements. +`chatTurnEngine.runTurn` wraps a product `produce()` hook with the +`session.run.*` lifecycle envelope, drains the producer stream through +the NDJSON line protocol, and calls the persist / post-process hooks +after drain. -- Builds the durable manifest from the chat identity (`tenantId` / - `sessionId` / `turnIndex`). -- Drives `runDurableTurn` — checkpoint + replay. -- Emits `session.run.*` lifecycle events around the producer stream. -- Returns a `ReadableStream` of NDJSON-encoded `ChatStreamEvent`s — the - shape your HTTP/SSE route forwards verbatim. - -The example shows: - -- A fresh turn streaming events (the dots are `message.part.updated`). -- A second, different turn — a fresh `runId`, the producer runs again. -- A retry of turn 0 — same identity → same `runId` → the **replay path** - emits the cached final text without re-running the producer. +The engine owns no execution state. Long-running execution durability +lives in the substrate: `@tangle-network/sandbox`'s `box.streamPrompt({ +executionId, lastEventId })` buffers the stream by `executionId`, +replays strictly after `lastEventId` on reconnect, and never spawns a +duplicate execution. `deriveExecutionId({ projectId, sessionId, +turnIndex })` gives a stable id products persist alongside their +session row. In production, `produce()` is a thin wrapper over `runAgentTaskStream(...)` against a real backend (`createOpenAICompatibleBackend` / -`createSandboxPromptBackend`). For the cross-worker-during-turn case — -worker dies *while* streaming — use `runSupervisedTurn` (see -`examples/durable-supervisor/`). +`createSandboxPromptBackend`). ```bash pnpm tsx examples/chat-handler/chat-handler.ts diff --git a/examples/chat-handler/chat-handler.ts b/examples/chat-handler/chat-handler.ts index 3f4ecfdb..9ab05c2d 100644 --- a/examples/chat-handler/chat-handler.ts +++ b/examples/chat-handler/chat-handler.ts @@ -1,14 +1,13 @@ /** - * Full durable chat handler — the centerpiece production pattern every - * product chat handler implements. + * Full chat handler — the centerpiece production pattern every product + * chat handler implements. * - * `DurableChatTurnEngine.runTurn` composes the substrate stack: it builds - * the durable manifest from the chat identity, drives `runDurableTurn`, - * emits `session.run.*` lifecycle events around the producer stream, - * checkpoints the final text into the run store, and returns a ready-to- - * pipe `ReadableStream`. A worker crash *after* the turn finishes replays - * the cached final text; a worker crash *during* the turn is what - * `runSupervisedTurn` is for (see `examples/durable-supervisor/`). + * `ChatTurnEngine.runTurn` wraps the product's `produce()` hook with the + * `session.run.*` lifecycle envelope, drains the producer stream through + * the NDJSON line protocol, and calls the persist / post-process hooks + * after drain. It owns no execution state — that lives in the substrate + * (`@tangle-network/sandbox`'s `box.streamPrompt({ executionId, + * lastEventId })` handles reconnect/replay/dedup). * * In a real product, `produce()` calls `runAgentTaskStream({ task, * backend, input })` with a real backend (`createOpenAICompatibleBackend` @@ -19,15 +18,13 @@ * pnpm tsx examples/chat-handler/chat-handler.ts */ -import type { ChatStreamEvent, DurableTurnProducer } from '@tangle-network/agent-runtime' -import { durableChatTurnEngine, InMemoryDurableRunStore } from '@tangle-network/agent-runtime' - -const store = new InMemoryDurableRunStore() +import type { ChatStreamEvent, ChatTurnProducer } from '@tangle-network/agent-runtime' +import { chatTurnEngine, deriveExecutionId } from '@tangle-network/agent-runtime' // ── The product's `produce` hook — yields the turn's event stream + a // finalText() once drained. In production this is a thin wrapper over // `runAgentTaskStream(...)` against a real backend. ────────────────── -function produce(userMessage: string): DurableTurnProducer { +function produce(userMessage: string): ChatTurnProducer { let accumulated = '' const reply = userMessage.toLowerCase().includes('missing') ? 'The 2026 return is missing Schedule B and one W-2. Please upload them.' @@ -49,17 +46,28 @@ function produce(userMessage: string): DurableTurnProducer { } async function runTurn(userMessage: string, turnIndex: number): Promise { - const result = durableChatTurnEngine.runTurn({ - store, - identity: { tenantId: 'demo-tenant', sessionId: 'thread-42', turnIndex }, - userMessage, + // The execution id products persist alongside their session row so a + // client retry lands on the same substrate execution. The chat-turn + // engine itself does not need it — it goes into the producer hook + // when calling `box.streamPrompt({ executionId, lastEventId })`. + const executionId = deriveExecutionId({ projectId: 'demo-agent', - domain: 'demo', - hooks: { produce: () => produce(userMessage) }, + sessionId: 'thread-42', + turnIndex, + }) + + const result = chatTurnEngine.runTurn({ + identity: { tenantId: 'demo-tenant', sessionId: 'thread-42', userId: 'demo-user', turnIndex }, + hooks: { + produce: () => produce(userMessage), + persistAssistantMessage: async ({ finalText }) => { + console.log( + `[persist ] turn=${turnIndex} executionId=${executionId} chars=${finalText.length}`, + ) + }, + }, }) - // The engine returns a ReadableStream of NDJSON-encoded ChatStreamEvent - // lines — exactly what an SSE/HTTP route forwards to the client. const reader = result.body.getReader() const decoder = new TextDecoder() let buffer = '' @@ -74,7 +82,7 @@ async function runTurn(userMessage: string, turnIndex: number): Promise if (!line) continue const event = JSON.parse(line) as ChatStreamEvent if (event.type === 'message.part.updated') process.stdout.write('.') - if (event.type === 'result') final = String((event.data ?? {}).finalText ?? '') + if (event.type === 'result') final = String(event.data?.finalText ?? '') if (event.type === 'session.run.started') console.log(`[run started ] turn=${turnIndex}`) if (event.type === 'session.run.completed') console.log(`\n[run done ] turn=${turnIndex}`) } @@ -83,18 +91,11 @@ async function runTurn(userMessage: string, turnIndex: number): Promise } async function main() { - // Turn 1: fresh. const t1 = await runTurn('Where do I start with my 2026 return?', 0) console.log(`[turn 0 text ] ${t1}\n`) - // Turn 2: a different turn — fresh manifest, fresh runId. const t2 = await runTurn('What about the missing Schedule B?', 1) console.log(`[turn 1 text ] ${t2}\n`) - - // Turn 3: same identity as turn 0 — durable run hits the replay path - // because step 0 of `chat:thread-42:0` is already completed. - const t3 = await runTurn('Where do I start with my 2026 return?', 0) - console.log(`[turn 0 text ] ${t3} (replayed from durable store)`) } main().catch((err) => { diff --git a/examples/durable-supervisor/README.md b/examples/durable-supervisor/README.md deleted file mode 100644 index 589b47f9..00000000 --- a/examples/durable-supervisor/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Durable run supervisor - -The cross-worker resume keystone. Worker 1 drains part of a long turn, -its isolate "dies" mid-stream, Worker 2 picks the same `runId` up and -resumes from the substrate's event log + cursor — the caller sees the -complete sequence exactly once. - -What the example shows: - -- `runSupervisedTurn` — the platform-agnostic supervisor (drains into - the durable log, persists the `RunHandle`, heartbeats the lease). -- A toy `SandboxReconnectAdapter` — one typed contract: `start()` for a - fresh run, `attach(handle, afterEventId)` to resume past a cursor. -- The three resolution modes — `fresh` / `resumed` / `replayed` — and - the idempotent `appendStreamEvent` that dedups the reconnect seam. - -For the Cloudflare Durable Object host — `createSessionSupervisorDO` — -see the README; it's a ~100-line glue layer around this same primitive. - -```bash -pnpm tsx examples/durable-supervisor/durable-supervisor.ts -``` diff --git a/examples/durable-supervisor/durable-supervisor.ts b/examples/durable-supervisor/durable-supervisor.ts deleted file mode 100644 index 58b99dce..00000000 --- a/examples/durable-supervisor/durable-supervisor.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Durable run supervisor — the cross-worker resume keystone. - * - * Worker 1 drains a few events of a long turn, then its isolate "dies" - * (we stop pulling the generator and expire the lease). Worker 2 picks - * the same `runId` up: it re-yields the logged prefix from the substrate - * and resumes via `adapter.attach(handle, cursor)`. The caller sees the - * complete event sequence exactly once — no gap, no duplicate. - * - * Run with: - * pnpm tsx examples/durable-supervisor/durable-supervisor.ts - */ - -import { - type DurableRunManifest, - InMemoryDurableRunStore, - type RunHandle, - runSupervisedTurn, - type SandboxReconnectAdapter, - type SupervisedEvent, -} from '@tangle-network/agent-runtime' - -// ── Your scripted "sandbox" — pretend the events are streaming from -// somewhere durable (a sandbox container that outlives the worker). ──── -function scriptedAdapter(): SandboxReconnectAdapter { - const script = ['intro', 'analysis', 'finding-1', 'finding-2', 'conclusion'] - return { - async *start(): AsyncGenerator> { - for (const [i, payload] of script.entries()) { - // The first frame carries the handle — that is what the substrate - // uses to re-attach. - const handle: RunHandle | undefined = - i === 0 ? { kind: 'sandbox', runId: 'sbx-1', status: 'running' } : undefined - yield { eventId: `e${i}`, payload, handle } - } - }, - async *attach(_handle, afterEventId) { - const idx = afterEventId ? script.findIndex((_, i) => `e${i}` === afterEventId) : -1 - for (let i = idx + 1; i < script.length; i++) { - yield { eventId: `e${i}`, payload: script[i]! } - } - }, - } -} - -const manifest = { - projectId: 'demo', - scenarioId: 'persona-1', - task: { id: 'long-turn', intent: 'chat', domain: 'demo' }, - input: { q: 'analyse the brief' }, -} as unknown as DurableRunManifest - -async function main() { - const store = new InMemoryDurableRunStore() - const runId = 'turn-1' - - // ── Worker 1 — drains 2 of 5 events, then dies mid-stream ──────────── - const w1 = runSupervisedTurn({ - store, - runId, - manifest, - workerId: 'w1', - adapter: scriptedAdapter(), - }) - const partial: string[] = [] - for await (const event of w1.stream) { - partial.push(event) - if (partial.length >= 2) break // worker 1's isolate "dies" - } - console.log('worker 1 saw: ', partial) // ['intro', 'analysis'] - console.log('store has: ', (await store.readStreamEvents(runId)).length, 'events') // 2 - - // Worker 1 stopped heartbeating; its lease lapses. - store._expireLease(runId) - - // ── Worker 2 picks up the same runId ───────────────────────────────── - const w2 = runSupervisedTurn({ - store, - runId, - manifest, - workerId: 'w2', - adapter: scriptedAdapter(), - }) - const full: string[] = [] - for await (const event of w2.stream) full.push(event) - - console.log('worker 2 saw: ', full) // complete sequence, exactly once - console.log('worker 2 mode: ', w2.mode()) // 'resumed' - console.log('store has: ', (await store.readStreamEvents(runId)).length, 'events') // 5 - console.log('record status: ', w2.record()?.status) // 'completed' -} - -main().catch((err) => { - console.error(err) - process.exit(1) -}) diff --git a/package.json b/package.json index 6ad6437f..9cea776a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tangle-network/agent-runtime", - "version": "0.15.1", + "version": "0.16.0", "description": "Reusable runtime lifecycle for domain-specific agents.", "homepage": "https://github.com/tangle-network/agent-runtime#readme", "repository": { @@ -48,7 +48,6 @@ "prepare": "tsup", "test": "vitest run", "test:watch": "vitest", - "test:workers": "vitest run -c vitest.workers.config.ts", "lint": "biome check src tests examples", "lint:fix": "biome check --write src tests examples", "typecheck": "tsc --noEmit" @@ -58,16 +57,11 @@ }, "devDependencies": { "@biomejs/biome": "^2.4.0", - "@cloudflare/vitest-pool-workers": "^0.8.71", - "@cloudflare/workers-types": "^4.20260522.1", "@tangle-network/sandbox": "0.1.2", - "@types/better-sqlite3": "^7.6.13", "@types/node": "^25.6.0", - "better-sqlite3": "^12.10.0", "tsup": "^8.0.0", "typescript": "^5.7.0", - "vitest": "^3.0.0", - "wrangler": "^4.94.0" + "vitest": "^3.0.0" }, "pnpm": { "minimumReleaseAge": 4320, @@ -75,9 +69,7 @@ "@tangle-network/agent-eval" ], "onlyBuiltDependencies": [ - "better-sqlite3", - "esbuild", - "workerd" + "esbuild" ] }, "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea8f04a0..577582d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,24 +15,12 @@ importers: '@biomejs/biome': specifier: ^2.4.0 version: 2.4.15 - '@cloudflare/vitest-pool-workers': - specifier: ^0.8.71 - version: 0.8.71(@cloudflare/workers-types@4.20260522.1)(@vitest/runner@3.2.4)(@vitest/snapshot@3.2.4)(vitest@3.2.4(@types/node@25.6.0)(yaml@2.8.4)) - '@cloudflare/workers-types': - specifier: ^4.20260522.1 - version: 4.20260522.1 '@tangle-network/sandbox': specifier: 0.1.2 version: 0.1.2(viem@2.48.8(typescript@5.9.3)(zod@4.4.2)) - '@types/better-sqlite3': - specifier: ^7.6.13 - version: 7.6.13 '@types/node': specifier: ^25.6.0 version: 25.6.0 - better-sqlite3: - specifier: ^12.10.0 - version: 12.10.0 tsup: specifier: ^8.0.0 version: 8.5.1(postcss@8.5.13)(typescript@5.9.3)(yaml@2.8.4) @@ -42,9 +30,6 @@ importers: vitest: specifier: ^3.0.0 version: 3.2.4(@types/node@25.6.0)(yaml@2.8.4) - wrangler: - specifier: ^4.94.0 - version: 4.94.0(@cloudflare/workers-types@4.20260522.1) packages: @@ -118,565 +103,156 @@ packages: cpu: [x64] os: [win32] - '@cloudflare/kv-asset-handler@0.4.0': - resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==} - engines: {node: '>=18.0.0'} - - '@cloudflare/kv-asset-handler@0.5.0': - resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} - engines: {node: '>=22.0.0'} - - '@cloudflare/unenv-preset@2.16.1': - resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} - peerDependencies: - unenv: 2.0.0-rc.24 - workerd: '>1.20260305.0 <2.0.0-0' - peerDependenciesMeta: - workerd: - optional: true - - '@cloudflare/unenv-preset@2.7.3': - resolution: {integrity: sha512-tsQQagBKjvpd9baa6nWVIv399ejiqcrUBBW6SZx6Z22+ymm+Odv5+cFimyuCsD/fC1fQTwfRmwXBNpzvHSeGCw==} - peerDependencies: - unenv: 2.0.0-rc.21 - workerd: ^1.20250828.1 - peerDependenciesMeta: - workerd: - optional: true - - '@cloudflare/vitest-pool-workers@0.8.71': - resolution: {integrity: sha512-keu2HCLQfRNwbmLBCDXJgCFpANTaYnQpE01fBOo4CNwiWHUT7SZGN7w64RKiSWRHyYppStXBuE5Ng7F42+flpg==} - peerDependencies: - '@vitest/runner': 2.0.x - 3.2.x - '@vitest/snapshot': 2.0.x - 3.2.x - vitest: 2.0.x - 3.2.x - - '@cloudflare/workerd-darwin-64@1.20250906.0': - resolution: {integrity: sha512-E+X/YYH9BmX0ew2j/mAWFif2z05NMNuhCTlNYEGLkqMe99K15UewBqajL9pMcMUKxylnlrEoK3VNxl33DkbnPA==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@cloudflare/workerd-darwin-64@1.20260521.1': - resolution: {integrity: sha512-aiNdXmxlhwGjTSajL3I7uQPpN4lAOcXjvg5ZOlJKIywnevr798n9XCS6lvuqgniM3KjurBNWRRypMJntg/eSLg==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@cloudflare/workerd-darwin-arm64@1.20250906.0': - resolution: {integrity: sha512-X5apsZ1SFW4FYTM19ISHf8005FJMPfrcf4U5rO0tdj+TeJgQgXuZ57IG0WeW7SpLVeBo8hM6WC8CovZh41AfnA==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@cloudflare/workerd-darwin-arm64@1.20260521.1': - resolution: {integrity: sha512-ikN8aKSi4Ak28ndOkuSO5rq6lmV6wwDQu9F9Vu6J7EkwAOth74J/Hjn4j4EuFceW/npw2Ws0Y/muzA6WKHl4TA==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@cloudflare/workerd-linux-64@1.20250906.0': - resolution: {integrity: sha512-rlKzWgsLnlQ5Nt9W69YBJKcmTmZbOGu0edUsenXPmc6wzULUxoQpi7ZE9k3TfTonJx4WoQsQlzCUamRYFsX+0Q==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@cloudflare/workerd-linux-64@1.20260521.1': - resolution: {integrity: sha512-D/gUhvQcG0pJr5aJl6yUoi2JxbFpjVtDq9xUJHPjfkAjL28TUVgCR/e5r8YGirepv4I1DK7ihuii9LZ2GGMJbw==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@cloudflare/workerd-linux-arm64@1.20250906.0': - resolution: {integrity: sha512-DdedhiQ+SeLzpg7BpcLrIPEZ33QKioJQ1wvL4X7nuLzEB9rWzS37NNNahQzc1+44rhG4fyiHbXBPOeox4B9XVA==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@cloudflare/workerd-linux-arm64@1.20260521.1': - resolution: {integrity: sha512-vhjWPIHenczegTakhRPwEmTeaavCpNqsuo3RlLCkUdU47HrwLvy/4QersGggs4+kF4Do+IE/EznCGyT40xYcLA==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@cloudflare/workerd-windows-64@1.20250906.0': - resolution: {integrity: sha512-Q8Qjfs8jGVILnZL6vUpQ90q/8MTCYaGR3d1LGxZMBqte8Vr7xF3KFHPEy7tFs0j0mMjnqCYzlofmPNY+9ZaDRg==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - - '@cloudflare/workerd-windows-64@1.20260521.1': - resolution: {integrity: sha512-wBolYC/+lnGIEbkkPdzFtjTOWip2uQH6maeAP1ZV0kyxi5SGpsa83+wD5rH5OOle+sHE5qJMdwCKjwRwj+FKJg==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - - '@cloudflare/workers-types@4.20260522.1': - resolution: {integrity: sha512-UjKZprpYHAaBVipLfZA2GzTuWSTHyPXWRsaedwVEqyDe+VHwFi8RVtxGr1lceBDCwLDGRjlUwuwokX6O9GZY2A==} - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - - '@esbuild/aix-ppc64@0.25.4': - resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.4': - resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.27.7': resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.4': - resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.27.7': resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.4': - resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.27.7': resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.4': - resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.27.7': resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.4': - resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.27.7': resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.4': - resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.27.7': resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.4': - resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.4': - resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.27.7': resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.4': - resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.27.7': resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.4': - resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.27.7': resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.4': - resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.27.7': resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.4': - resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.27.7': resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.4': - resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.27.7': resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.4': - resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.27.7': resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.4': - resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.27.7': resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.4': - resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.27.7': resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.4': - resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.27.7': resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.4': - resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.27.7': resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.4': - resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.27.7': resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.4': - resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.27.7': resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.27.7': resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.4': - resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.27.7': resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.4': - resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.27.7': resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.4': - resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.27.7': resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.4': - resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.27.7': resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} @@ -689,250 +265,8 @@ packages: peerDependencies: hono: ^4 - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} - cpu: [s390x] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} - cpu: [s390x] - os: [linux] - - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} - cpu: [x64] - os: [linux] - - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} - cpu: [x64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} - cpu: [x64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} - cpu: [x64] - os: [linux] - - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} @@ -944,9 +278,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@noble/ciphers@1.3.0': resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} @@ -971,15 +302,6 @@ packages: resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} - '@poppinss/colors@4.1.6': - resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} - - '@poppinss/dumper@0.6.5': - resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} - - '@poppinss/exception@1.2.3': - resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@rollup/rollup-android-arm-eabi@4.60.2': resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} cpu: [arm] @@ -1123,13 +445,6 @@ packages: '@scure/bip39@2.2.0': resolution: {integrity: sha512-T/Bj/YvYMNkIPq6EENO6/rcs2e7qTNuyoUXf0KBFDmp0ZDu0H2X4Lq6yC3i0c8PcWkov5EbW+yQZZbdMmk154A==} - '@sindresorhus/is@7.2.0': - resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} - engines: {node: '>=18'} - - '@speed-highlight/core@1.2.15': - resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==} - '@tangle-network/agent-eval@0.33.1': resolution: {integrity: sha512-VAbg1UkC480Xzfi2jqiFMQLYykWvDMO47UHx4bb2rOeiogN1zzM10kPst3OotM+k1B2lbu51uoVnKDBnqK8zcw==} engines: {node: '>=20'} @@ -1152,9 +467,6 @@ packages: engines: {node: '>=18'} hasBin: true - '@types/better-sqlite3@7.6.13': - resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} - '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1207,15 +519,6 @@ packages: zod: optional: true - acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} - engines: {node: '>=0.4.0'} - - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} @@ -1228,28 +531,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - better-sqlite3@12.10.0: - resolution: {integrity: sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==} - engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} - - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - birpc@0.2.14: - resolution: {integrity: sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA==} - - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - - blake3-wasm@2.1.5: - resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1272,26 +553,6 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - - cjs-module-lexer@1.4.3: - resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} @@ -1307,10 +568,6 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} - cookie@1.1.1: - resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} - engines: {node: '>=18'} - dayjs@1.11.20: resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} @@ -1323,47 +580,13 @@ packages: supports-color: optional: true - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - - defu@6.1.7: - resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - devalue@5.8.1: - resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - error-stack-parser-es@1.0.5: - resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} - es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - esbuild@0.25.4: - resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.27.7: resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} @@ -1375,21 +598,10 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - exit-hook@2.2.1: - resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} - engines: {node: '>=6'} - - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1399,42 +611,18 @@ packages: picomatch: optional: true - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - hono@4.12.16: resolution: {integrity: sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==} engines: {node: '>=16.9.0'} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - - is-arrayish@0.3.4: - resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} - isows@1.0.7: resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} peerDependencies: @@ -1447,10 +635,6 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1468,31 +652,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - - miniflare@4.20250906.0: - resolution: {integrity: sha512-T/RWn1sa0ien80s6NjU+Un/tj12gR6wqScZoiLeMJDD4/fK0UXfnbWXJDubnUED8Xjm7RPQ5ESYdE+mhPmMtuQ==} - engines: {node: '>=18.0.0'} - hasBin: true - - miniflare@4.20260521.0: - resolution: {integrity: sha512-roRfxPq49OkuSeQsc43hRjSB1+HdHtDNKRwDEVk2hCjCBuBWxb5Wvwq88b0ULj6QVEJLN/+ZqF19M+h4VYJ/zg==} - engines: {node: '>=22.0.0'} - hasBin: true - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} @@ -1507,23 +666,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-build-utils@2.0.0: - resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - - node-abi@3.92.0: - resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} - engines: {node: '>=10'} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - openapi3-ts@4.5.0: resolution: {integrity: sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==} @@ -1535,9 +681,6 @@ packages: typescript: optional: true - path-to-regexp@6.3.0: - resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1581,23 +724,6 @@ packages: resolution: {integrity: sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==} engines: {node: ^10 || ^12 || >=14} - prebuild-install@7.1.3: - resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} - engines: {node: '>=10'} - deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. - hasBin: true - - pump@3.0.4: - resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} - - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -1611,54 +737,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rosie-skills-darwin-arm64@0.6.4: - resolution: {integrity: sha512-rn1s5hqFKcxeiDEWWoFa1hdGPshR8TkwHLzy/cBavb9XJNAaUxbe3oQ78W9sQkRHAgRyzJYyk9tw68Qrdnizgg==} - cpu: [arm64] - os: [darwin] - - rosie-skills-freebsd-x64@0.6.4: - resolution: {integrity: sha512-SxCRduPBMtfjkQ+q56Yw9OLA3PyaqoALzt7kER7IDKuUVfM2O/1w8sa5xhTDiCvWkZJixnH5d5Ya6KT+/Mwcng==} - cpu: [x64] - os: [freebsd] - - rosie-skills-linux-x64@0.6.4: - resolution: {integrity: sha512-D9Y9mfu7goB0s0X59uU3hcFeUTef3VbpCIDwFMzyvJrAq3XhRACWBDMHQsHlyWdHxTXPX/ILyW65RXyrJlgqng==} - cpu: [x64] - os: [linux] - - rosie-skills@0.6.4: - resolution: {integrity: sha512-ojfhSiQRdZ2QyWbmKAHOSAUbaLYrTc5zIH7mS1jKoP8KCFSQddwVhMyFqldckTeybTfW3zNcsZzyOTzGTN1SBA==} - engines: {node: '>=18'} - hasBin: true - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - semver@7.8.0: - resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} - engines: {node: '>=10'} - hasBin: true - - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - - simple-swizzle@0.2.4: - resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1673,17 +754,6 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - stoppable@1.1.0: - resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} - engines: {node: '>=4', npm: '>=6'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} @@ -1692,17 +762,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true - supports-color@10.2.2: - resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} - engines: {node: '>=18'} - - tar-fs@2.1.4: - resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -1739,9 +798,6 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsup@8.5.1: resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} engines: {node: '>=18'} @@ -1761,9 +817,6 @@ packages: typescript: optional: true - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1775,19 +828,6 @@ packages: undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} - undici@7.24.8: - resolution: {integrity: sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==} - engines: {node: '>=20.18.1'} - - unenv@2.0.0-rc.21: - resolution: {integrity: sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==} - - unenv@2.0.0-rc.24: - resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - viem@2.48.8: resolution: {integrity: sha512-Xj3Nrt66SKtn06kczU91ELn9Difr84ZM5A62BTlaisT5lpgt058i2mBkfMZCXHGb1ocOLjzC2ztPhD0Lvky7uQ==} peerDependencies: @@ -1874,627 +914,158 @@ packages: engines: {node: '>=8'} hasBin: true - workerd@1.20250906.0: - resolution: {integrity: sha512-ryVyEaqXPPsr/AxccRmYZZmDAkfQVjhfRqrNTlEeN8aftBk6Ca1u7/VqmfOayjCXrA+O547TauebU+J3IpvFXw==} - engines: {node: '>=16'} - hasBin: true - - workerd@1.20260521.1: - resolution: {integrity: sha512-HzIThcZ0ZVEuzVxpY2IYZ3yssSrTjtrWXAVfmOl5rVwyqcu7aeZXGMiwrEmi9MOcC3wjy+BNv+hFrMMY5OrjQQ==} - engines: {node: '>=16'} - hasBin: true - - wrangler@4.35.0: - resolution: {integrity: sha512-HbyXtbrh4Fi3mU8ussY85tVdQ74qpVS1vctUgaPc+bPrXBTqfDLkZ6VRtHAVF/eBhz4SFmhJtCQpN1caY2Ak8A==} - engines: {node: '>=18.0.0'} - hasBin: true + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} peerDependencies: - '@cloudflare/workers-types': ^4.20250906.0 + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' peerDependenciesMeta: - '@cloudflare/workers-types': - optional: true - - wrangler@4.94.0: - resolution: {integrity: sha512-GsNw0DomGFfeXFtKVTwn2X69UKcCxcTB0CXykjsMineJIxOeyrw7LovlHQ/3JU8KJHH7repLB+kOHvfTBA/Eew==} - engines: {node: '>=22.0.0'} - hasBin: true - peerDependencies: - '@cloudflare/workers-types': ^4.20260521.1 - peerDependenciesMeta: - '@cloudflare/workers-types': - optional: true - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.20.1: - resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: + bufferutil: optional: true utf-8-validate: optional: true yaml@2.8.4: resolution: {integrity: sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==} - engines: {node: '>= 14.6'} - hasBin: true - - youch-core@0.3.3: - resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} - - youch@4.1.0-beta.10: - resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} - - zod@3.22.3: - resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - - zod@4.4.2: - resolution: {integrity: sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==} - -snapshots: - - '@adraffy/ens-normalize@1.11.1': {} - - '@asteasolutions/zod-to-openapi@8.5.0(zod@4.4.2)': - dependencies: - openapi3-ts: 4.5.0 - zod: 4.4.2 - - '@ax-llm/ax@19.0.45(zod@4.4.2)': - dependencies: - '@opentelemetry/api': 1.9.1 - dayjs: 1.11.20 - optionalDependencies: - zod: 4.4.2 - - '@biomejs/biome@2.4.15': - optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.4.15 - '@biomejs/cli-darwin-x64': 2.4.15 - '@biomejs/cli-linux-arm64': 2.4.15 - '@biomejs/cli-linux-arm64-musl': 2.4.15 - '@biomejs/cli-linux-x64': 2.4.15 - '@biomejs/cli-linux-x64-musl': 2.4.15 - '@biomejs/cli-win32-arm64': 2.4.15 - '@biomejs/cli-win32-x64': 2.4.15 - - '@biomejs/cli-darwin-arm64@2.4.15': - optional: true - - '@biomejs/cli-darwin-x64@2.4.15': - optional: true - - '@biomejs/cli-linux-arm64-musl@2.4.15': - optional: true - - '@biomejs/cli-linux-arm64@2.4.15': - optional: true - - '@biomejs/cli-linux-x64-musl@2.4.15': - optional: true - - '@biomejs/cli-linux-x64@2.4.15': - optional: true - - '@biomejs/cli-win32-arm64@2.4.15': - optional: true - - '@biomejs/cli-win32-x64@2.4.15': - optional: true - - '@cloudflare/kv-asset-handler@0.4.0': - dependencies: - mime: 3.0.0 - - '@cloudflare/kv-asset-handler@0.5.0': {} - - '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260521.1)': - dependencies: - unenv: 2.0.0-rc.24 - optionalDependencies: - workerd: 1.20260521.1 - - '@cloudflare/unenv-preset@2.7.3(unenv@2.0.0-rc.21)(workerd@1.20250906.0)': - dependencies: - unenv: 2.0.0-rc.21 - optionalDependencies: - workerd: 1.20250906.0 - - '@cloudflare/vitest-pool-workers@0.8.71(@cloudflare/workers-types@4.20260522.1)(@vitest/runner@3.2.4)(@vitest/snapshot@3.2.4)(vitest@3.2.4(@types/node@25.6.0)(yaml@2.8.4))': - dependencies: - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - birpc: 0.2.14 - cjs-module-lexer: 1.4.3 - devalue: 5.8.1 - miniflare: 4.20250906.0 - semver: 7.8.0 - vitest: 3.2.4(@types/node@25.6.0)(yaml@2.8.4) - wrangler: 4.35.0(@cloudflare/workers-types@4.20260522.1) - zod: 3.25.76 - transitivePeerDependencies: - - '@cloudflare/workers-types' - - bufferutil - - utf-8-validate - - '@cloudflare/workerd-darwin-64@1.20250906.0': - optional: true - - '@cloudflare/workerd-darwin-64@1.20260521.1': - optional: true - - '@cloudflare/workerd-darwin-arm64@1.20250906.0': - optional: true - - '@cloudflare/workerd-darwin-arm64@1.20260521.1': - optional: true - - '@cloudflare/workerd-linux-64@1.20250906.0': - optional: true - - '@cloudflare/workerd-linux-64@1.20260521.1': - optional: true - - '@cloudflare/workerd-linux-arm64@1.20250906.0': - optional: true - - '@cloudflare/workerd-linux-arm64@1.20260521.1': - optional: true - - '@cloudflare/workerd-windows-64@1.20250906.0': - optional: true - - '@cloudflare/workerd-windows-64@1.20260521.1': - optional: true - - '@cloudflare/workers-types@4.20260522.1': {} - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - - '@emnapi/runtime@1.10.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.25.4': - optional: true - - '@esbuild/aix-ppc64@0.27.3': - optional: true - - '@esbuild/aix-ppc64@0.27.7': - optional: true - - '@esbuild/android-arm64@0.25.4': - optional: true - - '@esbuild/android-arm64@0.27.3': - optional: true - - '@esbuild/android-arm64@0.27.7': - optional: true - - '@esbuild/android-arm@0.25.4': - optional: true - - '@esbuild/android-arm@0.27.3': - optional: true - - '@esbuild/android-arm@0.27.7': - optional: true - - '@esbuild/android-x64@0.25.4': - optional: true - - '@esbuild/android-x64@0.27.3': - optional: true - - '@esbuild/android-x64@0.27.7': - optional: true - - '@esbuild/darwin-arm64@0.25.4': - optional: true - - '@esbuild/darwin-arm64@0.27.3': - optional: true - - '@esbuild/darwin-arm64@0.27.7': - optional: true - - '@esbuild/darwin-x64@0.25.4': - optional: true - - '@esbuild/darwin-x64@0.27.3': - optional: true - - '@esbuild/darwin-x64@0.27.7': - optional: true - - '@esbuild/freebsd-arm64@0.25.4': - optional: true - - '@esbuild/freebsd-arm64@0.27.3': - optional: true - - '@esbuild/freebsd-arm64@0.27.7': - optional: true - - '@esbuild/freebsd-x64@0.25.4': - optional: true - - '@esbuild/freebsd-x64@0.27.3': - optional: true - - '@esbuild/freebsd-x64@0.27.7': - optional: true - - '@esbuild/linux-arm64@0.25.4': - optional: true - - '@esbuild/linux-arm64@0.27.3': - optional: true - - '@esbuild/linux-arm64@0.27.7': - optional: true - - '@esbuild/linux-arm@0.25.4': - optional: true - - '@esbuild/linux-arm@0.27.3': - optional: true - - '@esbuild/linux-arm@0.27.7': - optional: true - - '@esbuild/linux-ia32@0.25.4': - optional: true - - '@esbuild/linux-ia32@0.27.3': - optional: true - - '@esbuild/linux-ia32@0.27.7': - optional: true - - '@esbuild/linux-loong64@0.25.4': - optional: true - - '@esbuild/linux-loong64@0.27.3': - optional: true - - '@esbuild/linux-loong64@0.27.7': - optional: true - - '@esbuild/linux-mips64el@0.25.4': - optional: true - - '@esbuild/linux-mips64el@0.27.3': - optional: true - - '@esbuild/linux-mips64el@0.27.7': - optional: true - - '@esbuild/linux-ppc64@0.25.4': - optional: true - - '@esbuild/linux-ppc64@0.27.3': - optional: true - - '@esbuild/linux-ppc64@0.27.7': - optional: true - - '@esbuild/linux-riscv64@0.25.4': - optional: true - - '@esbuild/linux-riscv64@0.27.3': - optional: true - - '@esbuild/linux-riscv64@0.27.7': - optional: true - - '@esbuild/linux-s390x@0.25.4': - optional: true - - '@esbuild/linux-s390x@0.27.3': - optional: true - - '@esbuild/linux-s390x@0.27.7': - optional: true - - '@esbuild/linux-x64@0.25.4': - optional: true - - '@esbuild/linux-x64@0.27.3': - optional: true - - '@esbuild/linux-x64@0.27.7': - optional: true - - '@esbuild/netbsd-arm64@0.25.4': - optional: true - - '@esbuild/netbsd-arm64@0.27.3': - optional: true - - '@esbuild/netbsd-arm64@0.27.7': - optional: true - - '@esbuild/netbsd-x64@0.25.4': - optional: true - - '@esbuild/netbsd-x64@0.27.3': - optional: true - - '@esbuild/netbsd-x64@0.27.7': - optional: true - - '@esbuild/openbsd-arm64@0.25.4': - optional: true - - '@esbuild/openbsd-arm64@0.27.3': - optional: true - - '@esbuild/openbsd-arm64@0.27.7': - optional: true - - '@esbuild/openbsd-x64@0.25.4': - optional: true - - '@esbuild/openbsd-x64@0.27.3': - optional: true - - '@esbuild/openbsd-x64@0.27.7': - optional: true - - '@esbuild/openharmony-arm64@0.27.3': - optional: true - - '@esbuild/openharmony-arm64@0.27.7': - optional: true - - '@esbuild/sunos-x64@0.25.4': - optional: true - - '@esbuild/sunos-x64@0.27.3': - optional: true - - '@esbuild/sunos-x64@0.27.7': - optional: true - - '@esbuild/win32-arm64@0.25.4': - optional: true - - '@esbuild/win32-arm64@0.27.3': - optional: true - - '@esbuild/win32-arm64@0.27.7': - optional: true - - '@esbuild/win32-ia32@0.25.4': - optional: true - - '@esbuild/win32-ia32@0.27.3': - optional: true - - '@esbuild/win32-ia32@0.27.7': - optional: true + engines: {node: '>= 14.6'} + hasBin: true - '@esbuild/win32-x64@0.25.4': - optional: true + zod@4.4.2: + resolution: {integrity: sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==} - '@esbuild/win32-x64@0.27.3': - optional: true +snapshots: - '@esbuild/win32-x64@0.27.7': - optional: true + '@adraffy/ens-normalize@1.11.1': {} - '@hono/node-server@2.0.1(hono@4.12.16)': + '@asteasolutions/zod-to-openapi@8.5.0(zod@4.4.2)': dependencies: - hono: 4.12.16 - - '@img/colour@1.1.0': {} - - '@img/sharp-darwin-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 - optional: true + openapi3-ts: 4.5.0 + zod: 4.4.2 - '@img/sharp-darwin-arm64@0.34.5': + '@ax-llm/ax@19.0.45(zod@4.4.2)': + dependencies: + '@opentelemetry/api': 1.9.1 + dayjs: 1.11.20 optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 - optional: true + zod: 4.4.2 - '@img/sharp-darwin-x64@0.33.5': + '@biomejs/biome@2.4.15': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 - optional: true + '@biomejs/cli-darwin-arm64': 2.4.15 + '@biomejs/cli-darwin-x64': 2.4.15 + '@biomejs/cli-linux-arm64': 2.4.15 + '@biomejs/cli-linux-arm64-musl': 2.4.15 + '@biomejs/cli-linux-x64': 2.4.15 + '@biomejs/cli-linux-x64-musl': 2.4.15 + '@biomejs/cli-win32-arm64': 2.4.15 + '@biomejs/cli-win32-x64': 2.4.15 - '@img/sharp-darwin-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + '@biomejs/cli-darwin-arm64@2.4.15': optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': + '@biomejs/cli-darwin-x64@2.4.15': optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + '@biomejs/cli-linux-arm64-musl@2.4.15': optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': + '@biomejs/cli-linux-arm64@2.4.15': optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + '@biomejs/cli-linux-x64-musl@2.4.15': optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': + '@biomejs/cli-linux-x64@2.4.15': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + '@biomejs/cli-win32-arm64@2.4.15': optional: true - '@img/sharp-libvips-linux-arm@1.0.5': + '@biomejs/cli-win32-x64@2.4.15': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + '@esbuild/aix-ppc64@0.27.7': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + '@esbuild/android-arm64@0.27.7': optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + '@esbuild/android-arm@0.27.7': optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': + '@esbuild/android-x64@0.27.7': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + '@esbuild/darwin-arm64@0.27.7': optional: true - '@img/sharp-libvips-linux-x64@1.0.4': + '@esbuild/darwin-x64@0.27.7': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + '@esbuild/freebsd-arm64@0.27.7': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + '@esbuild/freebsd-x64@0.27.7': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + '@esbuild/linux-arm64@0.27.7': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': + '@esbuild/linux-arm@0.27.7': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': + '@esbuild/linux-ia32@0.27.7': optional: true - '@img/sharp-linux-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 + '@esbuild/linux-loong64@0.27.7': optional: true - '@img/sharp-linux-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + '@esbuild/linux-mips64el@0.27.7': optional: true - '@img/sharp-linux-arm@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 + '@esbuild/linux-ppc64@0.27.7': optional: true - '@img/sharp-linux-arm@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + '@esbuild/linux-riscv64@0.27.7': optional: true - '@img/sharp-linux-ppc64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@esbuild/linux-s390x@0.27.7': optional: true - '@img/sharp-linux-riscv64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@esbuild/linux-x64@0.27.7': optional: true - '@img/sharp-linux-s390x@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 + '@esbuild/netbsd-arm64@0.27.7': optional: true - '@img/sharp-linux-s390x@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + '@esbuild/netbsd-x64@0.27.7': optional: true - '@img/sharp-linux-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 + '@esbuild/openbsd-arm64@0.27.7': optional: true - '@img/sharp-linux-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + '@esbuild/openbsd-x64@0.27.7': optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@esbuild/openharmony-arm64@0.27.7': optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@esbuild/sunos-x64@0.27.7': optional: true - '@img/sharp-linuxmusl-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@esbuild/win32-arm64@0.27.7': optional: true - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@esbuild/win32-ia32@0.27.7': optional: true - '@img/sharp-wasm32@0.33.5': - dependencies: - '@emnapi/runtime': 1.10.0 + '@esbuild/win32-x64@0.27.7': optional: true - '@img/sharp-wasm32@0.34.5': + '@hono/node-server@2.0.1(hono@4.12.16)': dependencies: - '@emnapi/runtime': 1.10.0 - optional: true - - '@img/sharp-win32-arm64@0.34.5': - optional: true - - '@img/sharp-win32-ia32@0.33.5': - optional: true - - '@img/sharp-win32-ia32@0.34.5': - optional: true - - '@img/sharp-win32-x64@0.33.5': - optional: true - - '@img/sharp-win32-x64@0.34.5': - optional: true + hono: 4.12.16 '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -2510,11 +1081,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@noble/ciphers@1.3.0': {} '@noble/curves@1.9.1': @@ -2531,18 +1097,6 @@ snapshots: '@opentelemetry/api@1.9.1': {} - '@poppinss/colors@4.1.6': - dependencies: - kleur: 4.1.5 - - '@poppinss/dumper@0.6.5': - dependencies: - '@poppinss/colors': 4.1.6 - '@sindresorhus/is': 7.2.0 - supports-color: 10.2.2 - - '@poppinss/exception@1.2.3': {} - '@rollup/rollup-android-arm-eabi@4.60.2': optional: true @@ -2644,10 +1198,6 @@ snapshots: '@noble/hashes': 2.2.0 '@scure/base': 2.2.0 - '@sindresorhus/is@7.2.0': {} - - '@speed-highlight/core@1.2.15': {} - '@tangle-network/agent-eval@0.33.1(typescript@5.9.3)': dependencies: '@asteasolutions/zod-to-openapi': 8.5.0(zod@4.4.2) @@ -2681,10 +1231,6 @@ snapshots: - utf-8-validate - zod - '@types/better-sqlite3@7.6.13': - dependencies: - '@types/node': 25.6.0 - '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -2745,42 +1291,12 @@ snapshots: typescript: 5.9.3 zod: 4.4.2 - acorn-walk@8.3.2: {} - - acorn@8.14.0: {} - acorn@8.16.0: {} any-promise@1.3.0: {} assertion-error@2.0.1: {} - base64-js@1.5.1: {} - - better-sqlite3@12.10.0: - dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.3 - - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - - birpc@0.2.14: {} - - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - - blake3-wasm@2.1.5: {} - - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - bundle-require@5.1.0(esbuild@0.27.7): dependencies: esbuild: 0.27.7 @@ -2802,26 +1318,6 @@ snapshots: dependencies: readdirp: 4.1.2 - chownr@1.1.4: {} - - cjs-module-lexer@1.4.3: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.4 - - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - commander@14.0.3: {} commander@4.1.1: {} @@ -2830,93 +1326,16 @@ snapshots: consola@3.4.2: {} - cookie@1.1.1: {} - dayjs@1.11.20: {} debug@4.4.3: dependencies: ms: 2.1.3 - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - deep-eql@5.0.2: {} - deep-extend@0.6.0: {} - - defu@6.1.7: {} - - detect-libc@2.1.2: {} - - devalue@5.8.1: {} - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - error-stack-parser-es@1.0.5: {} - es-module-lexer@1.7.0: {} - esbuild@0.25.4: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.4 - '@esbuild/android-arm': 0.25.4 - '@esbuild/android-arm64': 0.25.4 - '@esbuild/android-x64': 0.25.4 - '@esbuild/darwin-arm64': 0.25.4 - '@esbuild/darwin-x64': 0.25.4 - '@esbuild/freebsd-arm64': 0.25.4 - '@esbuild/freebsd-x64': 0.25.4 - '@esbuild/linux-arm': 0.25.4 - '@esbuild/linux-arm64': 0.25.4 - '@esbuild/linux-ia32': 0.25.4 - '@esbuild/linux-loong64': 0.25.4 - '@esbuild/linux-mips64el': 0.25.4 - '@esbuild/linux-ppc64': 0.25.4 - '@esbuild/linux-riscv64': 0.25.4 - '@esbuild/linux-s390x': 0.25.4 - '@esbuild/linux-x64': 0.25.4 - '@esbuild/netbsd-arm64': 0.25.4 - '@esbuild/netbsd-x64': 0.25.4 - '@esbuild/openbsd-arm64': 0.25.4 - '@esbuild/openbsd-x64': 0.25.4 - '@esbuild/sunos-x64': 0.25.4 - '@esbuild/win32-arm64': 0.25.4 - '@esbuild/win32-ia32': 0.25.4 - '@esbuild/win32-x64': 0.25.4 - - esbuild@0.27.3: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 - esbuild@0.27.7: optionalDependencies: '@esbuild/aix-ppc64': 0.27.7 @@ -2952,45 +1371,23 @@ snapshots: eventemitter3@5.0.1: {} - exit-hook@2.2.1: {} - - expand-template@2.0.3: {} - expect-type@1.3.0: {} - exsolve@1.0.8: {} - fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 - file-uri-to-path@1.0.0: {} - fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 mlly: 1.8.2 rollup: 4.60.2 - fs-constants@1.0.0: {} - fsevents@2.3.3: optional: true - github-from-package@0.0.0: {} - - glob-to-regexp@0.4.1: {} - hono@4.12.16: {} - ieee754@1.2.1: {} - - inherits@2.0.4: {} - - ini@1.3.8: {} - - is-arrayish@0.3.4: {} - isows@1.0.7(ws@8.18.3): dependencies: ws: 8.18.3 @@ -2999,8 +1396,6 @@ snapshots: js-tokens@9.0.1: {} - kleur@4.1.5: {} - lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -3013,44 +1408,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - mime@3.0.0: {} - - mimic-response@3.1.0: {} - - miniflare@4.20250906.0: - dependencies: - '@cspotcode/source-map-support': 0.8.1 - acorn: 8.14.0 - acorn-walk: 8.3.2 - exit-hook: 2.2.1 - glob-to-regexp: 0.4.1 - sharp: 0.33.5 - stoppable: 1.1.0 - undici: 7.24.8 - workerd: 1.20250906.0 - ws: 8.18.0 - youch: 4.1.0-beta.10 - zod: 3.22.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - miniflare@4.20260521.0: - dependencies: - '@cspotcode/source-map-support': 0.8.1 - sharp: 0.34.5 - undici: 7.24.8 - workerd: 1.20260521.1 - ws: 8.20.1 - youch: 4.1.0-beta.10 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - minimist@1.2.8: {} - - mkdirp-classic@0.5.3: {} - mlly@1.8.2: dependencies: acorn: 8.16.0 @@ -3068,20 +1425,8 @@ snapshots: nanoid@3.3.12: {} - napi-build-utils@2.0.0: {} - - node-abi@3.92.0: - dependencies: - semver: 7.8.0 - object-assign@4.1.1: {} - ohash@2.0.11: {} - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - openapi3-ts@4.5.0: dependencies: yaml: 2.8.4 @@ -3101,8 +1446,6 @@ snapshots: transitivePeerDependencies: - zod - path-to-regexp@6.3.0: {} - pathe@2.0.3: {} pathval@2.0.1: {} @@ -3132,39 +1475,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prebuild-install@7.1.3: - dependencies: - detect-libc: 2.1.2 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 2.0.0 - node-abi: 3.92.0 - pump: 3.0.4 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.4 - tunnel-agent: 0.6.0 - - pump@3.0.4: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readdirp@4.1.2: {} resolve-from@5.0.0: {} @@ -3200,96 +1510,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.2 fsevents: 2.3.3 - rosie-skills-darwin-arm64@0.6.4: - optional: true - - rosie-skills-freebsd-x64@0.6.4: - optional: true - - rosie-skills-linux-x64@0.6.4: - optional: true - - rosie-skills@0.6.4: - optionalDependencies: - rosie-skills-darwin-arm64: 0.6.4 - rosie-skills-freebsd-x64: 0.6.4 - rosie-skills-linux-x64: 0.6.4 - - safe-buffer@5.2.1: {} - - semver@7.8.0: {} - - sharp@0.33.5: - dependencies: - color: 4.2.3 - detect-libc: 2.1.2 - semver: 7.8.0 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 - - sharp@0.34.5: - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.8.0 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 - siginfo@2.0.0: {} - simple-concat@1.0.1: {} - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - - simple-swizzle@0.2.4: - dependencies: - is-arrayish: 0.3.4 - source-map-js@1.2.1: {} source-map@0.7.6: {} @@ -3298,14 +1520,6 @@ snapshots: std-env@3.10.0: {} - stoppable@1.1.0: {} - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-json-comments@2.0.1: {} - strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 @@ -3320,23 +1534,6 @@ snapshots: tinyglobby: 0.2.16 ts-interface-checker: 0.1.13 - supports-color@10.2.2: {} - - tar-fs@2.1.4: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.4 - tar-stream: 2.2.0 - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.5 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -3364,9 +1561,6 @@ snapshots: ts-interface-checker@0.1.13: {} - tslib@2.8.1: - optional: true - tsup@8.5.1(postcss@8.5.13)(typescript@5.9.3)(yaml@2.8.4): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) @@ -3395,32 +1589,12 @@ snapshots: - tsx - yaml - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - typescript@5.9.3: {} ufo@1.6.4: {} undici-types@7.19.2: {} - undici@7.24.8: {} - - unenv@2.0.0-rc.21: - dependencies: - defu: 6.1.7 - exsolve: 1.0.8 - ohash: 2.0.11 - pathe: 2.0.3 - ufo: 1.6.4 - - unenv@2.0.0-rc.24: - dependencies: - pathe: 2.0.3 - - util-deprecate@1.0.2: {} - viem@2.48.8(typescript@5.9.3)(zod@4.4.2): dependencies: '@noble/curves': 1.9.1 @@ -3518,82 +1692,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - workerd@1.20250906.0: - optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20250906.0 - '@cloudflare/workerd-darwin-arm64': 1.20250906.0 - '@cloudflare/workerd-linux-64': 1.20250906.0 - '@cloudflare/workerd-linux-arm64': 1.20250906.0 - '@cloudflare/workerd-windows-64': 1.20250906.0 - - workerd@1.20260521.1: - optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260521.1 - '@cloudflare/workerd-darwin-arm64': 1.20260521.1 - '@cloudflare/workerd-linux-64': 1.20260521.1 - '@cloudflare/workerd-linux-arm64': 1.20260521.1 - '@cloudflare/workerd-windows-64': 1.20260521.1 - - wrangler@4.35.0(@cloudflare/workers-types@4.20260522.1): - dependencies: - '@cloudflare/kv-asset-handler': 0.4.0 - '@cloudflare/unenv-preset': 2.7.3(unenv@2.0.0-rc.21)(workerd@1.20250906.0) - blake3-wasm: 2.1.5 - esbuild: 0.25.4 - miniflare: 4.20250906.0 - path-to-regexp: 6.3.0 - unenv: 2.0.0-rc.21 - workerd: 1.20250906.0 - optionalDependencies: - '@cloudflare/workers-types': 4.20260522.1 - fsevents: 2.3.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - wrangler@4.94.0(@cloudflare/workers-types@4.20260522.1): - dependencies: - '@cloudflare/kv-asset-handler': 0.5.0 - '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260521.1) - blake3-wasm: 2.1.5 - esbuild: 0.27.3 - miniflare: 4.20260521.0 - path-to-regexp: 6.3.0 - rosie-skills: 0.6.4 - unenv: 2.0.0-rc.24 - workerd: 1.20260521.1 - optionalDependencies: - '@cloudflare/workers-types': 4.20260522.1 - fsevents: 2.3.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - wrappy@1.0.2: {} - - ws@8.18.0: {} - ws@8.18.3: {} - ws@8.20.1: {} - yaml@2.8.4: {} - youch-core@0.3.3: - dependencies: - '@poppinss/exception': 1.2.3 - error-stack-parser-es: 1.0.5 - - youch@4.1.0-beta.10: - dependencies: - '@poppinss/colors': 4.1.6 - '@poppinss/dumper': 0.6.5 - '@speed-highlight/core': 1.2.15 - cookie: 1.1.1 - youch-core: 0.3.3 - - zod@3.22.3: {} - - zod@3.25.76: {} - zod@4.4.2: {} diff --git a/src/durable/chat-engine.ts b/src/durable/chat-engine.ts index 5df4d9fa..2697fcda 100644 --- a/src/durable/chat-engine.ts +++ b/src/durable/chat-engine.ts @@ -1,13 +1,19 @@ /** - * `DurableChatTurnEngine` — the framework-neutral chat-turn orchestrator every - * product chat handler routes through. It owns the parts that were copy-pasted - * across legal / gtm / creative / tax: durable checkpointing, the NDJSON - * `StreamEvent` line protocol, the `session.run.*` lifecycle vocabulary, the - * runtime-run cost ledger, and trace flush. Everything genuinely - * product-specific is a hook the product supplies. + * `ChatTurnEngine` — the framework-neutral chat-turn orchestrator every + * product chat handler routes through. Owns the parts that were copy-pasted + * across legal / gtm / creative / tax: the NDJSON `ChatStreamEvent` line + * protocol, the `session.run.*` lifecycle vocabulary, the persist / + * post-process / trace-flush hook ordering. + * + * Execution durability is NOT this engine's problem. The substrate + * (@tangle-network/sandbox + orchestrator) owns it: `box.streamPrompt({ + * executionId, lastEventId })` buffers the stream by `executionId`, + * replays strictly after `lastEventId` on reconnect, and never spawns a + * duplicate. `AgentExecutionHandle` is the typed pointer products + * persist; this engine just wraps a producer that already speaks that + * primitive. * * What the engine owns: - * - durable turn (`runDurableTurn`): completed turns replay free, no re-bill * - the `session.run.started` / `session.run.completed` / `session.run.failed` * event envelope around the producer's events * - NDJSON encoding into a `ReadableStream` (the body every @@ -24,18 +30,14 @@ * - `onTurnComplete` (optional) — post-process (proposals, citations, …) * - `onEvent` (optional) — per-event side-channel (e.g. DO broadcast) * - `transformFinalText` (optional) — pre-persist transform (e.g. PII redact) + * - `traceFlush` (optional) — handed to waitUntil so OTLP export lands * - * Framework neutrality: the engine takes already-resolved values - * (`userId`, identity tuple, parsed message, a `DurableRunStore`, a - * `waitUntil`), never a `Request` or a `Context`. The product's thin route - * adapter does auth + parse + access-control, then calls `engine.runTurn(...)` - * and returns `result.body` as its platform `Response`. + * Framework neutrality: the engine takes already-resolved values (`identity` + * tuple, a `waitUntil`), never a `Request` or a `Context`. The product's + * thin route adapter does auth + parse + access-control, then calls + * `engine.runTurn(...)` and returns `result.body` as its platform `Response`. */ -import { deriveWorkerId } from './identity' -import { type DurableTurnProducer, runDurableTurn } from './turn' -import type { DurableRunManifest, DurableRunStore, RunRecord } from './types' - /** The NDJSON line protocol every product chat client already speaks. */ export interface ChatStreamEvent { type: string @@ -46,30 +48,37 @@ export interface ChatStreamEvent { * scoped products and the user id for session-scoped products. */ export interface ChatTurnIdentity { tenantId: string - /** Thread / session id — the durable run is keyed on this + `turnIndex`. */ + /** Thread / session id. */ sessionId: string userId: string /** Monotonic 0-based turn index within the session. */ turnIndex: number } +/** The live side of a turn — what the product's `produce` hook returns. */ +export interface ChatTurnProducer { + /** The turn's event stream. Forwarded verbatim to the caller. */ + stream: AsyncGenerator + /** The turn's final assistant text. Read once, after `stream` drains. + * When the producer cannot populate this synchronously after drain, + * return '' and use `ChatTurnHooks.accumulate` to assemble text from + * events instead. */ + finalText(): string +} + export interface ChatTurnHooks { /** * Build the backend stream for this turn. The engine never inspects which * backend this is — sandbox container, tcloud router, direct runtime, a * test double — it only forwards the events and reads `finalText()`. */ - produce(): DurableTurnProducer + produce(): ChatTurnProducer /** * Persist the completed assistant message to the product's own store. - * Called once, after the stream drains, on a fresh (non-replay) run. - * Receives the assembled (and `transformFinalText`-transformed) text. + * Called once, after the stream drains, with the assembled (and + * `transformFinalText`-transformed) text. */ - persistAssistantMessage(input: { - identity: ChatTurnIdentity - finalText: string - record: RunRecord | undefined - }): Promise + persistAssistantMessage(input: { identity: ChatTurnIdentity; finalText: string }): Promise /** * Optional post-processing after persistence — proposal extraction, * citation validation, credit metering, etc. Product policy; the engine @@ -88,6 +97,13 @@ export interface ChatTurnHooks { * Affects only what is persisted; the live stream is never altered. */ transformFinalText?(text: string): string | Promise + /** + * Optional live accumulator. When the producer's `finalText()` is only + * valid after drain, this lets the engine also observe each event to + * build the text — return the running text or `undefined` to ignore an + * event. When omitted, `producer.finalText()` is the sole source. + */ + accumulate?(event: ChatStreamEvent, current: string): string | undefined /** * Optional trace flush — resolves when OTLP export completes. The engine * hands it to `waitUntil` so the worker isolate stays alive for the POST. @@ -97,23 +113,10 @@ export interface ChatTurnHooks { export interface RunChatTurnInput { identity: ChatTurnIdentity - /** The user's message for this turn. Hashed into the durable run identity. */ - userMessage: string - /** Product id for telemetry / the durable manifest (`legal-agent`, …). */ - projectId: string - /** Domain tag for the task spec (`legal`, `gtm`, …). */ - domain: string - /** Model id, when known — recorded on the manifest. */ - model?: string - store: DurableRunStore hooks: ChatTurnHooks /** Worker liveness hook (`ctx.waitUntil` / `executionCtx.waitUntil`). When * omitted, trace flush is awaited inline before the stream closes. */ waitUntil?: (p: Promise) => void - /** Stable per-isolate worker id. Defaults to a fresh `deriveWorkerId()`. */ - workerId?: string - /** Lease window in ms. Default 60_000. */ - leaseMs?: number /** Optional structured logger for swallowed hook errors. */ log?: (message: string, meta?: Record) => void } @@ -135,49 +138,23 @@ function encodeLine(event: ChatStreamEvent): Uint8Array { * The engine. One instance is stateless and reusable across requests — all * per-turn state lives in `runTurn`'s closure. */ -export class DurableChatTurnEngine { +export class ChatTurnEngine { /** - * Run one durable chat turn. Returns immediately with a `ReadableStream` - * body; the turn executes as the body is pulled. Never rejects — backend + * Run one chat turn. Returns immediately with a `ReadableStream` body; + * the turn executes as the body is pulled. Never rejects — backend * failures surface as `error` + `session.run.failed` events. */ runTurn(input: RunChatTurnInput): ChatTurnResult { - const workerId = input.workerId ?? deriveWorkerId() const log = input.log ?? (() => undefined) - const { identity } = input - const runId = `chat:${identity.sessionId}:${identity.turnIndex}` - - const manifest: DurableRunManifest = { - projectId: input.projectId, - scenarioId: identity.sessionId, - task: { - id: `${input.projectId}:chat:${identity.sessionId}:${identity.turnIndex}`, - intent: `Run a ${input.domain} chat turn with workspace context.`, - domain: input.domain, - requiredKnowledge: [], - metadata: { - tenantId: identity.tenantId, - sessionId: identity.sessionId, - turnIndex: identity.turnIndex, - }, - }, - input: { - userMessage: input.userMessage, - model: input.model ?? null, - }, - tags: { - session_id: identity.sessionId, - tenant_id: identity.tenantId, - }, - } + const { identity, hooks } = input const body = new ReadableStream({ start: async (controller) => { const emit = async (event: ChatStreamEvent): Promise => { controller.enqueue(encodeLine(event)) - if (input.hooks.onEvent) { + if (hooks.onEvent) { try { - await input.hooks.onEvent(event) + await hooks.onEvent(event) } catch (err) { log('[chat-engine] onEvent hook threw', { error: err instanceof Error ? err.message : String(err), @@ -186,7 +163,6 @@ export class DurableChatTurnEngine { } } - let turnFailed = false try { await emit({ type: 'session.run.started', @@ -197,75 +173,45 @@ export class DurableChatTurnEngine { }, }) - const turn = runDurableTurn({ - store: input.store, - runId, - manifest, - workerId, - leaseMs: input.leaseMs, - intent: `chat:turn-${identity.turnIndex}`, - produce: input.hooks.produce, - replayEvent: (finalText) => ({ type: 'result', data: { finalText } }), - accumulate: (event, current) => { - // Accumulate from the same event shapes products already emit: - // `message.part.updated` text deltas and a trailing `result`. - if (event.type === 'message.part.updated') { - const data = event.data ?? {} - const delta = typeof data.delta === 'string' ? data.delta : '' - const part = data.part as { type?: string; text?: string } | undefined - if (delta) return current + delta - if (part?.type === 'text' && typeof part.text === 'string') return part.text - return undefined - } - if (event.type === 'result') { - const data = event.data ?? {} - if (typeof data.finalText === 'string') return data.finalText - } - return undefined - }, - }) - - for await (const event of turn.stream) { + const producer = hooks.produce() + let accumulated = '' + for await (const event of producer.stream) { + if (hooks.accumulate) { + const next = hooks.accumulate(event, accumulated) + if (typeof next === 'string') accumulated = next + } await emit(event) } - - const rawFinal = turn.finalText() - const finalText = input.hooks.transformFinalText - ? await input.hooks.transformFinalText(rawFinal) + // Producer's own finalText wins when populated; otherwise the + // live accumulator's value stands. + const producerText = producer.finalText() + const rawFinal = producerText || accumulated + const finalText = hooks.transformFinalText + ? await hooks.transformFinalText(rawFinal) : rawFinal - // Persist + post-process only on a fresh run. On replay the - // assistant message + side effects already landed on the first - // (completed) attempt — re-persisting would double-write. - if (!turn.replayed()) { + try { + await hooks.persistAssistantMessage({ identity, finalText }) + } catch (err) { + log('[chat-engine] persistAssistantMessage threw', { + error: err instanceof Error ? err.message : String(err), + }) + } + if (hooks.onTurnComplete) { try { - await input.hooks.persistAssistantMessage({ - identity, - finalText, - record: turn.record(), - }) + await hooks.onTurnComplete({ identity, finalText }) } catch (err) { - log('[chat-engine] persistAssistantMessage threw', { + log('[chat-engine] onTurnComplete threw', { error: err instanceof Error ? err.message : String(err), }) } - if (input.hooks.onTurnComplete) { - try { - await input.hooks.onTurnComplete({ identity, finalText }) - } catch (err) { - log('[chat-engine] onTurnComplete threw', { - error: err instanceof Error ? err.message : String(err), - }) - } - } } await emit({ type: 'session.run.completed', - data: { sessionId: identity.sessionId, replayed: turn.replayed() }, + data: { sessionId: identity.sessionId }, }) } catch (err) { - turnFailed = true const message = err instanceof Error ? err.message : String(err) log('[chat-engine] turn failed', { error: message }) await emit({ type: 'error', data: { message } }) @@ -274,10 +220,8 @@ export class DurableChatTurnEngine { data: { sessionId: identity.sessionId, message }, }) } finally { - // Trace flush: hand to waitUntil so the isolate survives the POST; - // await inline when no waitUntil is available (local / tests). - if (input.hooks.traceFlush) { - const flush = input.hooks.traceFlush().catch((err) => + if (hooks.traceFlush) { + const flush = hooks.traceFlush().catch((err) => log('[chat-engine] traceFlush threw', { error: err instanceof Error ? err.message : String(err), }), @@ -286,7 +230,6 @@ export class DurableChatTurnEngine { else await flush } controller.close() - void turnFailed } }, }) @@ -296,4 +239,4 @@ export class DurableChatTurnEngine { } /** Convenience singleton — the engine is stateless, one instance is enough. */ -export const durableChatTurnEngine = new DurableChatTurnEngine() +export const chatTurnEngine = new ChatTurnEngine() diff --git a/src/durable/d1-store.ts b/src/durable/d1-store.ts deleted file mode 100644 index 7137f1f1..00000000 --- a/src/durable/d1-store.ts +++ /dev/null @@ -1,531 +0,0 @@ -/** - * D1DurableRunStore — the production path for Cloudflare Workers. Backed by - * a D1 (SQLite-compatible) database via the binding the worker already holds. - * - * Apply `./schema.sql` once before use; the store itself does not run DDL. - * Migration version is recorded in `durable_schema_info`; consumers can - * inspect `getSchemaVersion()` if they ship a migration tool. - * - * Why structural typing: agent-runtime stays Cloudflare-free at the dep - * level. Consumers pass their `D1Database` binding — TypeScript matches the - * minimal `D1DatabaseLike` surface below. Tests use the same interface with - * a fake. - */ - -import { manifestHash } from './identity' -import { - DurableRunDivergenceError, - DurableRunInputMismatchError, - DurableRunLeaseHeldError, - type DurableRunManifest, - type DurableRunStore, - type EventRecord, - type RunHandle, - type RunOutcome, - type RunRecord, - type StepError, - type StepKind, - type StepRecord, - type StreamEventRecord, -} from './types' - -const DEFAULT_LEASE_MS = 30_000 - -/** - * Minimal D1 surface this store uses. Compatible with Cloudflare's - * `D1Database` from `@cloudflare/workers-types`. Defined locally so - * agent-runtime does not depend on workers-types at the package level. - */ -export interface D1DatabaseLike { - prepare(query: string): D1PreparedStatementLike - batch(statements: D1PreparedStatementLike[]): Promise -} - -export interface D1PreparedStatementLike { - bind(...values: unknown[]): D1PreparedStatementLike - first(): Promise - all(): Promise<{ results: T[] }> - run(): Promise<{ success: boolean; meta?: { changes?: number } }> -} - -interface RunRow { - run_id: string - manifest_hash: string - project_id: string - scenario_id: string | null - status: RunRecord['status'] - created_at: string - updated_at: string - completed_at: string | null - lease_holder_id: string | null - lease_expires_at: string | null - outcome_json: string | null - step_count: number - handle_json: string | null -} - -interface StreamEventRow { - run_id: string - seq: number - event_id: string - payload_json: string | null - appended_at: string -} - -interface StepRow { - run_id: string - step_index: number - intent: string - kind: string - input_hash: string - status: StepRecord['status'] - attempts: number - result_json: string | null - error_json: string | null - started_at: string | null - completed_at: string | null -} - -interface EventRow { - run_id: string - key: string - payload_json: string | null - emitted_at: string -} - -export class D1DurableRunStore implements DurableRunStore { - constructor(private readonly db: D1DatabaseLike) {} - - /** Override for tests — defaults to Date.now(). */ - public now: () => number = () => Date.now() - - async startOrResume(input: { - runId: string - manifest: DurableRunManifest - workerId: string - leaseMs?: number - }): ReturnType { - const leaseMs = input.leaseMs ?? DEFAULT_LEASE_MS - const hash = manifestHash(input.manifest) - const nowMs = this.now() - const nowIso = new Date(nowMs).toISOString() - const leaseExpiresAt = new Date(nowMs + leaseMs).toISOString() - - const existing = await this.db - .prepare('SELECT * FROM durable_runs WHERE run_id = ?') - .bind(input.runId) - .first() - - if (!existing) { - await this.db - .prepare( - `INSERT INTO durable_runs - (run_id, manifest_hash, project_id, scenario_id, status, - created_at, updated_at, lease_holder_id, lease_expires_at, step_count) - VALUES (?, ?, ?, ?, 'running', ?, ?, ?, ?, 0)`, - ) - .bind( - input.runId, - hash, - input.manifest.projectId, - input.manifest.scenarioId ?? null, - nowIso, - nowIso, - input.workerId, - leaseExpiresAt, - ) - .run() - const record: RunRecord = { - runId: input.runId, - manifestHash: hash, - projectId: input.manifest.projectId, - scenarioId: input.manifest.scenarioId, - status: 'running', - createdAt: nowIso, - updatedAt: nowIso, - leaseHolderId: input.workerId, - leaseExpiresAt, - stepCount: 0, - } - return { run: record, completedSteps: [], leaseExpiresAt } - } - - if (existing.manifest_hash !== hash) { - throw new DurableRunInputMismatchError( - `runId ${input.runId} exists with a different manifest hash; refuse to corrupt prior steps`, - ) - } - // Lease takeover — conditional UPDATE. - const claim = await this.db - .prepare( - `UPDATE durable_runs - SET lease_holder_id = ?, - lease_expires_at = ?, - updated_at = ?, - status = CASE WHEN status IN ('completed','failed') THEN status ELSE 'running' END - WHERE run_id = ? - AND ( - lease_holder_id = ? OR - lease_holder_id IS NULL OR - lease_expires_at IS NULL OR - lease_expires_at < ? - )`, - ) - .bind(input.workerId, leaseExpiresAt, nowIso, input.runId, input.workerId, nowIso) - .run() - const changes = claim.meta?.changes ?? 0 - if (changes === 0) { - throw new DurableRunLeaseHeldError( - `runId ${input.runId} leased by ${existing.lease_holder_id} until ${existing.lease_expires_at}`, - ) - } - const completedSteps = await this.readSteps(input.runId, 'completed') - const record: RunRecord = rowToRunRecord({ - ...existing, - lease_holder_id: input.workerId, - lease_expires_at: leaseExpiresAt, - updated_at: nowIso, - status: - existing.status === 'completed' || existing.status === 'failed' - ? existing.status - : 'running', - }) - return { run: record, completedSteps, leaseExpiresAt } - } - - async renewLease(input: { - runId: string - workerId: string - leaseMs?: number - }): Promise<{ ok: boolean; leaseExpiresAt?: string }> { - const nowMs = this.now() - const nowIso = new Date(nowMs).toISOString() - const leaseExpiresAt = new Date(nowMs + (input.leaseMs ?? DEFAULT_LEASE_MS)).toISOString() - const res = await this.db - .prepare( - `UPDATE durable_runs - SET lease_expires_at = ?, updated_at = ? - WHERE run_id = ? - AND (lease_holder_id = ? OR lease_expires_at IS NULL OR lease_expires_at < ?)`, - ) - .bind(leaseExpiresAt, nowIso, input.runId, input.workerId, nowIso) - .run() - const ok = (res.meta?.changes ?? 0) > 0 - return ok ? { ok: true, leaseExpiresAt } : { ok: false } - } - - async loadStep(runId: string, stepIndex: number): Promise { - const row = await this.db - .prepare('SELECT * FROM durable_steps WHERE run_id = ? AND step_index = ?') - .bind(runId, stepIndex) - .first() - return row ? rowToStepRecord(row) : undefined - } - - async beginStep(input: { - runId: string - stepIndex: number - intent: string - kind: StepKind - inputHash: string - }): Promise { - const nowIso = new Date(this.now()).toISOString() - const prior = await this.loadStep(input.runId, input.stepIndex) - if (prior) { - if (prior.intent !== input.intent) { - throw new DurableRunDivergenceError( - `step ${input.stepIndex}: intent changed ('${prior.intent}' -> '${input.intent}')`, - ) - } - await this.db - .prepare( - `UPDATE durable_steps - SET status='running', attempts = attempts + 1, started_at = ?, error_json = NULL - WHERE run_id = ? AND step_index = ?`, - ) - .bind(nowIso, input.runId, input.stepIndex) - .run() - await this.bumpUpdated(input.runId, nowIso) - return { - ...prior, - attempts: prior.attempts + 1, - status: 'running', - startedAt: nowIso, - error: undefined, - } - } - await this.db - .prepare( - `INSERT INTO durable_steps - (run_id, step_index, intent, kind, input_hash, status, attempts, started_at) - VALUES (?, ?, ?, ?, ?, 'running', 1, ?)`, - ) - .bind(input.runId, input.stepIndex, input.intent, input.kind, input.inputHash, nowIso) - .run() - await this.db - .prepare( - `UPDATE durable_runs - SET step_count = MAX(step_count, ?), updated_at = ? - WHERE run_id = ?`, - ) - .bind(input.stepIndex + 1, nowIso, input.runId) - .run() - return { - runId: input.runId, - stepIndex: input.stepIndex, - intent: input.intent, - kind: input.kind, - inputHash: input.inputHash, - status: 'running', - attempts: 1, - startedAt: nowIso, - } - } - - async completeStep(input: { - runId: string - stepIndex: number - result: unknown - }): Promise { - const nowIso = new Date(this.now()).toISOString() - await this.db - .prepare( - `UPDATE durable_steps - SET status='completed', result_json = ?, completed_at = ?, error_json = NULL - WHERE run_id = ? AND step_index = ?`, - ) - .bind(JSON.stringify(input.result ?? null), nowIso, input.runId, input.stepIndex) - .run() - await this.bumpUpdated(input.runId, nowIso) - const row = await this.loadStep(input.runId, input.stepIndex) - if (!row) { - throw new Error(`durable-runs: completeStep cannot find step ${input.stepIndex}`) - } - return row - } - - async failStep(input: { - runId: string - stepIndex: number - error: StepError - }): Promise { - const nowIso = new Date(this.now()).toISOString() - await this.db - .prepare( - `UPDATE durable_steps - SET status='failed', error_json = ?, completed_at = ? - WHERE run_id = ? AND step_index = ?`, - ) - .bind(JSON.stringify(input.error), nowIso, input.runId, input.stepIndex) - .run() - await this.bumpUpdated(input.runId, nowIso) - const row = await this.loadStep(input.runId, input.stepIndex) - if (!row) { - throw new Error(`durable-runs: failStep cannot find step ${input.stepIndex}`) - } - return row - } - - async endRun(input: { - runId: string - workerId: string - status: 'completed' | 'failed' - outcome?: RunOutcome - }): Promise { - const nowIso = new Date(this.now()).toISOString() - await this.db - .prepare( - `UPDATE durable_runs - SET status = ?, completed_at = ?, updated_at = ?, - outcome_json = ?, - lease_holder_id = CASE WHEN lease_holder_id = ? THEN NULL ELSE lease_holder_id END, - lease_expires_at = CASE WHEN lease_holder_id = ? THEN NULL ELSE lease_expires_at END - WHERE run_id = ?`, - ) - .bind( - input.status, - nowIso, - nowIso, - input.outcome ? JSON.stringify(input.outcome) : null, - input.workerId, - input.workerId, - input.runId, - ) - .run() - const row = await this.db - .prepare('SELECT * FROM durable_runs WHERE run_id = ?') - .bind(input.runId) - .first() - if (!row) throw new Error(`durable-runs: endRun cannot find run ${input.runId}`) - return rowToRunRecord(row) - } - - async emitEvent(input: { - runId: string - key: string - payload: unknown - }): ReturnType { - const nowIso = new Date(this.now()).toISOString() - // INSERT OR IGNORE — first emit wins; subsequent inserts no-op. - const res = await this.db - .prepare( - `INSERT OR IGNORE INTO durable_events (run_id, key, payload_json, emitted_at) - VALUES (?, ?, ?, ?)`, - ) - .bind(input.runId, input.key, JSON.stringify(input.payload ?? null), nowIso) - .run() - const accepted = (res.meta?.changes ?? 0) > 0 - const row = await this.db - .prepare('SELECT * FROM durable_events WHERE run_id = ? AND key = ?') - .bind(input.runId, input.key) - .first() - if (!row) throw new Error('durable-runs: emitEvent failed to persist or read back') - return { - accepted, - record: rowToEventRecord(row), - } - } - - async loadEvent(runId: string, key: string): Promise { - const row = await this.db - .prepare('SELECT * FROM durable_events WHERE run_id = ? AND key = ?') - .bind(runId, key) - .first() - return row ? rowToEventRecord(row) : undefined - } - - async appendStreamEvent(input: { - runId: string - eventId: string - payload: unknown - }): ReturnType { - const nowIso = new Date(this.now()).toISOString() - // INSERT OR IGNORE — the UNIQUE(run_id, event_id) index makes a re-append - // a no-op; seq is the next monotonic value, computed atomically with the - // insert. A new event_id never collides on the (run_id, seq) PK. - const res = await this.db - .prepare( - `INSERT OR IGNORE INTO durable_stream_events (run_id, seq, event_id, payload_json, appended_at) - VALUES ( - ?, - (SELECT COALESCE(MAX(seq), -1) + 1 FROM durable_stream_events WHERE run_id = ?), - ?, ?, ? - )`, - ) - .bind(input.runId, input.runId, input.eventId, JSON.stringify(input.payload ?? null), nowIso) - .run() - const accepted = (res.meta?.changes ?? 0) > 0 - const row = await this.db - .prepare('SELECT * FROM durable_stream_events WHERE run_id = ? AND event_id = ?') - .bind(input.runId, input.eventId) - .first() - if (!row) throw new Error('durable-runs: appendStreamEvent failed to persist or read back') - return { accepted, record: rowToStreamEventRecord(row) } - } - - async readStreamEvents( - runId: string, - afterSeq?: number, - ): Promise> { - const { results } = await this.db - .prepare('SELECT * FROM durable_stream_events WHERE run_id = ? AND seq > ? ORDER BY seq') - .bind(runId, afterSeq ?? -1) - .all() - return results.map(rowToStreamEventRecord) - } - - async setRunHandle(input: { runId: string; handle: RunHandle }): Promise { - const nowIso = new Date(this.now()).toISOString() - await this.db - .prepare('UPDATE durable_runs SET handle_json = ?, updated_at = ? WHERE run_id = ?') - .bind(JSON.stringify(input.handle), nowIso, input.runId) - .run() - } - - async close(): Promise { - // D1 binding lifecycle is owned by the runtime; no-op. - } - - /** Inspect the currently-applied schema version. */ - async getSchemaVersion(): Promise { - const row = await this.db - .prepare('SELECT MAX(version) AS version FROM durable_schema_info') - .first<{ version: number | null }>() - return row?.version ?? undefined - } - - // ── internals ────────────────────────────────────────────────────── - - private async readSteps( - runId: string, - status: StepRecord['status'], - ): Promise> { - const { results } = await this.db - .prepare('SELECT * FROM durable_steps WHERE run_id = ? AND status = ? ORDER BY step_index') - .bind(runId, status) - .all() - return results.map(rowToStepRecord) - } - - private async bumpUpdated(runId: string, nowIso: string): Promise { - await this.db - .prepare('UPDATE durable_runs SET updated_at = ? WHERE run_id = ?') - .bind(nowIso, runId) - .run() - } -} - -// ── row → record helpers ─────────────────────────────────────────────── - -function rowToRunRecord(row: RunRow): RunRecord { - return { - runId: row.run_id, - manifestHash: row.manifest_hash, - projectId: row.project_id, - scenarioId: row.scenario_id ?? undefined, - status: row.status, - createdAt: row.created_at, - updatedAt: row.updated_at, - completedAt: row.completed_at ?? undefined, - leaseHolderId: row.lease_holder_id ?? undefined, - leaseExpiresAt: row.lease_expires_at ?? undefined, - outcome: row.outcome_json ? (JSON.parse(row.outcome_json) as RunOutcome) : undefined, - stepCount: row.step_count, - handle: row.handle_json ? (JSON.parse(row.handle_json) as RunHandle) : undefined, - } -} - -function rowToStreamEventRecord(row: StreamEventRow): StreamEventRecord { - return { - runId: row.run_id, - seq: row.seq, - eventId: row.event_id, - payload: row.payload_json ? JSON.parse(row.payload_json) : null, - appendedAt: row.appended_at, - } -} - -function rowToStepRecord(row: StepRow): StepRecord { - return { - runId: row.run_id, - stepIndex: row.step_index, - intent: row.intent, - kind: row.kind as StepKind, - inputHash: row.input_hash, - status: row.status, - attempts: row.attempts, - result: row.result_json ? JSON.parse(row.result_json) : undefined, - error: row.error_json ? (JSON.parse(row.error_json) as StepError) : undefined, - startedAt: row.started_at ?? undefined, - completedAt: row.completed_at ?? undefined, - } -} - -function rowToEventRecord(row: EventRow): EventRecord { - return { - runId: row.run_id, - key: row.key, - payload: row.payload_json ? JSON.parse(row.payload_json) : null, - emittedAt: row.emitted_at, - } -} diff --git a/src/durable/execution-handle.ts b/src/durable/execution-handle.ts new file mode 100644 index 00000000..a2af1c8d --- /dev/null +++ b/src/durable/execution-handle.ts @@ -0,0 +1,80 @@ +/** + * `AgentExecutionHandle` — the typed pointer to a substrate-owned, long- + * running agent execution. + * + * Execution state lives in the substrate. `@tangle-network/sandbox`'s + * `box.streamPrompt({ executionId, lastEventId })` (and the orchestrator + * behind it) already buffers the event stream by `executionId`, replays + * strictly after `lastEventId` on reconnect, and never spawns a duplicate + * execution for the same id. agent-runtime owns this typed pointer so + * callers (chat handlers, replay glue, telemetry) can talk about a run's + * continuity without depending on the SDK shape directly. + * + * Usage: + * + * const handle: AgentExecutionHandle = { + * executionId: deriveExecutionId({ projectId, sessionId, turnIndex }), + * sessionId, + * } + * for await (const event of box.streamPrompt(prompt, { + * executionId: handle.executionId, + * lastEventId: handle.lastEventId, + * })) { ... } + * + * On reconnect the product persists the last event id it acknowledged and + * re-passes both — the orchestrator replays from `lastEventId + 1` without + * re-running the agent. + */ + +export interface AgentExecutionHandle { + /** Stable substrate execution id. The same id on retry → orchestrator + * replays the buffered stream rather than spawning a second run. */ + executionId: string + /** Substrate session id, when the execution is part of a multi-turn + * session. Optional — not every substrate is session-scoped. */ + sessionId?: string + /** Last event id the caller acknowledged. The substrate replays strictly + * after this id on reconnect. Omit on first attempt. */ + lastEventId?: string +} + +/** + * Structural contract for a substrate-backed reconnectable stream — what + * `box.streamPrompt` already implements. Provided as a typed shape so + * substrate-neutral helpers (e.g. a chat-turn producer) can be written + * without depending on the sandbox SDK directly: + * + * const sandboxStream: ReconnectableAgentStream = { + * open: (handle) => box.streamPrompt(prompt, { + * executionId: handle.executionId, + * lastEventId: handle.lastEventId, + * ...sandboxOptions, + * }), + * } + */ +export interface ReconnectableAgentStream { + /** Open or resume the stream. The implementation forwards + * `handle.executionId` + `handle.lastEventId` to the substrate; on + * reconnect the substrate replays from `lastEventId` without + * re-running the agent. */ + open(handle: AgentExecutionHandle): AsyncIterable +} + +/** + * Derive a stable executionId from the run identity. The same + * `(projectId, sessionId, turnIndex)` tuple yields the same id — so a + * client retry of the same turn lands on the same substrate execution + * and the orchestrator's buffer replays instead of starting a second + * prompt. + * + * Format is readable, not hashed: operators grepping orchestrator logs + * for `gtm-agent:thread-abc:3` find the run without translating an opaque + * id. Substrate executionIds are not a secrecy boundary. + */ +export function deriveExecutionId(input: { + projectId: string + sessionId: string + turnIndex: number +}): string { + return `${input.projectId}:${input.sessionId}:${input.turnIndex}` +} diff --git a/src/durable/file-system-store.ts b/src/durable/file-system-store.ts deleted file mode 100644 index bd71592d..00000000 --- a/src/durable/file-system-store.ts +++ /dev/null @@ -1,426 +0,0 @@ -/** - * FileSystemDurableRunStore — durable-run substrate backed by a directory - * tree under a single root. One subdir per run: - * - * // - * run.json — RunRecord (rewritten on every mutation; the only - * scalar fields are status/lease, so this stays small) - * steps.jsonl — append-only StepRecord stream; one JSON per line - * events.jsonl — append-only EventRecord stream - * lease.json — current leaseholder + deadline (separate from - * run.json so renewLease writes one tiny file - * instead of round-tripping the whole run record) - * - * Concurrency: the eval harness is single-process — we rely on Node's - * append-mode semantics for atomicity of step / event writes (single-line - * writes < PIPE_BUF are atomic on POSIX). For run.json / lease.json we write - * to a `.tmp` then `rename` to make replacement atomic. This is - * sufficient for the single-process eval harness use case. Multi-process - * concurrency on the SAME filesystem requires a flock-based extension; - * for that path use D1DurableRunStore. - */ - -import { existsSync, mkdirSync } from 'node:fs' -import { appendFile, readdir, readFile, rename, writeFile } from 'node:fs/promises' -import { join } from 'node:path' - -import { manifestHash } from './identity' -import { - DurableRunDivergenceError, - DurableRunInputMismatchError, - DurableRunLeaseHeldError, - type DurableRunManifest, - type DurableRunStore, - type EventRecord, - type RunHandle, - type RunOutcome, - type RunRecord, - type StepError, - type StepKind, - type StepRecord, - type StreamEventRecord, -} from './types' - -const DEFAULT_LEASE_MS = 30_000 - -interface LeaseFile { - workerId: string - leaseExpiresAt: string -} - -export class FileSystemDurableRunStore implements DurableRunStore { - constructor(private readonly root: string) { - mkdirSync(root, { recursive: true }) - } - - /** Override for tests — defaults to Date.now(). */ - public now: () => number = () => Date.now() - - async startOrResume(input: { - runId: string - manifest: DurableRunManifest - workerId: string - leaseMs?: number - }): ReturnType { - const leaseMs = input.leaseMs ?? DEFAULT_LEASE_MS - const hash = manifestHash(input.manifest) - const nowMs = this.now() - const nowIso = new Date(nowMs).toISOString() - const leaseExpiresAt = new Date(nowMs + leaseMs).toISOString() - const dir = this.runDir(input.runId) - - if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }) - const record: RunRecord = { - runId: input.runId, - manifestHash: hash, - projectId: input.manifest.projectId, - scenarioId: input.manifest.scenarioId, - status: 'running', - createdAt: nowIso, - updatedAt: nowIso, - leaseHolderId: input.workerId, - leaseExpiresAt, - stepCount: 0, - } - await this.writeRun(record) - await this.writeLease(input.runId, { workerId: input.workerId, leaseExpiresAt }) - // Touch the jsonl files so listing them later doesn't ENOENT. - await appendFile(join(dir, 'steps.jsonl'), '', 'utf8') - await appendFile(join(dir, 'events.jsonl'), '', 'utf8') - await appendFile(join(dir, 'stream-events.jsonl'), '', 'utf8') - return { run: record, completedSteps: [], leaseExpiresAt } - } - - const record = await this.readRun(input.runId) - if (record.manifestHash !== hash) { - throw new DurableRunInputMismatchError( - `runId ${input.runId} exists with a different manifest hash; refuse to corrupt prior steps`, - ) - } - const lease = await this.readLeaseSafe(input.runId) - const leaseStillLive = - lease && lease.workerId !== input.workerId && new Date(lease.leaseExpiresAt).getTime() > nowMs - if (leaseStillLive) { - throw new DurableRunLeaseHeldError( - `runId ${input.runId} leased by ${lease.workerId} until ${lease.leaseExpiresAt}`, - ) - } - const completedSteps = await this.readSteps(input.runId) - const nextRecord: RunRecord = { - ...record, - status: - record.status === 'completed' || record.status === 'failed' ? record.status : 'running', - updatedAt: nowIso, - leaseHolderId: input.workerId, - leaseExpiresAt, - } - await this.writeRun(nextRecord) - await this.writeLease(input.runId, { workerId: input.workerId, leaseExpiresAt }) - return { run: nextRecord, completedSteps, leaseExpiresAt } - } - - async renewLease(input: { - runId: string - workerId: string - leaseMs?: number - }): Promise<{ ok: boolean; leaseExpiresAt?: string }> { - const lease = await this.readLeaseSafe(input.runId) - const nowMs = this.now() - if (lease && lease.workerId !== input.workerId) { - if (new Date(lease.leaseExpiresAt).getTime() > nowMs) { - return { ok: false } - } - } - const leaseExpiresAt = new Date(nowMs + (input.leaseMs ?? DEFAULT_LEASE_MS)).toISOString() - await this.writeLease(input.runId, { workerId: input.workerId, leaseExpiresAt }) - return { ok: true, leaseExpiresAt } - } - - async loadStep(runId: string, stepIndex: number): Promise { - const steps = await this.readSteps(runId, { includeFailed: true, includeRunning: true }) - return steps.find((s) => s.stepIndex === stepIndex) - } - - async beginStep(input: { - runId: string - stepIndex: number - intent: string - kind: StepKind - inputHash: string - }): Promise { - const all = await this.readSteps(input.runId, { includeFailed: true, includeRunning: true }) - const prior = all.find((s) => s.stepIndex === input.stepIndex) - const nowIso = new Date(this.now()).toISOString() - if (prior) { - if (prior.intent !== input.intent) { - throw new DurableRunDivergenceError( - `step ${input.stepIndex}: intent changed ('${prior.intent}' -> '${input.intent}')`, - ) - } - const rec: StepRecord = { - ...prior, - attempts: prior.attempts + 1, - status: 'running', - startedAt: nowIso, - error: undefined, - } - await this.appendStep(input.runId, rec) - await this.bumpRunUpdated(input.runId, nowIso) - return rec - } - const rec: StepRecord = { - runId: input.runId, - stepIndex: input.stepIndex, - intent: input.intent, - kind: input.kind, - inputHash: input.inputHash, - status: 'running', - attempts: 1, - startedAt: nowIso, - } - await this.appendStep(input.runId, rec) - // Bump run.stepCount opportunistically. - const record = await this.readRun(input.runId) - record.stepCount = Math.max(record.stepCount, input.stepIndex + 1) - record.updatedAt = nowIso - await this.writeRun(record) - return rec - } - - async completeStep(input: { - runId: string - stepIndex: number - result: unknown - }): Promise { - const all = await this.readSteps(input.runId, { includeFailed: true, includeRunning: true }) - const prior = all.find((s) => s.stepIndex === input.stepIndex) - if (!prior) { - throw new Error( - `durable-runs: completeStep called before beginStep (step ${input.stepIndex})`, - ) - } - const nowIso = new Date(this.now()).toISOString() - const rec: StepRecord = { - ...prior, - status: 'completed', - result: input.result, - completedAt: nowIso, - error: undefined, - } - await this.appendStep(input.runId, rec) - await this.bumpRunUpdated(input.runId, nowIso) - return rec - } - - async failStep(input: { - runId: string - stepIndex: number - error: StepError - }): Promise { - const all = await this.readSteps(input.runId, { includeFailed: true, includeRunning: true }) - const prior = all.find((s) => s.stepIndex === input.stepIndex) - if (!prior) { - throw new Error(`durable-runs: failStep called before beginStep (step ${input.stepIndex})`) - } - const nowIso = new Date(this.now()).toISOString() - const rec: StepRecord = { - ...prior, - status: 'failed', - error: input.error, - completedAt: nowIso, - } - await this.appendStep(input.runId, rec) - await this.bumpRunUpdated(input.runId, nowIso) - return rec - } - - async endRun(input: { - runId: string - workerId: string - status: 'completed' | 'failed' - outcome?: RunOutcome - }): Promise { - const record = await this.readRun(input.runId) - const nowIso = new Date(this.now()).toISOString() - record.status = input.status - record.outcome = input.outcome - record.completedAt = nowIso - record.updatedAt = nowIso - const lease = await this.readLeaseSafe(input.runId) - if (lease && lease.workerId === input.workerId) { - record.leaseHolderId = undefined - record.leaseExpiresAt = undefined - await this.writeLease(input.runId, null) - } - await this.writeRun(record) - return record - } - - async emitEvent(input: { - runId: string - key: string - payload: unknown - }): ReturnType { - const existing = await this.loadEvent(input.runId, input.key) - if (existing) return { accepted: false, record: existing } - const rec: EventRecord = { - runId: input.runId, - key: input.key, - payload: input.payload, - emittedAt: new Date(this.now()).toISOString(), - } - await appendFile( - join(this.runDir(input.runId), 'events.jsonl'), - `${JSON.stringify(rec)}\n`, - 'utf8', - ) - return { accepted: true, record: rec } - } - - async loadEvent(runId: string, key: string): Promise { - const path = join(this.runDir(runId), 'events.jsonl') - if (!existsSync(path)) return undefined - const content = await readFile(path, 'utf8') - for (const line of content.split('\n').reverse()) { - if (!line) continue - const rec = JSON.parse(line) as EventRecord - if (rec.key === key) return rec - } - return undefined - } - - async appendStreamEvent(input: { - runId: string - eventId: string - payload: unknown - }): ReturnType { - const existing = await this.readStreamEventsRaw(input.runId) - const dup = existing.find((e) => e.eventId === input.eventId) - if (dup) return { accepted: false, record: dup } - const rec: StreamEventRecord = { - runId: input.runId, - seq: existing.length, - eventId: input.eventId, - payload: input.payload, - appendedAt: new Date(this.now()).toISOString(), - } - await appendFile( - join(this.runDir(input.runId), 'stream-events.jsonl'), - `${JSON.stringify(rec)}\n`, - 'utf8', - ) - return { accepted: true, record: rec } - } - - async readStreamEvents( - runId: string, - afterSeq?: number, - ): Promise> { - const cutoff = afterSeq ?? -1 - return (await this.readStreamEventsRaw(runId)).filter((e) => e.seq > cutoff) - } - - async setRunHandle(input: { runId: string; handle: RunHandle }): Promise { - const record = await this.readRun(input.runId) - record.handle = input.handle - record.updatedAt = new Date(this.now()).toISOString() - await this.writeRun(record) - } - - async close(): Promise { - // No persistent handles to close. - } - - private async readStreamEventsRaw(runId: string): Promise { - const path = join(this.runDir(runId), 'stream-events.jsonl') - if (!existsSync(path)) return [] - const content = await readFile(path, 'utf8') - const out: StreamEventRecord[] = [] - for (const line of content.split('\n')) { - if (!line) continue - out.push(JSON.parse(line) as StreamEventRecord) - } - return out.sort((a, b) => a.seq - b.seq) - } - - /** @internal — used by tests to list runs in the store. */ - async _listRunIds(): Promise { - if (!existsSync(this.root)) return [] - const entries = await readdir(this.root, { withFileTypes: true }) - return entries.filter((e) => e.isDirectory()).map((e) => e.name) - } - - // ── internals ────────────────────────────────────────────────────── - - private runDir(runId: string): string { - return join(this.root, runId) - } - - private async readRun(runId: string): Promise { - const path = join(this.runDir(runId), 'run.json') - const content = await readFile(path, 'utf8') - return JSON.parse(content) as RunRecord - } - - private async writeRun(record: RunRecord): Promise { - const dir = this.runDir(record.runId) - const path = join(dir, 'run.json') - const tmp = `${path}.tmp` - await writeFile(tmp, JSON.stringify(record, null, 2), 'utf8') - await rename(tmp, path) - } - - private async readLeaseSafe(runId: string): Promise { - const path = join(this.runDir(runId), 'lease.json') - if (!existsSync(path)) return undefined - try { - const content = await readFile(path, 'utf8') - if (!content.trim()) return undefined - return JSON.parse(content) as LeaseFile - } catch { - return undefined - } - } - - private async writeLease(runId: string, lease: LeaseFile | null): Promise { - const path = join(this.runDir(runId), 'lease.json') - const tmp = `${path}.tmp` - await writeFile(tmp, lease ? JSON.stringify(lease) : '', 'utf8') - await rename(tmp, path) - } - - private async readSteps( - runId: string, - opts: { includeFailed?: boolean; includeRunning?: boolean } = {}, - ): Promise { - const path = join(this.runDir(runId), 'steps.jsonl') - if (!existsSync(path)) return [] - const content = await readFile(path, 'utf8') - // Append-only log: later writes for the same stepIndex override earlier - // ones. Walk forward and keep the latest per index. - const latest = new Map() - for (const line of content.split('\n')) { - if (!line) continue - const rec = JSON.parse(line) as StepRecord - latest.set(rec.stepIndex, rec) - } - const out = [...latest.values()].sort((a, b) => a.stepIndex - b.stepIndex) - return out.filter((s) => { - if (s.status === 'completed') return true - if (s.status === 'failed') return opts.includeFailed ?? false - if (s.status === 'running') return opts.includeRunning ?? false - return false - }) - } - - private async appendStep(runId: string, rec: StepRecord): Promise { - await appendFile(join(this.runDir(runId), 'steps.jsonl'), `${JSON.stringify(rec)}\n`, 'utf8') - } - - private async bumpRunUpdated(runId: string, nowIso: string): Promise { - const record = await this.readRun(runId) - record.updatedAt = nowIso - await this.writeRun(record) - } -} diff --git a/src/durable/identity.ts b/src/durable/identity.ts deleted file mode 100644 index ecae7740..00000000 --- a/src/durable/identity.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Identity + canonical-hash helpers for the durable-runs substrate. - * - * Two boundary disciplines: - * - * 1. **Manifest hash** — sha256 over a sorted-key JSON of (projectId, - * scenarioId, task.id, task.intent, task.domain, input). Same hash = - * same run identity. Used to detect "same runId, different inputs." - * - * 2. **Step input hash** — sha256 over a sorted-key JSON of the step's - * input fingerprint. Used to detect drift across replays. - * - * Sorted-key JSON makes hashes deterministic regardless of object insertion - * order. NaN / Infinity / undefined / functions / symbols / class instances - * are rejected — pure JSON only at the boundary, so the hash matches whatever - * the store round-trips. - */ - -import { createHash } from 'node:crypto' - -import type { DurableRunManifest } from './types' - -/** sha256-hex over a JSON-canonicalized value. */ -export function canonicalHash(value: unknown): string { - const json = canonicalJson(value) - return createHash('sha256').update(json).digest('hex') -} - -/** Canonical JSON: object keys sorted lexicographically; arrays preserved. */ -export function canonicalJson(value: unknown): string { - return JSON.stringify(canonicalize(value)) -} - -function canonicalize(value: unknown): unknown { - if (value === null) return null - if (Array.isArray(value)) return value.map(canonicalize) - const t = typeof value - if (t === 'string' || t === 'boolean') return value - if (t === 'number') { - if (!Number.isFinite(value as number)) { - throw new TypeError(`canonicalJson: non-finite number ${String(value)} not serializable`) - } - return value - } - if (t === 'undefined' || t === 'function' || t === 'symbol') { - throw new TypeError(`canonicalJson: ${t} is not JSON-serializable`) - } - if (t === 'bigint') { - // BigInts have no JSON representation. Encode as string; tag for - // disambiguation so consumers can choose to reject or decode. - return { __bigint: String(value) } - } - if (t === 'object') { - const obj = value as Record - // Reject class instances — Date / Error / Map / Set / TypedArray — to - // force callers to project to plain JSON at the boundary. Errors round- - // tripped silently are the #1 source of "looks the same but differs" - // bugs. - const proto = Object.getPrototypeOf(obj) - if (proto !== null && proto !== Object.prototype) { - const ctor = obj.constructor?.name ?? 'unknown' - throw new TypeError( - `canonicalJson: class instance (${ctor}) is not JSON-serializable. Project to plain { ... } at the boundary.`, - ) - } - const keys = Object.keys(obj).sort() - const out: Record = {} - for (const k of keys) out[k] = canonicalize(obj[k]) - return out - } - throw new TypeError(`canonicalJson: unsupported type ${t}`) -} - -/** Hash a DurableRunManifest into the run identity component. */ -export function manifestHash(manifest: DurableRunManifest): string { - return canonicalHash({ - projectId: manifest.projectId, - scenarioId: manifest.scenarioId ?? null, - taskId: manifest.task.id, - taskIntent: manifest.task.intent, - taskDomain: manifest.task.domain, - input: manifest.input, - }) -} - -/** Stable per-step identifier — hash of (runId, position, intent). */ -export function stepId(runId: string, stepIndex: number, intent: string): string { - return canonicalHash({ runId, stepIndex, intent }) -} - -let counter = 0 -/** - * Stable worker id for a single process. Format: `host:pid:rand`. Random - * suffix prevents collisions when the host/pid pair is short-lived (e.g., - * Cloudflare isolates that recycle fast). - */ -export function deriveWorkerId(): string { - const host = process.env.HOSTNAME ?? 'host' - const pid = process.pid ?? 0 - const rand = Math.random().toString(36).slice(2, 10) - counter += 1 - return `${host}:${pid}:${rand}:${counter}` -} diff --git a/src/durable/in-memory-store.ts b/src/durable/in-memory-store.ts deleted file mode 100644 index e51fa321..00000000 --- a/src/durable/in-memory-store.ts +++ /dev/null @@ -1,330 +0,0 @@ -/** - * In-memory DurableRunStore for dev + tests. Single-process. All state lives - * in maps. Lease enforcement is real (Date.now() vs lease deadline) so the - * crash-recovery + multi-worker race tests run identically against this and - * the file-system / D1 stores. - */ - -import { manifestHash } from './identity' -import type { - DurableRunManifest, - DurableRunStore, - EventRecord, - RunHandle, - RunOutcome, - RunRecord, - StepError, - StepKind, - StepRecord, - StreamEventRecord, -} from './types' -import { - DurableRunDivergenceError, - DurableRunInputMismatchError, - DurableRunLeaseHeldError, -} from './types' - -const DEFAULT_LEASE_MS = 30_000 - -interface RunState { - record: RunRecord - steps: Map - events: Map - /** Ordered, replayable event-stream log — `seq` is the array index. */ - streamEvents: StreamEventRecord[] -} - -export class InMemoryDurableRunStore implements DurableRunStore { - private readonly runs = new Map() - /** Override for tests — defaults to Date.now(). */ - public now: () => number = () => Date.now() - - async startOrResume(input: { - runId: string - manifest: DurableRunManifest - workerId: string - leaseMs?: number - }): ReturnType { - const leaseMs = input.leaseMs ?? DEFAULT_LEASE_MS - const hash = manifestHash(input.manifest) - const nowMs = this.now() - const nowIso = new Date(nowMs).toISOString() - const leaseExpiresMs = nowMs + leaseMs - const leaseExpiresAt = new Date(leaseExpiresMs).toISOString() - - let state = this.runs.get(input.runId) - if (!state) { - const record: RunRecord = { - runId: input.runId, - manifestHash: hash, - projectId: input.manifest.projectId, - scenarioId: input.manifest.scenarioId, - status: 'running', - createdAt: nowIso, - updatedAt: nowIso, - leaseHolderId: input.workerId, - leaseExpiresAt, - stepCount: 0, - } - state = { record, steps: new Map(), events: new Map(), streamEvents: [] } - this.runs.set(input.runId, state) - return { run: { ...record }, completedSteps: [], leaseExpiresAt } - } - - if (state.record.manifestHash !== hash) { - throw new DurableRunInputMismatchError( - `runId ${input.runId} exists with a different manifest hash; refuse to corrupt prior steps`, - ) - } - // Lease check — held by another worker with a non-expired lease. - const leaseStillLive = - state.record.leaseHolderId !== undefined && - state.record.leaseHolderId !== input.workerId && - state.record.leaseExpiresAt !== undefined && - new Date(state.record.leaseExpiresAt).getTime() > nowMs - if (leaseStillLive) { - throw new DurableRunLeaseHeldError( - `runId ${input.runId} is leased by ${state.record.leaseHolderId} until ${state.record.leaseExpiresAt}`, - ) - } - // Acquire / renew. - state.record.leaseHolderId = input.workerId - state.record.leaseExpiresAt = leaseExpiresAt - state.record.status = - state.record.status === 'completed' || state.record.status === 'failed' - ? state.record.status - : 'running' - state.record.updatedAt = nowIso - const completed = [...state.steps.values()] - .filter((s) => s.status === 'completed') - .sort((a, b) => a.stepIndex - b.stepIndex) - return { - run: { ...state.record }, - completedSteps: completed.map((s) => ({ ...s })), - leaseExpiresAt, - } - } - - async renewLease(input: { - runId: string - workerId: string - leaseMs?: number - }): Promise<{ ok: boolean; leaseExpiresAt?: string }> { - const state = this.runs.get(input.runId) - if (!state) return { ok: false } - const nowMs = this.now() - if (state.record.leaseHolderId !== input.workerId) { - // Lease lapsed — another worker may have taken over. - if (state.record.leaseExpiresAt && new Date(state.record.leaseExpiresAt).getTime() > nowMs) { - return { ok: false } - } - } - const leaseExpiresMs = nowMs + (input.leaseMs ?? DEFAULT_LEASE_MS) - const leaseExpiresAt = new Date(leaseExpiresMs).toISOString() - state.record.leaseHolderId = input.workerId - state.record.leaseExpiresAt = leaseExpiresAt - state.record.updatedAt = new Date(nowMs).toISOString() - return { ok: true, leaseExpiresAt } - } - - async loadStep(runId: string, stepIndex: number): Promise { - const state = this.runs.get(runId) - return state ? cloneStep(state.steps.get(stepIndex)) : undefined - } - - async beginStep(input: { - runId: string - stepIndex: number - intent: string - kind: StepKind - inputHash: string - }): Promise { - const state = this.requireRun(input.runId) - const nowIso = new Date(this.now()).toISOString() - const prior = state.steps.get(input.stepIndex) - if (prior) { - if (prior.intent !== input.intent) { - throw new DurableRunDivergenceError( - `step ${input.stepIndex}: intent changed across replays ('${prior.intent}' -> '${input.intent}')`, - ) - } - // Begin called again — bump attempts. A prior failed or running step - // can re-execute; a prior completed step would be filtered before begin - // is called (the runner short-circuits on cached results). - prior.attempts += 1 - prior.status = 'running' - prior.startedAt = nowIso - prior.error = undefined - state.record.updatedAt = nowIso - return cloneStep(prior)! - } - const rec: StepRecord = { - runId: input.runId, - stepIndex: input.stepIndex, - intent: input.intent, - kind: input.kind, - inputHash: input.inputHash, - status: 'running', - attempts: 1, - startedAt: nowIso, - } - state.steps.set(input.stepIndex, rec) - state.record.stepCount = Math.max(state.record.stepCount, input.stepIndex + 1) - state.record.updatedAt = nowIso - return cloneStep(rec)! - } - - async completeStep(input: { - runId: string - stepIndex: number - result: unknown - }): Promise { - const state = this.requireRun(input.runId) - const rec = state.steps.get(input.stepIndex) - if (!rec) { - throw new Error( - `durable-runs: completeStep called before beginStep (step ${input.stepIndex})`, - ) - } - const nowIso = new Date(this.now()).toISOString() - rec.status = 'completed' - rec.result = input.result - rec.completedAt = nowIso - rec.error = undefined - state.record.updatedAt = nowIso - return cloneStep(rec)! - } - - async failStep(input: { - runId: string - stepIndex: number - error: StepError - }): Promise { - const state = this.requireRun(input.runId) - const rec = state.steps.get(input.stepIndex) - if (!rec) { - throw new Error(`durable-runs: failStep called before beginStep (step ${input.stepIndex})`) - } - const nowIso = new Date(this.now()).toISOString() - rec.status = 'failed' - rec.error = input.error - rec.completedAt = nowIso - state.record.updatedAt = nowIso - return cloneStep(rec)! - } - - async endRun(input: { - runId: string - workerId: string - status: 'completed' | 'failed' - outcome?: RunOutcome - }): Promise { - const state = this.requireRun(input.runId) - const nowIso = new Date(this.now()).toISOString() - state.record.status = input.status - state.record.outcome = input.outcome - state.record.completedAt = nowIso - state.record.updatedAt = nowIso - // Release lease iff caller still holds it. - if (state.record.leaseHolderId === input.workerId) { - state.record.leaseHolderId = undefined - state.record.leaseExpiresAt = undefined - } - return { ...state.record } - } - - async emitEvent(input: { - runId: string - key: string - payload: unknown - }): ReturnType { - const state = this.requireRun(input.runId) - const existing = state.events.get(input.key) - if (existing) { - return { accepted: false, record: { ...existing } } - } - const rec: EventRecord = { - runId: input.runId, - key: input.key, - payload: input.payload, - emittedAt: new Date(this.now()).toISOString(), - } - state.events.set(input.key, rec) - return { accepted: true, record: { ...rec } } - } - - async loadEvent(runId: string, key: string): Promise { - const state = this.runs.get(runId) - if (!state) return undefined - const rec = state.events.get(key) - return rec ? { ...rec } : undefined - } - - async appendStreamEvent(input: { - runId: string - eventId: string - payload: unknown - }): ReturnType { - const state = this.requireRun(input.runId) - const existing = state.streamEvents.find((e) => e.eventId === input.eventId) - if (existing) return { accepted: false, record: { ...existing } } - const rec: StreamEventRecord = { - runId: input.runId, - seq: state.streamEvents.length, - eventId: input.eventId, - payload: input.payload, - appendedAt: new Date(this.now()).toISOString(), - } - state.streamEvents.push(rec) - return { accepted: true, record: { ...rec } } - } - - async readStreamEvents( - runId: string, - afterSeq?: number, - ): Promise> { - const state = this.runs.get(runId) - if (!state) return [] - const cutoff = afterSeq ?? -1 - return state.streamEvents.filter((e) => e.seq > cutoff).map((e) => ({ ...e })) - } - - async setRunHandle(input: { runId: string; handle: RunHandle }): Promise { - const state = this.requireRun(input.runId) - state.record.handle = { ...input.handle } - state.record.updatedAt = new Date(this.now()).toISOString() - } - - async close(): Promise { - this.runs.clear() - } - - // ── test helpers ─────────────────────────────────────────────────── - /** @internal — used by tests to inspect lease metadata. */ - _inspect(runId: string): RunRecord | undefined { - const s = this.runs.get(runId) - return s ? { ...s.record } : undefined - } - - /** @internal — used by tests to simulate lease expiry. */ - _expireLease(runId: string): void { - const s = this.runs.get(runId) - if (s) { - s.record.leaseHolderId = undefined - s.record.leaseExpiresAt = undefined - } - } - - private requireRun(runId: string): RunState { - const s = this.runs.get(runId) - if (!s) { - throw new Error(`durable-runs: run ${runId} not found (must call startOrResume first)`) - } - return s - } -} - -function cloneStep(rec: StepRecord | undefined): StepRecord | undefined { - if (!rec) return undefined - return { ...rec, error: rec.error ? { ...rec.error } : undefined } -} diff --git a/src/durable/index.ts b/src/durable/index.ts index a89e7aae..e1d32509 100644 --- a/src/durable/index.ts +++ b/src/durable/index.ts @@ -1,92 +1,29 @@ /** - * Durable-run substrate for `@tangle-network/agent-runtime`. + * Turn-lifecycle helpers for `@tangle-network/agent-runtime`. * - * Public surface — what consumers import. Implementations live in - * sibling files (in-memory / file-system / D1). Runner + ctx live in - * runner.ts. Identity helpers live in identity.ts. + * Execution state — long-running agent execution, reconnect, replay, + * dedup — lives in the substrate (`@tangle-network/sandbox` SDK + + * orchestrator). agent-runtime owns the layer above: * - * See `./types.ts` for the full type contract and concurrency model. + * - `AgentExecutionHandle` — the typed pointer products persist so a + * reconnect lands on the same substrate execution instead of starting + * a second prompt. + * - `ChatTurnEngine` — the framework-neutral turn lifecycle: NDJSON + * framing, `session.run.*` envelope, persist / post-process / trace- + * flush hook ordering. Wraps any producer; the producer talks to the + * substrate. */ -// ── Durable chat-turn engine ────────────────────────────────────────── -// Framework-neutral chat-turn orchestrator: durable turn + NDJSON -// streaming + session.run.* lifecycle + product persist/post-process -// hooks. Every product chat handler routes through this. +// ── Chat-turn engine ────────────────────────────────────────────────── export type { ChatStreamEvent, ChatTurnHooks, ChatTurnIdentity, + ChatTurnProducer, ChatTurnResult, RunChatTurnInput, } from './chat-engine' -export { DurableChatTurnEngine, durableChatTurnEngine } from './chat-engine' -export type { D1DatabaseLike, D1PreparedStatementLike } from './d1-store' -export { D1DurableRunStore } from './d1-store' -export { FileSystemDurableRunStore } from './file-system-store' -export { canonicalHash, canonicalJson, deriveWorkerId, manifestHash, stepId } from './identity' -export { InMemoryDurableRunStore } from './in-memory-store' -export type { DurableContext, RunDurableInput, RunDurableResult } from './runner' -export { runDurable } from './runner' -// Canonical D1 schema string + current version. Consumers wire via -// await env.DB.exec(DURABLE_SCHEMA_SQL) -// during one-time bootstrap. `src/durable/schema.sql` is the source of -// truth; `schema.ts` is the build-bundled string that ships in dist/. -export { DURABLE_SCHEMA_SQL, DURABLE_SCHEMA_VERSION } from './schema' -// ── Durable-run supervisor — cross-worker / cross-DO durability ─────── -// Relocates the durability boundary off the ephemeral worker isolate: the -// supervisor drains a run's event stream into the substrate's own log, and -// a fresh supervisor re-attaches from the persisted cursor instead of -// re-prompting. SessionSupervisorDO hosts it on a Cloudflare Durable Object. -export type { - DurableObjectStateLike, - DurableObjectStorageLike, - SessionSupervisorDO, - SupervisorHostConfig, -} from './session-supervisor-do' -export { createSessionSupervisorDO } from './session-supervisor-do' -export type { - RunSupervisorOptions, - SandboxReconnectAdapter, - SupervisedEvent, - SupervisedRunHandle, - SupervisedRunMode, -} from './supervisor' -export { runSupervisedTurn } from './supervisor' -// ── Durable turn primitive ──────────────────────────────────────────── -// Streaming, backend-agnostic, checkpoint+replay durable turn. The single -// reusable primitive every product chat handler routes through. -export type { - DurableTurnHandle, - DurableTurnProducer, - RunDurableTurnOptions, -} from './turn' -export { runDurableTurn } from './turn' -export type { - DurableRunManifest, - DurableRunStore, - EventRecord, - RunHandle, - RunOutcome, - RunRecord, - RunStatus, - StepError, - StepKind, - StepRecord, - StepStatus, - StreamEventRecord, -} from './types' -export { - DurableAwaitEventTimeoutError, - DurableRunDivergenceError, - DurableRunError, - DurableRunInputMismatchError, - DurableRunLeaseHeldError, -} from './types' - -// ── Cloudflare Workflows integration ────────────────────────────────── -export type { - RunOnWorkflowStepInput, - WorkflowStepConfig, - WorkflowStepLike, -} from './workflows' -export { runOnWorkflowStep } from './workflows' +export { ChatTurnEngine, chatTurnEngine } from './chat-engine' +// ── Execution-continuity contract ───────────────────────────────────── +export type { AgentExecutionHandle, ReconnectableAgentStream } from './execution-handle' +export { deriveExecutionId } from './execution-handle' diff --git a/src/durable/runner.ts b/src/durable/runner.ts deleted file mode 100644 index 1b4da645..00000000 --- a/src/durable/runner.ts +++ /dev/null @@ -1,357 +0,0 @@ -/** - * Durable runner — wraps a user-supplied async function in checkpoint / - * resume / lease semantics. The user writes plain async code, awaiting - * `ctx.step(intent, fn)` boundaries. On worker crash, the next caller with - * the same `runId` skips completed steps and resumes from the first unfinished - * one. - * - * Invariants: - * - * - Step positions are derived from a monotonic counter on the ctx. The - * same intent at position N is the same step across replays. If the user - * reorders steps, position N changes intent and we raise - * DurableRunDivergenceError fail-loud. - * - * - `ctx.now()` and `ctx.uuid()` are checkpointed as zero-input logic steps - * with kind='deterministic'. On replay they return the recorded value. - * - * - `awaitEvent` writes a 'event' step that records the event payload on - * first awaited completion. On replay, the cached payload returns - * synchronously. If the event has not been emitted and the runner is in - * a fresh execution, it polls the store until timeout. - * - * - Lease renewal happens on a wall-clock interval (every leaseMs/3). If - * the store reports a lost lease, the runner aborts the current step - * execution and throws — letting whichever worker holds the lease pick - * up. Committed steps survive. - */ - -import { canonicalHash, deriveWorkerId } from './identity' -import type { - DurableRunManifest, - DurableRunStore, - RunOutcome, - RunRecord, - StepKind, - StepRecord, -} from './types' -import { DurableAwaitEventTimeoutError, DurableRunDivergenceError } from './types' - -export interface DurableContext { - readonly runId: string - readonly projectId: string - readonly scenarioId?: string - - /** - * Execute a checkpointed step. The step is identified by its **position** - * (monotonic counter on this ctx); `intent` is a human-readable label that - * must stay stable across replays. - * - * On first execution: runs `fn`, records the result, returns it. - * On replay: returns the recorded result WITHOUT calling `fn`. - * - * The `inputFingerprint` (optional) lets the runner detect "same intent, - * different inputs" — it gets hashed and compared. If you don't supply - * one, drift is allowed (input not checked). - */ - step( - intent: string, - fn: () => Promise, - opts?: { kind?: StepKind; inputFingerprint?: unknown }, - ): Promise - - /** Race-free first-emit-wins event wait. */ - awaitEvent(key: string, opts?: { timeoutMs?: number; pollMs?: number }): Promise - - /** Emit an event. First emit wins. Subsequent emits no-op. */ - emitEvent(key: string, payload: unknown): Promise<{ accepted: boolean }> - - /** Deterministic clock — checkpointed once per call. */ - now(): Promise - - /** Deterministic uuid — checkpointed once per call. */ - uuid(): Promise -} - -const DEFAULT_LEASE_MS = 30_000 -const DEFAULT_AWAIT_POLL_MS = 250 - -export interface RunDurableInput { - runId: string - manifest: DurableRunManifest - store: DurableRunStore - workerId?: string - leaseMs?: number - /** Total time budget for the run. Used for awaitEvent timeouts; runner - * itself doesn't kill long-running steps (the step fn must respect - * AbortSignal if it cares). */ - signal?: AbortSignal - taskFn: (ctx: DurableContext) => Promise - /** Default outcome on successful completion. */ - defaultOutcome?: RunOutcome -} - -export interface RunDurableResult { - result: TResult - record: RunRecord - /** All steps captured this run (replayed + freshly executed). */ - steps: ReadonlyArray -} - -export async function runDurable( - input: RunDurableInput, -): Promise> { - const workerId = input.workerId ?? deriveWorkerId() - const leaseMs = input.leaseMs ?? DEFAULT_LEASE_MS - const { completedSteps } = await input.store.startOrResume({ - runId: input.runId, - manifest: input.manifest, - workerId, - leaseMs, - }) - - // Pre-compute a position-indexed view of prior steps for O(1) replay - // lookups. We DON'T trust that the store returned them in order — sort. - const priorByIndex = new Map() - for (const s of completedSteps) priorByIndex.set(s.stepIndex, s) - - const collected: StepRecord[] = [...completedSteps].sort((a, b) => a.stepIndex - b.stepIndex) - - // Lease renewal heartbeat — best-effort. Errors are surfaced via - // `leaseLost` so steps can short-circuit. - let leaseLost = false - const heartbeatIntervalMs = Math.max(1_000, Math.floor(leaseMs / 3)) - const heartbeat = setInterval(() => { - void input.store - .renewLease({ runId: input.runId, workerId, leaseMs }) - .then((res) => { - if (!res.ok) leaseLost = true - }) - .catch(() => { - leaseLost = true - }) - }, heartbeatIntervalMs) - // unref() so the heartbeat doesn't keep the process alive on test exit. - if (typeof heartbeat.unref === 'function') heartbeat.unref() - - let positionCounter = 0 - - const ctx: DurableContext = { - runId: input.runId, - projectId: input.manifest.projectId, - scenarioId: input.manifest.scenarioId, - async step( - intent: string, - fn: () => Promise, - opts?: { kind?: StepKind; inputFingerprint?: unknown }, - ): Promise { - checkAbortAndLease(input.signal, leaseLost) - const stepIndex = positionCounter++ - const prior = priorByIndex.get(stepIndex) - const inputHash = - opts?.inputFingerprint !== undefined ? canonicalHash(opts.inputFingerprint) : '' - if (prior && prior.status === 'completed') { - // Replay path — return cached result. - if (prior.intent !== intent) { - throw new DurableRunDivergenceError( - `step ${stepIndex}: intent changed across replays ('${prior.intent}' -> '${intent}')`, - ) - } - return prior.result as T - } - // Begin a fresh attempt (either net-new or retry of a failed step). - const begun = await input.store.beginStep({ - runId: input.runId, - stepIndex, - intent, - kind: opts?.kind ?? 'logic', - inputHash, - }) - try { - const result = await fn() - const completed = await input.store.completeStep({ - runId: input.runId, - stepIndex, - result, - }) - upsertCollected(collected, completed) - priorByIndex.set(stepIndex, completed) - return result - } catch (err) { - const failed = await input.store.failStep({ - runId: input.runId, - stepIndex, - error: toStepError(err), - }) - upsertCollected(collected, failed) - priorByIndex.set(stepIndex, failed) - throw err - } finally { - // The `begun` reference suppresses "unused" warnings without an - // eslint-disable comment. - void begun - } - }, - async awaitEvent(key: string, opts?: { timeoutMs?: number; pollMs?: number }): Promise { - const stepIndex = positionCounter++ - const prior = priorByIndex.get(stepIndex) - if (prior && prior.status === 'completed') { - if (prior.intent !== `event:${key}`) { - throw new DurableRunDivergenceError( - `step ${stepIndex}: awaitEvent key changed across replays`, - ) - } - return prior.result as T - } - const beginAt = Date.now() - const timeoutMs = opts?.timeoutMs ?? 60_000 - const pollMs = opts?.pollMs ?? DEFAULT_AWAIT_POLL_MS - await input.store.beginStep({ - runId: input.runId, - stepIndex, - intent: `event:${key}`, - kind: 'event', - inputHash: '', - }) - try { - for (;;) { - checkAbortAndLease(input.signal, leaseLost) - const evt = await input.store.loadEvent(input.runId, key) - if (evt) { - const completed = await input.store.completeStep({ - runId: input.runId, - stepIndex, - result: evt.payload, - }) - upsertCollected(collected, completed) - priorByIndex.set(stepIndex, completed) - return evt.payload as T - } - if (Date.now() - beginAt > timeoutMs) { - const err = new DurableAwaitEventTimeoutError( - `awaitEvent('${key}') timed out after ${timeoutMs}ms`, - ) - const failed = await input.store.failStep({ - runId: input.runId, - stepIndex, - error: toStepError(err), - }) - upsertCollected(collected, failed) - priorByIndex.set(stepIndex, failed) - throw err - } - await sleep(pollMs, input.signal) - } - } catch (err) { - // Any non-timeout error: mark failed (if not already), surface. - if (!(err instanceof DurableAwaitEventTimeoutError)) { - const existing = priorByIndex.get(stepIndex) - if (!existing || existing.status !== 'failed') { - const failed = await input.store.failStep({ - runId: input.runId, - stepIndex, - error: toStepError(err), - }) - upsertCollected(collected, failed) - priorByIndex.set(stepIndex, failed) - } - } - throw err - } - }, - async emitEvent(key: string, payload: unknown): Promise<{ accepted: boolean }> { - const res = await input.store.emitEvent({ runId: input.runId, key, payload }) - return { accepted: res.accepted } - }, - async now(): Promise { - const v = await this.step(`deterministic:now`, async () => new Date().toISOString(), { - kind: 'deterministic', - }) - return new Date(v) - }, - async uuid(): Promise { - return this.step(`deterministic:uuid`, async () => cryptoRandomUuid(), { - kind: 'deterministic', - }) - }, - } - - try { - const result = await input.taskFn(ctx) - const finalRecord = await input.store.endRun({ - runId: input.runId, - workerId, - status: 'completed', - outcome: input.defaultOutcome, - }) - return { result, record: finalRecord, steps: collected } - } catch (err) { - const finalRecord = await input.store.endRun({ - runId: input.runId, - workerId, - status: 'failed', - outcome: { - ...input.defaultOutcome, - notes: err instanceof Error ? err.message : String(err), - }, - }) - void finalRecord - throw err - } finally { - clearInterval(heartbeat) - } -} - -function checkAbortAndLease(signal: AbortSignal | undefined, leaseLost: boolean): void { - if (signal?.aborted) throw signal.reason ?? new Error('aborted') - if (leaseLost) throw new Error('durable-runs: lease lost; another worker has taken over this run') -} - -function upsertCollected(list: StepRecord[], rec: StepRecord): void { - const i = list.findIndex((s) => s.stepIndex === rec.stepIndex) - if (i === -1) list.push(rec) - else list[i] = rec -} - -function toStepError(err: unknown): { message: string; code?: string; stack?: string } { - if (err instanceof Error) { - return { - message: err.message, - code: (err as { code?: string }).code, - stack: err.stack, - } - } - return { message: String(err) } -} - -function sleep(ms: number, signal?: AbortSignal): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(signal.reason ?? new Error('aborted')) - return - } - const t = setTimeout(() => { - signal?.removeEventListener('abort', onAbort) - resolve() - }, ms) - const onAbort = () => { - clearTimeout(t) - reject(signal?.reason ?? new Error('aborted')) - } - signal?.addEventListener('abort', onAbort, { once: true }) - }) -} - -function cryptoRandomUuid(): string { - // Defensive: globalThis.crypto.randomUUID may not be available in older - // Node — fall back to a manual v4 derivation. - if (typeof globalThis.crypto?.randomUUID === 'function') { - return globalThis.crypto.randomUUID() - } - const bytes = new Uint8Array(16) - for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256) - bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40 - bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80 - const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('') - return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}` -} diff --git a/src/durable/schema.sql b/src/durable/schema.sql deleted file mode 100644 index 1cc8aabc..00000000 --- a/src/durable/schema.sql +++ /dev/null @@ -1,96 +0,0 @@ --- Durable-run substrate — versioned schema for D1 / SQLite. --- --- Apply once per database. Subsequent migrations append; never rewrite a --- prior version. See `durable_schema_info` for the migration trail. --- --- Concurrency notes for D1: --- - SQLite supports UNIQUE constraints for first-emit-wins (`durable_events` --- PK is (run_id, key) — duplicate insert raises, caller treats as "already --- emitted"). --- - Lease takeover happens via a conditional UPDATE: we only claim the lease --- if (lease_holder_id IS NULL OR lease_expires_at < :now) — atomic under --- SQLite's row-level locking. --- - All timestamps stored as ISO-8601 TEXT for cross-platform consistency. --- - `result_json` / `error_json` / `outcome_json` / `payload_json` are --- JSON-encoded TEXT; the application enforces canonical-JSON discipline at --- the boundary so the store stays type-agnostic. - -CREATE TABLE IF NOT EXISTS durable_schema_info ( - version INTEGER PRIMARY KEY, - applied_at TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS durable_runs ( - run_id TEXT PRIMARY KEY, - manifest_hash TEXT NOT NULL, - project_id TEXT NOT NULL, - scenario_id TEXT, - status TEXT NOT NULL CHECK (status IN ('pending','running','completed','failed','suspended')), - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - completed_at TEXT, - lease_holder_id TEXT, - lease_expires_at TEXT, - outcome_json TEXT, - step_count INTEGER NOT NULL DEFAULT 0 -); - -CREATE INDEX IF NOT EXISTS idx_durable_runs_project_status ON durable_runs(project_id, status); -CREATE INDEX IF NOT EXISTS idx_durable_runs_lease_expires ON durable_runs(lease_expires_at); - -CREATE TABLE IF NOT EXISTS durable_steps ( - run_id TEXT NOT NULL, - step_index INTEGER NOT NULL, - intent TEXT NOT NULL, - kind TEXT NOT NULL, - input_hash TEXT NOT NULL DEFAULT '', - status TEXT NOT NULL CHECK (status IN ('pending','running','completed','failed')), - attempts INTEGER NOT NULL DEFAULT 0, - result_json TEXT, - error_json TEXT, - started_at TEXT, - completed_at TEXT, - PRIMARY KEY (run_id, step_index) -); - -CREATE INDEX IF NOT EXISTS idx_durable_steps_status ON durable_steps(run_id, status); - -CREATE TABLE IF NOT EXISTS durable_events ( - run_id TEXT NOT NULL, - key TEXT NOT NULL, - payload_json TEXT, - emitted_at TEXT NOT NULL, - PRIMARY KEY (run_id, key) -); - -INSERT OR IGNORE INTO durable_schema_info (version, applied_at) -VALUES (1, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')); - --- ── Migration v2 — durable event-stream log + run handle ─────────────── --- Run once on a database created at v1. `ALTER TABLE` is not idempotent; the --- version trail in `durable_schema_info` is how migrations are sequenced — --- never by blind re-execution of this block. --- --- - `durable_stream_events` is the ordered, replayable per-run event log. --- `seq` is the store-assigned monotonic cursor; the UNIQUE index on --- (run_id, event_id) makes appends idempotent — a reconnecting adapter --- that re-yields a boundary event cannot double-log it. --- - `durable_runs.handle_json` is the pointer (sandbox + substrate run id + --- cursor) a fresh supervisor re-attaches by. - -ALTER TABLE durable_runs ADD COLUMN handle_json TEXT; - -CREATE TABLE IF NOT EXISTS durable_stream_events ( - run_id TEXT NOT NULL, - seq INTEGER NOT NULL, - event_id TEXT NOT NULL, - payload_json TEXT, - appended_at TEXT NOT NULL, - PRIMARY KEY (run_id, seq) -); - -CREATE UNIQUE INDEX IF NOT EXISTS idx_durable_stream_events_event_id - ON durable_stream_events(run_id, event_id); - -INSERT OR IGNORE INTO durable_schema_info (version, applied_at) -VALUES (2, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')); diff --git a/src/durable/schema.ts b/src/durable/schema.ts deleted file mode 100644 index d4a43d70..00000000 --- a/src/durable/schema.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * The durable-runs SQL schema as a string constant. Inlined so consumers - * (Cloudflare Workers via D1) can apply it without bundling a `.sql` file: - * - * import { DURABLE_SCHEMA_SQL } from '@tangle-network/agent-runtime' - * await env.DB.exec(DURABLE_SCHEMA_SQL) - * - * The canonical source is `src/durable/schema.sql` — this string MUST stay - * byte-identical to it. The build does not copy `.sql` files into `dist/`, - * so the constant is the only path consumers have. A unit test asserts the - * two stay in sync (`durable-schema.test.ts`). - * - * `DURABLE_SCHEMA_VERSION` reflects the latest migration version applied by - * this string. Bump it on every backwards-incompatible change AND add a new - * migration entry to durable_schema_info instead of mutating prior rows. - */ - -export const DURABLE_SCHEMA_VERSION = 2 - -export const DURABLE_SCHEMA_SQL = `-- Durable-run substrate — versioned schema for D1 / SQLite. --- --- Apply once per database. Subsequent migrations append; never rewrite a --- prior version. See \`durable_schema_info\` for the migration trail. --- --- Concurrency notes for D1: --- - SQLite supports UNIQUE constraints for first-emit-wins (\`durable_events\` --- PK is (run_id, key) — duplicate insert raises, caller treats as "already --- emitted"). --- - Lease takeover happens via a conditional UPDATE: we only claim the lease --- if (lease_holder_id IS NULL OR lease_expires_at < :now) — atomic under --- SQLite's row-level locking. --- - All timestamps stored as ISO-8601 TEXT for cross-platform consistency. --- - \`result_json\` / \`error_json\` / \`outcome_json\` / \`payload_json\` are --- JSON-encoded TEXT; the application enforces canonical-JSON discipline at --- the boundary so the store stays type-agnostic. - -CREATE TABLE IF NOT EXISTS durable_schema_info ( - version INTEGER PRIMARY KEY, - applied_at TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS durable_runs ( - run_id TEXT PRIMARY KEY, - manifest_hash TEXT NOT NULL, - project_id TEXT NOT NULL, - scenario_id TEXT, - status TEXT NOT NULL CHECK (status IN ('pending','running','completed','failed','suspended')), - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - completed_at TEXT, - lease_holder_id TEXT, - lease_expires_at TEXT, - outcome_json TEXT, - step_count INTEGER NOT NULL DEFAULT 0 -); - -CREATE INDEX IF NOT EXISTS idx_durable_runs_project_status ON durable_runs(project_id, status); -CREATE INDEX IF NOT EXISTS idx_durable_runs_lease_expires ON durable_runs(lease_expires_at); - -CREATE TABLE IF NOT EXISTS durable_steps ( - run_id TEXT NOT NULL, - step_index INTEGER NOT NULL, - intent TEXT NOT NULL, - kind TEXT NOT NULL, - input_hash TEXT NOT NULL DEFAULT '', - status TEXT NOT NULL CHECK (status IN ('pending','running','completed','failed')), - attempts INTEGER NOT NULL DEFAULT 0, - result_json TEXT, - error_json TEXT, - started_at TEXT, - completed_at TEXT, - PRIMARY KEY (run_id, step_index) -); - -CREATE INDEX IF NOT EXISTS idx_durable_steps_status ON durable_steps(run_id, status); - -CREATE TABLE IF NOT EXISTS durable_events ( - run_id TEXT NOT NULL, - key TEXT NOT NULL, - payload_json TEXT, - emitted_at TEXT NOT NULL, - PRIMARY KEY (run_id, key) -); - -INSERT OR IGNORE INTO durable_schema_info (version, applied_at) -VALUES (1, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')); - --- ── Migration v2 — durable event-stream log + run handle ─────────────── --- Run once on a database created at v1. \`ALTER TABLE\` is not idempotent; the --- version trail in \`durable_schema_info\` is how migrations are sequenced — --- never by blind re-execution of this block. --- --- - \`durable_stream_events\` is the ordered, replayable per-run event log. --- \`seq\` is the store-assigned monotonic cursor; the UNIQUE index on --- (run_id, event_id) makes appends idempotent — a reconnecting adapter --- that re-yields a boundary event cannot double-log it. --- - \`durable_runs.handle_json\` is the pointer (sandbox + substrate run id + --- cursor) a fresh supervisor re-attaches by. - -ALTER TABLE durable_runs ADD COLUMN handle_json TEXT; - -CREATE TABLE IF NOT EXISTS durable_stream_events ( - run_id TEXT NOT NULL, - seq INTEGER NOT NULL, - event_id TEXT NOT NULL, - payload_json TEXT, - appended_at TEXT NOT NULL, - PRIMARY KEY (run_id, seq) -); - -CREATE UNIQUE INDEX IF NOT EXISTS idx_durable_stream_events_event_id - ON durable_stream_events(run_id, event_id); - -INSERT OR IGNORE INTO durable_schema_info (version, applied_at) -VALUES (2, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')); -` diff --git a/src/durable/session-supervisor-do.ts b/src/durable/session-supervisor-do.ts deleted file mode 100644 index e1f6a8ab..00000000 --- a/src/durable/session-supervisor-do.ts +++ /dev/null @@ -1,178 +0,0 @@ -/** - * `SessionSupervisorDO` — the Cloudflare Durable Object host for - * `runSupervisedTurn`. - * - * A stateless Worker isolate is the wrong place to own a 15-minute run: it - * dies on a deploy roll or CPU limit. A Durable Object is addressable by - * session id and survives across requests — it is the right home for the - * supervisor. This host is deliberately thin: all the durability logic lives - * in the platform-agnostic `runSupervisedTurn`; the DO only hosts it and - * uses an `alarm()` to re-attach a run the response stream abandoned. - * - * - `fetch` resolves the run, records it, arms the orphan-check alarm, and - * streams the supervised events back. If the client disconnects, the - * supervisor stops being pulled and its short lease lapses. - * - `alarm()` is the recovery mechanism: it finds a recorded-but-unfinished - * run and re-drives `runSupervisedTurn` headlessly to completion (events - * land in the durable log; a later `fetch` replays them). A run still held - * by a live `fetch` raises `DurableRunLeaseHeldError` — not orphaned, so - * the alarm just re-arms. - * - * Structural CF types (`DurableObjectStateLike`) are defined locally so - * agent-runtime keeps no dependency on `@cloudflare/workers-types` — the same - * discipline as `D1DatabaseLike` in `d1-store.ts`. - */ - -import type { RunSupervisorOptions } from './supervisor' -import { runSupervisedTurn } from './supervisor' -import { DurableRunLeaseHeldError } from './types' - -/** Minimal Durable Object storage surface this host uses. Compatible with - * Cloudflare's `DurableObjectStorage`. */ -export interface DurableObjectStorageLike { - get(key: string): Promise - put(key: string, value: T): Promise - delete(key: string): Promise - /** Schedule the next `alarm()` invocation at an epoch-ms time. */ - setAlarm(scheduledTime: number): Promise -} - -/** Minimal Durable Object state surface — the `state` ctor argument. */ -export interface DurableObjectStateLike { - storage: DurableObjectStorageLike -} - -/** - * Product-supplied wiring for the host. `resolveRun` / `resolveOrphan` build - * the supervisor inputs (store, adapter, manifest) — the host owns no - * product policy. - */ -export interface SupervisorHostConfig { - /** Build supervisor inputs for an incoming request. `undefined` → 404. */ - resolveRun( - request: Request, - env: TEnv, - state: DurableObjectStateLike, - ): Promise | undefined> - /** Rebuild supervisor inputs for an orphan re-attach, from the recorded - * runId. `undefined` → the run is untrackable; the host stops tracking it. */ - resolveOrphan( - runId: string, - env: TEnv, - state: DurableObjectStateLike, - ): Promise | undefined> - /** Serialize one event into a response-stream chunk (an SSE or NDJSON - * line — the product owns the framing). */ - encodeEvent(event: TEvent): string - /** Delay before the orphan-check alarm fires. Default 60_000. */ - orphanCheckMs?: number - /** Time source — tests pin this. */ - now?: () => number -} - -/** The host instance surface — what a Cloudflare DO runtime invokes. */ -export interface SessionSupervisorDO { - fetch(request: Request): Promise - alarm(): Promise -} - -/** DO-storage key under which the host records the in-flight run id, so the - * orphan-check `alarm()` can find a run a dropped response stream left behind. */ -export const ACTIVE_RUN_KEY = 'agent-runtime:active-run-id' - -/** Drain a supervised stream without a consumer — events land in the durable - * log via the supervisor; nothing forwards them. */ -async function drainHeadless(stream: AsyncGenerator): Promise { - let next = await stream.next() - while (!next.done) next = await stream.next() -} - -/** - * Build the `SessionSupervisorDO` class for a product. Export the result from - * the Worker entrypoint and bind it in `wrangler.toml`: - * - * export const SessionSupervisor = createSessionSupervisorDO(config) - * - * # wrangler.toml - * [[durable_objects.bindings]] - * name = "SESSION_SUPERVISOR" - * class_name = "SessionSupervisor" - * [[migrations]] - * tag = "v1" - * new_classes = ["SessionSupervisor"] - */ -export function createSessionSupervisorDO( - config: SupervisorHostConfig, -): new ( - state: DurableObjectStateLike, - env: TEnv, -) => SessionSupervisorDO { - const orphanCheckMs = config.orphanCheckMs ?? 60_000 - const now = config.now ?? (() => Date.now()) - - return class implements SessionSupervisorDO { - constructor( - private readonly state: DurableObjectStateLike, - private readonly env: TEnv, - ) {} - - async fetch(request: Request): Promise { - const opts = await config.resolveRun(request, this.env, this.state) - if (!opts) return new Response('no run for this request', { status: 404 }) - - // Record the run + arm the orphan check before streaming, so a crash - // mid-stream still leaves a recovery trail. - await this.state.storage.put(ACTIVE_RUN_KEY, opts.runId) - await this.state.storage.setAlarm(now() + orphanCheckMs) - - const supervised = runSupervisedTurn(opts) - const storage = this.state.storage - const encoder = new TextEncoder() - const body = new ReadableStream({ - async pull(controller) { - try { - const next = await supervised.stream.next() - if (next.done) { - await storage.delete(ACTIVE_RUN_KEY) - controller.close() - return - } - controller.enqueue(encoder.encode(config.encodeEvent(next.value))) - } catch (err) { - controller.error(err instanceof Error ? err : new Error(String(err))) - } - }, - }) - return new Response(body, { - headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' }, - }) - } - - async alarm(): Promise { - const runId = await this.state.storage.get(ACTIVE_RUN_KEY) - if (!runId) return - - const opts = await config.resolveOrphan(runId, this.env, this.state) - if (!opts) { - await this.state.storage.delete(ACTIVE_RUN_KEY) - return - } - - try { - // Re-drive the run headlessly. startOrResume inside resolves the - // path: replay (already done), resume (orphaned + in-flight), or a - // fresh run. Events land in the durable log regardless. - await drainHeadless(runSupervisedTurn(opts).stream) - await this.state.storage.delete(ACTIVE_RUN_KEY) - } catch (err) { - if (err instanceof DurableRunLeaseHeldError) { - // A live fetch still owns the run — not orphaned. Re-check later. - await this.state.storage.setAlarm(now() + orphanCheckMs) - return - } - // The run failed for real — stop tracking it. - await this.state.storage.delete(ACTIVE_RUN_KEY) - } - } - } -} diff --git a/src/durable/supervisor.ts b/src/durable/supervisor.ts deleted file mode 100644 index ee28f45a..00000000 --- a/src/durable/supervisor.ts +++ /dev/null @@ -1,229 +0,0 @@ -/** - * `runSupervisedTurn` — relocates the durability boundary off the ephemeral - * worker isolate. - * - * `runDurableTurn` replays a *completed* turn; an interrupted turn re-runs. - * `runSupervisedTurn` closes that gap for sandbox-backed runs: the sandbox - * container is orchestrator-managed and outlives the worker, so instead of - * re-prompting, a fresh supervisor re-attaches to the in-flight substrate run - * and resumes draining its event stream. - * - * Durability is owned by the substrate, not hoped-for from the sandbox. The - * supervisor drains every event into the store's stream log as it flows - * (`appendStreamEvent`), persists the reconnect pointer the instant the - * substrate yields it (`setRunHandle`), and heartbeats the lease. A fresh - * supervisor reads the log for its cursor and calls `adapter.attach` to - * resume strictly after it — the append's idempotency on `eventId` dedups - * the reconnect seam, so no event is lost and none is delivered twice. - * - * The platform-agnostic core is here; `SessionSupervisorDO` hosts it on a - * Cloudflare Durable Object. The reconnect glue is one typed contract — - * `SandboxReconnectAdapter` — implemented once per substrate, never per - * product. - */ - -import { canonicalHash } from './identity' -import type { DurableRunManifest, DurableRunStore, RunHandle, RunRecord } from './types' - -/** One event drained from a supervised run. */ -export interface SupervisedEvent { - /** Stable substrate id — the dedup key and the reconnect cursor. */ - eventId: string - payload: TEvent - /** - * The substrate run handle, carried on the first frame(s) once the run id - * is known. The supervisor persists it so a fresh supervisor can re-attach. - * Omit on later frames; the last non-undefined handle wins. - */ - handle?: RunHandle -} - -/** - * Product-supplied glue to a reconnectable substrate run. The dangerous - * reconnect logic — re-attaching to a live distributed run — lives behind - * this one typed contract: implement it once per substrate (the sandbox SDK, - * etc.), never per product. - * - * Conformance (asserted by `supervisor.test.ts`): - * - `start()` yields the run's events; at least one early event carries a - * `handle` with `status: 'running'` and a defined `runId`. - * - `attach(handle, afterEventId)` yields only events strictly after - * `afterEventId`, and terminates cleanly when the run has no more. - * - `eventId`s are unique within a run. - */ -export interface SandboxReconnectAdapter { - /** Begin a fresh substrate run. */ - start(): AsyncIterable> - /** - * Re-attach to an in-flight run, resuming strictly after `afterEventId` - * (`undefined` → from the first event). - */ - attach( - handle: RunHandle, - afterEventId: string | undefined, - ): AsyncIterable> -} - -/** How the supervised turn resolved. */ -export type SupervisedRunMode = 'fresh' | 'resumed' | 'replayed' - -export interface RunSupervisorOptions { - store: DurableRunStore - /** Stable per-turn run id — the same id on a retry is what enables both - * replay (completed turn) and resume (in-flight turn). */ - runId: string - manifest: DurableRunManifest - /** Stable per-isolate worker id. */ - workerId: string - adapter: SandboxReconnectAdapter - /** Lease window in ms. Default 60_000 — deliberately short: the heartbeat - * keeps an actively-draining supervisor's lease alive, so an abandoned - * supervisor's lease lapses fast and a fresh supervisor can take over. */ - leaseMs?: number - /** Renew the lease at most this often while draining. Default 30_000 — - * must be below `leaseMs` or an active drain loses its own lease. */ - heartbeatMs?: number - /** Human-readable step label. Default `turn`. */ - intent?: string - /** Time source override — tests pin this for deterministic heartbeats. */ - now?: () => number -} - -export interface SupervisedRunHandle { - /** Drop-in stream. Fresh forwards live events; resumed re-yields the logged - * prefix then forwards live events; replayed re-yields the full log. */ - stream: AsyncGenerator - /** Which path ran. Valid after `stream` drains. */ - mode(): SupervisedRunMode - /** The durable RunRecord for the turn. Valid after `stream` drains. */ - record(): RunRecord | undefined -} - -const TURN_STEP = 0 - -export function runSupervisedTurn( - options: RunSupervisorOptions, -): SupervisedRunHandle { - const { store, runId, manifest, workerId, adapter } = options - const leaseMs = options.leaseMs ?? 60_000 - const heartbeatMs = options.heartbeatMs ?? 30_000 - const intent = options.intent ?? 'turn' - const now = options.now ?? (() => Date.now()) - const inputHash = canonicalHash(manifest.input) - - let mode: SupervisedRunMode = 'fresh' - let finalRecord: RunRecord | undefined - let currentHandle: RunHandle | undefined - // Set when the heartbeat finds the lease taken by another supervisor. The - // catch block then skips failStep/endRun — this worker no longer owns the - // run, and must not write its terminal state. - let leaseLost = false - - async function* drain( - source: AsyncIterable>, - ): AsyncGenerator { - let lastRenew = now() - for await (const event of source) { - // Persist the reconnect pointer the instant the substrate yields it, - // before the event itself — a crash between the two must leave a - // resumable handle, never an event a fresh supervisor cannot place. - if (event.handle) { - currentHandle = event.handle - await store.setRunHandle({ runId, handle: event.handle }) - } - // Drain into the durable log. Idempotent on eventId: a boundary event - // re-yielded across the reconnect seam returns accepted=false and is - // not re-forwarded — no duplicate reaches the caller. - const { accepted } = await store.appendStreamEvent({ - runId, - eventId: event.eventId, - payload: event.payload, - }) - if (now() - lastRenew >= heartbeatMs) { - const renewed = await store.renewLease({ runId, workerId, leaseMs }) - if (!renewed.ok) { - leaseLost = true - throw new Error(`durable-runs: lease lost on ${runId} — another supervisor took over`) - } - lastRenew = now() - } - if (accepted) yield event.payload - } - } - - async function* stream(): AsyncGenerator { - const { run, completedSteps } = await store.startOrResume({ - runId, - manifest, - workerId, - leaseMs, - }) - - // ── Replayed — the turn already completed ─────────────────────────── - if (completedSteps.some((s) => s.stepIndex === TURN_STEP && s.status === 'completed')) { - mode = 'replayed' - for (const e of await store.readStreamEvents(runId)) yield e.payload as TEvent - finalRecord = await store.endRun({ runId, workerId, status: 'completed' }) - return - } - - // ── Decide fresh vs resume ────────────────────────────────────────── - const logged = await store.readStreamEvents(runId) - const priorHandle = run.handle - const resumable = - priorHandle !== undefined && - priorHandle.status === 'running' && - typeof priorHandle.runId === 'string' - - let source: AsyncIterable> - if (resumable && priorHandle) { - mode = 'resumed' - currentHandle = priorHandle - // Re-yield the logged prefix so the caller sees the whole stream, then - // attach strictly after the last logged event. - for (const e of logged) yield e.payload as TEvent - const cursor = logged.length > 0 ? logged[logged.length - 1]!.eventId : priorHandle.cursor - source = adapter.attach(priorHandle, cursor) - } else { - mode = 'fresh' - source = adapter.start() - } - - await store.beginStep({ runId, stepIndex: TURN_STEP, intent, kind: 'llm', inputHash }) - try { - yield* drain(source) - const eventCount = (await store.readStreamEvents(runId)).length - // completeStep first — it is the "turn finished" signal the replay path - // keys on. A crash after it leaves a cleanly replayable run; flipping - // the handle first and crashing before completeStep would instead make - // a finished run look fresh (handle not `running`, step not completed). - await store.completeStep({ runId, stepIndex: TURN_STEP, result: { eventCount } }) - if (currentHandle && currentHandle.status === 'running') { - await store.setRunHandle({ runId, handle: { ...currentHandle, status: 'completed' } }) - } - finalRecord = await store.endRun({ - runId, - workerId, - status: 'completed', - outcome: { notes: intent, metadata: { events: eventCount, mode } }, - }) - } catch (err) { - if (!leaseLost) { - // The handle stays `running` — exactly what a resuming retry needs. - await store.failStep({ - runId, - stepIndex: TURN_STEP, - error: { message: err instanceof Error ? err.message : String(err) }, - }) - finalRecord = await store.endRun({ runId, workerId, status: 'failed' }) - } - throw err - } - } - - return { - stream: stream(), - mode: () => mode, - record: () => finalRecord, - } -} diff --git a/src/durable/tests/chat-engine.test.ts b/src/durable/tests/chat-engine.test.ts index dc23af83..31d80308 100644 --- a/src/durable/tests/chat-engine.test.ts +++ b/src/durable/tests/chat-engine.test.ts @@ -1,28 +1,14 @@ /** - * `DurableChatTurnEngine` tests — the orchestration contract every product - * chat handler depends on. Run against all three stores so the engine is - * proven on every backend. Covers: lifecycle envelope, NDJSON encoding, - * replay-skips-persist (no double-write), hook ordering, the failure - * envelope, the per-event side channel, and the pre-persist transform. + * `ChatTurnEngine` tests — the orchestration contract every product chat + * handler depends on. Covers: lifecycle envelope, NDJSON encoding, + * hook ordering, the failure envelope, the per-event side channel, + * the pre-persist transform, the live accumulator, and swallowed + * hook errors. */ -import { mkdtempSync, readFileSync, rmSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' +import { describe, expect, it } from 'vitest' -import { afterEach, beforeEach, describe, expect, it } from 'vitest' - -import { - type ChatStreamEvent, - D1DurableRunStore, - DurableChatTurnEngine, - type DurableRunStore, - FileSystemDurableRunStore, - InMemoryDurableRunStore, -} from '../index' -import { createSqliteD1 } from './sqlite-d1-adapter' - -const SCHEMA_SQL = readFileSync(new URL('../schema.sql', import.meta.url), 'utf8') +import { type ChatStreamEvent, ChatTurnEngine } from '../chat-engine' /** Drain an NDJSON ReadableStream into parsed events. */ async function drain(body: ReadableStream): Promise { @@ -58,231 +44,166 @@ function textProducer(text: string, onConstruct?: () => void) { } } -const storeKinds = [ - { - name: 'InMemoryDurableRunStore', - factory: () => ({ store: new InMemoryDurableRunStore(), cleanup: () => undefined }), - }, - { - name: 'FileSystemDurableRunStore', - factory: () => { - const dir = mkdtempSync(join(tmpdir(), 'chat-engine-test-')) - return { - store: new FileSystemDurableRunStore(dir), - cleanup: () => rmSync(dir, { recursive: true, force: true }), - } - }, - }, - { - name: 'D1DurableRunStore (better-sqlite3)', - factory: () => { - const handle = createSqliteD1() - handle.raw.exec(SCHEMA_SQL) - return { - store: new D1DurableRunStore(handle.db), - cleanup: () => handle.close(), - } - }, - }, -] as const - const IDENTITY = { tenantId: 'ws-1', sessionId: 'thread-1', userId: 'user-1', turnIndex: 0 } -for (const kind of storeKinds) { - describe(`DurableChatTurnEngine / ${kind.name}`, () => { - let store: DurableRunStore - let cleanup: () => void - const engine = new DurableChatTurnEngine() - - beforeEach(() => { - const made = kind.factory() - store = made.store - cleanup = made.cleanup - }) - - afterEach(async () => { - await store.close() - cleanup() +describe('ChatTurnEngine', () => { + const engine = new ChatTurnEngine() + + it('wraps the turn in the session.run.* lifecycle envelope', async () => { + const persisted: string[] = [] + const { body, contentType } = engine.runTurn({ + identity: IDENTITY, + hooks: { + produce: textProducer('Hi there.'), + persistAssistantMessage: async ({ finalText }) => { + persisted.push(finalText) + }, + }, }) + expect(contentType).toBe('application/x-ndjson') + const events = await drain(body) + + expect(events[0]?.type).toBe('session.run.started') + expect(events.at(-1)?.type).toBe('session.run.completed') + expect(events.some((e) => e.type === 'message.part.updated')).toBe(true) + expect(events.some((e) => e.type === 'result')).toBe(true) + expect(persisted).toEqual(['Hi there.']) + }) - it('wraps the turn in the session.run.* lifecycle envelope', async () => { - const persisted: string[] = [] - const { body, contentType } = engine.runTurn({ - identity: IDENTITY, - userMessage: 'hello', - projectId: 'legal-agent', - domain: 'legal', - store, - hooks: { - produce: textProducer('Hi there.'), - persistAssistantMessage: async ({ finalText }) => { - persisted.push(finalText) - }, + it('runs hooks in order: persist → onTurnComplete, after the stream', async () => { + const order: string[] = [] + const { body } = engine.runTurn({ + identity: IDENTITY, + hooks: { + produce: textProducer('answer'), + persistAssistantMessage: async () => { + order.push('persist') }, - }) - expect(contentType).toBe('application/x-ndjson') - const events = await drain(body) - - expect(events[0]?.type).toBe('session.run.started') - expect(events.at(-1)?.type).toBe('session.run.completed') - expect(events.some((e) => e.type === 'message.part.updated')).toBe(true) - expect(events.some((e) => e.type === 'result')).toBe(true) - expect(persisted).toEqual(['Hi there.']) + onTurnComplete: async () => { + order.push('postProcess') + }, + }, }) + await drain(body) + expect(order).toEqual(['persist', 'postProcess']) + }) - it('runs hooks in order: persist → onTurnComplete, after the stream', async () => { - const order: string[] = [] - const { body } = engine.runTurn({ - identity: IDENTITY, - userMessage: 'q', - projectId: 'gtm-agent', - domain: 'gtm', - store, - hooks: { - produce: textProducer('answer'), - persistAssistantMessage: async () => { - order.push('persist') - }, - onTurnComplete: async () => { - order.push('postProcess') - }, + it('a producer failure becomes error + session.run.failed, stream still closes', async () => { + const { body } = engine.runTurn({ + identity: IDENTITY, + hooks: { + produce: () => { + async function* stream(): AsyncGenerator { + yield { type: 'message.part.updated', data: { delta: 'partial' } } + throw new Error('backend exploded') + } + return { stream: stream(), finalText: () => '' } }, - }) - await drain(body) - expect(order).toEqual(['persist', 'postProcess']) + persistAssistantMessage: async () => undefined, + }, }) + const events = await drain(body) + expect(events[0]?.type).toBe('session.run.started') + const err = events.find((e) => e.type === 'error') + expect(err?.data?.message).toBe('backend exploded') + expect(events.at(-1)?.type).toBe('session.run.failed') + }) - it('replay skips the producer AND skips persist/post-process (no double-write)', async () => { - let constructs = 0 - let persists = 0 - let postProcesses = 0 - const hooks = { - produce: textProducer('cached', () => (constructs += 1)), - persistAssistantMessage: async () => { - persists += 1 - }, - onTurnComplete: async () => { - postProcesses += 1 + it('onEvent side channel receives every emitted event', async () => { + const broadcast: string[] = [] + const { body } = engine.runTurn({ + identity: IDENTITY, + hooks: { + produce: textProducer('x'), + persistAssistantMessage: async () => undefined, + onEvent: (event) => { + broadcast.push(event.type) }, - } - // First attempt — fresh. - await drain( - engine.runTurn({ - identity: IDENTITY, - userMessage: 'q', - projectId: 'creative-agent', - domain: 'creative', - store, - hooks, - }).body, - ) - // Second attempt — same identity → replay. - const replayEvents = await drain( - engine.runTurn({ - identity: IDENTITY, - userMessage: 'q', - projectId: 'creative-agent', - domain: 'creative', - store, - hooks, - }).body, - ) - - expect(constructs).toBe(1) // producer built once - expect(persists).toBe(1) // assistant message persisted once - expect(postProcesses).toBe(1) // post-process ran once - expect(replayEvents.some((e) => e.type === 'result')).toBe(true) - const completed = replayEvents.find((e) => e.type === 'session.run.completed') - expect(completed?.data?.replayed).toBe(true) + }, }) + const events = await drain(body) + expect(broadcast).toEqual(events.map((e) => e.type)) + }) - it('a producer failure becomes error + session.run.failed, stream still closes', async () => { - const { body } = engine.runTurn({ - identity: IDENTITY, - userMessage: 'q', - projectId: 'tax-agent', - domain: 'tax', - store, - hooks: { - produce: () => { - async function* stream(): AsyncGenerator { - yield { type: 'message.part.updated', data: { delta: 'partial' } } - throw new Error('backend exploded') - } - return { stream: stream(), finalText: () => '' } - }, - persistAssistantMessage: async () => undefined, + it('transformFinalText alters what is persisted, not the live stream', async () => { + let persisted = '' + const { body } = engine.runTurn({ + identity: IDENTITY, + hooks: { + produce: textProducer('SSN 123-45-6789'), + transformFinalText: (t) => t.replace(/\d{3}-\d{2}-\d{4}/, '[REDACTED]'), + persistAssistantMessage: async ({ finalText }) => { + persisted = finalText }, - }) - const events = await drain(body) - expect(events[0]?.type).toBe('session.run.started') - const err = events.find((e) => e.type === 'error') - expect(err?.data?.message).toBe('backend exploded') - expect(events.at(-1)?.type).toBe('session.run.failed') + }, }) + const events = await drain(body) + const result = events.find((e) => e.type === 'result') + expect(result?.data?.finalText).toBe('SSN 123-45-6789') + expect(persisted).toBe('SSN [REDACTED]') + }) - it('onEvent side channel receives every emitted event', async () => { - const broadcast: string[] = [] - const { body } = engine.runTurn({ - identity: IDENTITY, - userMessage: 'q', - projectId: 'gtm-agent', - domain: 'gtm', - store, - hooks: { - produce: textProducer('x'), - persistAssistantMessage: async () => undefined, - onEvent: (event) => { - broadcast.push(event.type) - }, + it('accumulate builds final text from events when producer.finalText() is empty', async () => { + let persisted = '' + const { body } = engine.runTurn({ + identity: IDENTITY, + hooks: { + produce: () => { + async function* stream(): AsyncGenerator { + yield { type: 'message.part.updated', data: { delta: 'Hello' } } + yield { type: 'message.part.updated', data: { delta: ' world' } } + } + return { stream: stream(), finalText: () => '' } }, - }) - const events = await drain(body) - // Side channel saw exactly what the client saw. - expect(broadcast).toEqual(events.map((e) => e.type)) + accumulate: (event, current) => { + if (event.type !== 'message.part.updated') return undefined + const delta = typeof event.data?.delta === 'string' ? event.data.delta : '' + return current + delta + }, + persistAssistantMessage: async ({ finalText }) => { + persisted = finalText + }, + }, }) + await drain(body) + expect(persisted).toBe('Hello world') + }) - it('transformFinalText alters what is persisted, not the live stream', async () => { - let persisted = '' - const { body } = engine.runTurn({ - identity: IDENTITY, - userMessage: 'q', - projectId: 'legal-agent', - domain: 'legal', - store, - hooks: { - produce: textProducer('SSN 123-45-6789'), - transformFinalText: (t) => t.replace(/\d{3}-\d{2}-\d{4}/, '[REDACTED]'), - persistAssistantMessage: async ({ finalText }) => { - persisted = finalText - }, + it('a throwing persist hook is swallowed — the turn still completes', async () => { + const { body } = engine.runTurn({ + identity: IDENTITY, + hooks: { + produce: textProducer('ok'), + persistAssistantMessage: async () => { + throw new Error('db down') }, - }) - const events = await drain(body) - // Live stream still carries the raw text. - const result = events.find((e) => e.type === 'result') - expect(result?.data?.finalText).toBe('SSN 123-45-6789') - // Persisted copy is redacted. - expect(persisted).toBe('SSN [REDACTED]') + }, }) + const events = await drain(body) + expect(events.at(-1)?.type).toBe('session.run.completed') + }) - it('a throwing persist hook is swallowed — the turn still completes', async () => { - const { body } = engine.runTurn({ - identity: IDENTITY, - userMessage: 'q', - projectId: 'legal-agent', - domain: 'legal', - store, - hooks: { - produce: textProducer('ok'), - persistAssistantMessage: async () => { - throw new Error('db down') - }, + it('traceFlush is handed to waitUntil so the worker isolate survives the POST', async () => { + let flushAwaited = false + let waitUntilCalled = false + const { body } = engine.runTurn({ + identity: IDENTITY, + waitUntil: (p) => { + waitUntilCalled = true + void p.then(() => { + flushAwaited = true + }) + }, + hooks: { + produce: textProducer('ok'), + persistAssistantMessage: async () => undefined, + traceFlush: async () => { + flushAwaited = true }, - }) - const events = await drain(body) - // Persist failure must not turn into session.run.failed. - expect(events.at(-1)?.type).toBe('session.run.completed') + }, }) + await drain(body) + expect(waitUntilCalled).toBe(true) + expect(flushAwaited).toBe(true) }) -} +}) diff --git a/src/durable/tests/durable-runs.test.ts b/src/durable/tests/durable-runs.test.ts deleted file mode 100644 index 15383568..00000000 --- a/src/durable/tests/durable-runs.test.ts +++ /dev/null @@ -1,331 +0,0 @@ -/** - * Durable-run substrate tests — crash recovery, lease semantics, event races, - * divergence detection. The tests run identically against the in-memory store - * and the file-system store (same `DurableRunStore` contract), so a single - * matrix proves both implementations. - */ - -import { mkdtempSync, readFileSync, rmSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { afterEach, beforeEach, describe, expect, it } from 'vitest' - -import { - D1DurableRunStore, - DurableAwaitEventTimeoutError, - DurableRunDivergenceError, - DurableRunInputMismatchError, - DurableRunLeaseHeldError, - type DurableRunManifest, - type DurableRunStore, - FileSystemDurableRunStore, - InMemoryDurableRunStore, - manifestHash, - runDurable, -} from '../index' -import { createSqliteD1 } from './sqlite-d1-adapter' - -const SCHEMA_SQL = readFileSync(new URL('../schema.sql', import.meta.url), 'utf8') - -function makeManifest(overrides?: Partial): DurableRunManifest { - return { - projectId: 'test-project', - scenarioId: 'scenario-1', - task: { - id: 'task-1', - intent: 'unit-test', - domain: 'test', - requiredKnowledge: [], - metadata: {}, - }, - input: { x: 1 }, - ...overrides, - } -} - -const storeKinds = [ - { - name: 'InMemoryDurableRunStore', - factory: () => ({ store: new InMemoryDurableRunStore(), cleanup: () => undefined }), - }, - { - name: 'FileSystemDurableRunStore', - factory: () => { - const dir = mkdtempSync(join(tmpdir(), 'durable-runs-test-')) - return { - store: new FileSystemDurableRunStore(dir), - cleanup: () => rmSync(dir, { recursive: true, force: true }), - } - }, - }, - { - name: 'D1DurableRunStore (better-sqlite3)', - factory: () => { - const handle = createSqliteD1() - handle.raw.exec(SCHEMA_SQL) - return { - store: new D1DurableRunStore(handle.db), - cleanup: () => handle.close(), - } - }, - }, -] as const - -for (const kind of storeKinds) { - describe(`durable-runs / ${kind.name}`, () => { - let store: DurableRunStore - let cleanup: () => void - - beforeEach(() => { - const made = kind.factory() - store = made.store - cleanup = made.cleanup - }) - - afterEach(async () => { - await store.close() - cleanup() - }) - - it('executes a fresh run end-to-end and returns the result', async () => { - const { result, record, steps } = await runDurable({ - runId: 'r1', - manifest: makeManifest(), - store, - taskFn: async (ctx) => { - const a = await ctx.step('add', async () => 1 + 1) - const b = await ctx.step('multiply', async () => a * 5) - return b - }, - }) - expect(result).toBe(10) - expect(record.status).toBe('completed') - expect(steps.map((s) => ({ idx: s.stepIndex, intent: s.intent, status: s.status }))).toEqual([ - { idx: 0, intent: 'add', status: 'completed' }, - { idx: 1, intent: 'multiply', status: 'completed' }, - ]) - }) - - it('replays completed steps without re-executing fn', async () => { - let calls = 0 - // First run — abort mid-stream. - try { - await runDurable({ - runId: 'r2', - manifest: makeManifest(), - store, - taskFn: async (ctx) => { - await ctx.step('first', async () => { - calls += 1 - return 'one' - }) - await ctx.step('boom', async () => { - throw new Error('forced failure') - }) - return 'never' - }, - }) - } catch (e) { - expect((e as Error).message).toBe('forced failure') - } - expect(calls).toBe(1) - - // Second run — fix the failing step. Verify 'first' is NOT re-executed. - const { result } = await runDurable({ - runId: 'r2', - manifest: makeManifest(), - store, - taskFn: async (ctx) => { - const a = await ctx.step('first', async () => { - calls += 1 - return 'one' - }) - const b = await ctx.step('boom', async () => 'fixed') - return `${a}-${b}` - }, - }) - expect(result).toBe('one-fixed') - // First-step callback called exactly once across the two runs. - expect(calls).toBe(1) - }) - - it('rejects manifest mismatch on resume', async () => { - await runDurable({ - runId: 'r3', - manifest: makeManifest({ input: { x: 1 } }), - store, - taskFn: async (ctx) => ctx.step('s', async () => 'ok'), - }) - await expect( - runDurable({ - runId: 'r3', - manifest: makeManifest({ input: { x: 2 } }), - store, - taskFn: async () => 'noop', - }), - ).rejects.toBeInstanceOf(DurableRunInputMismatchError) - }) - - it('rejects step divergence (intent change at same position)', async () => { - try { - await runDurable({ - runId: 'r4', - manifest: makeManifest(), - store, - taskFn: async (ctx) => { - await ctx.step('first', async () => 1) - await ctx.step('second', async () => { - throw new Error('boom') - }) - return 'never' - }, - }) - } catch { - /* expected */ - } - await expect( - runDurable({ - runId: 'r4', - manifest: makeManifest(), - store, - taskFn: async (ctx) => { - await ctx.step('first', async () => 1) - await ctx.step('DIFFERENT', async () => 2) // diverges at idx 1 - return 'noop' - }, - }), - ).rejects.toBeInstanceOf(DurableRunDivergenceError) - }) - - it('refuses concurrent acquisition while lease is live', async () => { - const { run } = await store.startOrResume({ - runId: 'r5', - manifest: makeManifest(), - workerId: 'workerA', - leaseMs: 60_000, - }) - expect(run.leaseHolderId).toBe('workerA') - await expect( - store.startOrResume({ - runId: 'r5', - manifest: makeManifest(), - workerId: 'workerB', - leaseMs: 60_000, - }), - ).rejects.toBeInstanceOf(DurableRunLeaseHeldError) - }) - - it('allows takeover after lease expires', async () => { - const { run } = await store.startOrResume({ - runId: 'r6', - manifest: makeManifest(), - workerId: 'workerA', - leaseMs: 50, - }) - expect(run.leaseHolderId).toBe('workerA') - // Wait past lease expiry. - await new Promise((r) => setTimeout(r, 80)) - const taken = await store.startOrResume({ - runId: 'r6', - manifest: makeManifest(), - workerId: 'workerB', - leaseMs: 60_000, - }) - expect(taken.run.leaseHolderId).toBe('workerB') - }) - - it('awaitEvent receives the first emit and is replayed thereafter', async () => { - // Concurrently emit the event while runDurable is awaiting it — this - // mirrors the production pattern (external system fires a webhook / - // tool callback while the agent task is suspended). - const emitSoon = new Promise((resolve) => { - setTimeout(() => { - void store - .emitEvent({ runId: 'r7', key: 'shipment', payload: { tracking: 'X1' } }) - .then(() => resolve()) - }, 40) - }) - - const { result } = await runDurable({ - runId: 'r7', - manifest: makeManifest(), - store, - taskFn: async (ctx) => { - const ev = await ctx.awaitEvent<{ tracking: string }>('shipment', { - timeoutMs: 2_000, - pollMs: 10, - }) - return ev.tracking - }, - }) - await emitSoon - expect(result).toBe('X1') - - // Replay the same run — awaitEvent returns from cache. - const replay = await runDurable({ - runId: 'r7', - manifest: makeManifest(), - store, - taskFn: async (ctx) => { - const ev = await ctx.awaitEvent<{ tracking: string }>('shipment', { timeoutMs: 1_000 }) - return ev.tracking - }, - }) - expect(replay.result).toBe('X1') - }) - - it('awaitEvent times out cleanly', async () => { - await expect( - runDurable({ - runId: 'r8', - manifest: makeManifest(), - store, - taskFn: async (ctx) => ctx.awaitEvent('never', { timeoutMs: 50, pollMs: 10 }), - }), - ).rejects.toBeInstanceOf(DurableAwaitEventTimeoutError) - }) - - it('emitEvent enforces first-emit-wins', async () => { - await store.startOrResume({ runId: 'r9', manifest: makeManifest(), workerId: 'w1' }) - const first = await store.emitEvent({ runId: 'r9', key: 'k', payload: { v: 1 } }) - expect(first.accepted).toBe(true) - const second = await store.emitEvent({ runId: 'r9', key: 'k', payload: { v: 2 } }) - expect(second.accepted).toBe(false) - expect((second.record.payload as { v: number }).v).toBe(1) - }) - - it('ctx.now and ctx.uuid are stable across replay', async () => { - const first = await runDurable({ - runId: 'r10', - manifest: makeManifest(), - store, - taskFn: async (ctx) => { - const t = await ctx.now() - const id = await ctx.uuid() - return { t: t.toISOString(), id } - }, - }) - // Force a "fresh" replay by manually re-running. The fact that the - // run is already 'completed' shouldn't matter for cached step replay. - const replay = await runDurable({ - runId: 'r10', - manifest: makeManifest(), - store, - taskFn: async (ctx) => { - const t = await ctx.now() - const id = await ctx.uuid() - return { t: t.toISOString(), id } - }, - }) - expect(replay.result).toEqual(first.result) - }) - }) -} - -describe('manifestHash', () => { - it('is stable across object insertion order', () => { - const a = makeManifest({ input: { a: 1, b: 2 } }) - const b = makeManifest({ input: { b: 2, a: 1 } }) - expect(manifestHash(a)).toBe(manifestHash(b)) - }) -}) diff --git a/src/durable/tests/durable-turn.test.ts b/src/durable/tests/durable-turn.test.ts deleted file mode 100644 index bfe3c5bf..00000000 --- a/src/durable/tests/durable-turn.test.ts +++ /dev/null @@ -1,302 +0,0 @@ -/** - * `runDurableTurn` tests — fresh run, replay, mid-stream crash re-run, - * concurrent-attempt rejection. Run identically against all three stores - * (InMemory / FileSystem / D1-over-sqlite) via the shared matrix, so the - * turn primitive is proven on every backend a product could use. - */ - -import { mkdtempSync, readFileSync, rmSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' - -import { afterEach, beforeEach, describe, expect, it } from 'vitest' - -import { - D1DurableRunStore, - DurableRunLeaseHeldError, - type DurableRunManifest, - type DurableRunStore, - FileSystemDurableRunStore, - InMemoryDurableRunStore, - runDurableTurn, -} from '../index' -import { createSqliteD1 } from './sqlite-d1-adapter' - -const SCHEMA_SQL = readFileSync(new URL('../schema.sql', import.meta.url), 'utf8') - -interface FakeEvent { - type: string - text?: string -} - -function makeManifest(turnIndex = 0): DurableRunManifest { - return { - projectId: 'test-product', - scenarioId: 'thread-1', - task: { - id: `chat:thread-1:${turnIndex}`, - intent: 'unit-test chat turn', - domain: 'test', - requiredKnowledge: [], - metadata: { turnIndex }, - }, - input: { userMessage: `q-${turnIndex}` }, - } -} - -/** A producer that yields N text deltas then a final event. Records whether - * it was constructed so tests can prove the replay path skips it. */ -function fakeProducer(opts: { chunks: string[]; onConstruct?: () => void; throwAfter?: number }) { - opts.onConstruct?.() - let assembled = '' - async function* stream(): AsyncGenerator { - let emitted = 0 - for (const chunk of opts.chunks) { - if (opts.throwAfter !== undefined && emitted >= opts.throwAfter) { - throw new Error('producer exploded mid-stream') - } - assembled += chunk - yield { type: 'delta', text: chunk } - emitted += 1 - } - yield { type: 'result', text: assembled } - } - return { - stream: stream(), - finalText: () => assembled, - } -} - -const storeKinds = [ - { - name: 'InMemoryDurableRunStore', - factory: () => ({ store: new InMemoryDurableRunStore(), cleanup: () => undefined }), - }, - { - name: 'FileSystemDurableRunStore', - factory: () => { - const dir = mkdtempSync(join(tmpdir(), 'durable-turn-test-')) - return { - store: new FileSystemDurableRunStore(dir), - cleanup: () => rmSync(dir, { recursive: true, force: true }), - } - }, - }, - { - name: 'D1DurableRunStore (better-sqlite3)', - factory: () => { - const handle = createSqliteD1() - handle.raw.exec(SCHEMA_SQL) - return { - store: new D1DurableRunStore(handle.db), - cleanup: () => handle.close(), - } - }, - }, -] as const - -for (const kind of storeKinds) { - describe(`runDurableTurn / ${kind.name}`, () => { - let store: DurableRunStore - let cleanup: () => void - - beforeEach(() => { - const made = kind.factory() - store = made.store - cleanup = made.cleanup - }) - - afterEach(async () => { - await store.close() - cleanup() - }) - - it('fresh run forwards producer events live and checkpoints final text', async () => { - let constructed = 0 - const handle = runDurableTurn({ - store, - runId: 'chat:thread-1:0', - manifest: makeManifest(0), - workerId: 'worker-a', - produce: () => - fakeProducer({ chunks: ['Hello', ', ', 'world'], onConstruct: () => (constructed += 1) }), - replayEvent: (text) => ({ type: 'result', text }), - }) - - const events: FakeEvent[] = [] - for await (const e of handle.stream) events.push(e) - - expect(constructed).toBe(1) - expect(handle.replayed()).toBe(false) - expect(events).toEqual([ - { type: 'delta', text: 'Hello' }, - { type: 'delta', text: ', ' }, - { type: 'delta', text: 'world' }, - { type: 'result', text: 'Hello, world' }, - ]) - expect(handle.finalText()).toBe('Hello, world') - expect(handle.record()?.status).toBe('completed') - }) - - it('replay emits one synthetic event and never constructs the producer', async () => { - // First attempt — completes, checkpoints. - const first = runDurableTurn({ - store, - runId: 'chat:thread-1:1', - manifest: makeManifest(1), - workerId: 'worker-a', - produce: () => fakeProducer({ chunks: ['cached ', 'answer'] }), - replayEvent: (text) => ({ type: 'result', text }), - }) - for await (const _ of first.stream) { - /* drain */ - } - expect(first.replayed()).toBe(false) - - // Second attempt — same runId. Producer must NOT be constructed. - let constructed = 0 - const replay = runDurableTurn({ - store, - runId: 'chat:thread-1:1', - manifest: makeManifest(1), - workerId: 'worker-b', - produce: () => fakeProducer({ chunks: ['x'], onConstruct: () => (constructed += 1) }), - replayEvent: (text) => ({ type: 'result', text }), - }) - const events: FakeEvent[] = [] - for await (const e of replay.stream) events.push(e) - - expect(constructed).toBe(0) - expect(replay.replayed()).toBe(true) - expect(replay.finalText()).toBe('cached answer') - expect(events).toEqual([{ type: 'result', text: 'cached answer' }]) - }) - - it('mid-stream crash re-runs the turn from the top (no partial replay)', async () => { - // First attempt explodes after 1 event. - let firstConstructs = 0 - const first = runDurableTurn({ - store, - runId: 'chat:thread-1:2', - manifest: makeManifest(2), - workerId: 'worker-a', - produce: () => - fakeProducer({ - chunks: ['partial', '-lost'], - throwAfter: 1, - onConstruct: () => (firstConstructs += 1), - }), - replayEvent: (text) => ({ type: 'result', text }), - }) - const firstEvents: FakeEvent[] = [] - await expect( - (async () => { - for await (const e of first.stream) firstEvents.push(e) - })(), - ).rejects.toThrow('producer exploded mid-stream') - expect(firstConstructs).toBe(1) - expect(first.record()?.status).toBe('failed') - - // Second attempt — the failed step is NOT replayed; the producer runs - // again and the turn completes cleanly. - let secondConstructs = 0 - const second = runDurableTurn({ - store, - runId: 'chat:thread-1:2', - manifest: makeManifest(2), - workerId: 'worker-a', - produce: () => - fakeProducer({ chunks: ['recovered'], onConstruct: () => (secondConstructs += 1) }), - replayEvent: (text) => ({ type: 'result', text }), - }) - const secondEvents: FakeEvent[] = [] - for await (const e of second.stream) secondEvents.push(e) - - expect(secondConstructs).toBe(1) - expect(second.replayed()).toBe(false) - expect(second.finalText()).toBe('recovered') - expect(secondEvents).toEqual([ - { type: 'delta', text: 'recovered' }, - { type: 'result', text: 'recovered' }, - ]) - }) - - it('the accumulate hook builds final text when the producer reports none', async () => { - // Producer whose finalText() stays empty — accumulate must fill it. - function emptyFinalProducer() { - async function* stream(): AsyncGenerator { - yield { type: 'delta', text: 'a' } - yield { type: 'delta', text: 'b' } - } - return { stream: stream(), finalText: () => '' } - } - const handle = runDurableTurn({ - store, - runId: 'chat:thread-1:3', - manifest: makeManifest(3), - workerId: 'worker-a', - produce: emptyFinalProducer, - replayEvent: (text) => ({ type: 'result', text }), - accumulate: (event, current) => - event.type === 'delta' ? current + (event.text ?? '') : undefined, - }) - for await (const _ of handle.stream) { - /* drain */ - } - expect(handle.finalText()).toBe('ab') - - // Replay must return the accumulated text. - const replay = runDurableTurn({ - store, - runId: 'chat:thread-1:3', - manifest: makeManifest(3), - workerId: 'worker-b', - produce: emptyFinalProducer, - replayEvent: (text) => ({ type: 'result', text }), - }) - const events: FakeEvent[] = [] - for await (const e of replay.stream) events.push(e) - expect(events).toEqual([{ type: 'result', text: 'ab' }]) - }) - - it('rejects a concurrent attempt while the first turn holds the lease', async () => { - // Start the first turn but do NOT drain it — lease stays held. - const first = runDurableTurn({ - store, - runId: 'chat:thread-1:4', - manifest: makeManifest(4), - workerId: 'worker-a', - leaseMs: 60_000, - produce: () => fakeProducer({ chunks: ['slow'] }), - replayEvent: (text) => ({ type: 'result', text }), - }) - // Pump the first stream just enough to claim the lease (startOrResume - // runs on the first `.next()`). - const firstIter = first.stream[Symbol.asyncIterator]() - await firstIter.next() - - // Concurrent worker on the same runId must be rejected. - const second = runDurableTurn({ - store, - runId: 'chat:thread-1:4', - manifest: makeManifest(4), - workerId: 'worker-b', - leaseMs: 60_000, - produce: () => fakeProducer({ chunks: ['concurrent'] }), - replayEvent: (text) => ({ type: 'result', text }), - }) - await expect( - (async () => { - for await (const _ of second.stream) { - /* drain */ - } - })(), - ).rejects.toBeInstanceOf(DurableRunLeaseHeldError) - - // Drain the first to release the lease cleanly. - for await (const _ of { [Symbol.asyncIterator]: () => firstIter }) { - /* drain remainder */ - } - }) - }) -} diff --git a/src/durable/tests/execution-handle.test.ts b/src/durable/tests/execution-handle.test.ts new file mode 100644 index 00000000..e18fb958 --- /dev/null +++ b/src/durable/tests/execution-handle.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest' + +import { deriveExecutionId } from '../execution-handle' + +describe('deriveExecutionId', () => { + it('is stable for the same identity tuple — a client retry of the same turn lands on the same substrate execution', () => { + const a = deriveExecutionId({ projectId: 'gtm-agent', sessionId: 'thread-1', turnIndex: 0 }) + const b = deriveExecutionId({ projectId: 'gtm-agent', sessionId: 'thread-1', turnIndex: 0 }) + expect(a).toBe(b) + }) + + it('differs across turnIndex — turn N+1 cannot collide with turn N', () => { + expect(deriveExecutionId({ projectId: 'p', sessionId: 's', turnIndex: 0 })).not.toBe( + deriveExecutionId({ projectId: 'p', sessionId: 's', turnIndex: 1 }), + ) + }) + + it('differs across projectId — two products sharing a sessionId do not collide', () => { + expect(deriveExecutionId({ projectId: 'a', sessionId: 's', turnIndex: 0 })).not.toBe( + deriveExecutionId({ projectId: 'b', sessionId: 's', turnIndex: 0 }), + ) + }) + + it('differs across sessionId', () => { + expect(deriveExecutionId({ projectId: 'p', sessionId: 's1', turnIndex: 0 })).not.toBe( + deriveExecutionId({ projectId: 'p', sessionId: 's2', turnIndex: 0 }), + ) + }) +}) diff --git a/src/durable/tests/schema-sync.test.ts b/src/durable/tests/schema-sync.test.ts deleted file mode 100644 index 55b80680..00000000 --- a/src/durable/tests/schema-sync.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Guard: the inlined `DURABLE_SCHEMA_SQL` constant must stay byte-identical - * to `src/durable/schema.sql`. If the .sql file is the source of truth, the - * constant cannot drift — otherwise consumers and tests see different - * schemas and the D1 store silently goes inconsistent. - */ - -import { readFileSync } from 'node:fs' - -import { describe, expect, it } from 'vitest' - -import { DURABLE_SCHEMA_SQL, DURABLE_SCHEMA_VERSION } from '../schema' - -describe('durable schema', () => { - it('inlined SQL constant matches schema.sql byte-for-byte', () => { - const fileSql = readFileSync(new URL('../schema.sql', import.meta.url), 'utf8') - expect(DURABLE_SCHEMA_SQL).toBe(fileSql) - }) - - it('schema version is positive integer', () => { - expect(DURABLE_SCHEMA_VERSION).toBeGreaterThan(0) - expect(Number.isInteger(DURABLE_SCHEMA_VERSION)).toBe(true) - }) -}) diff --git a/src/durable/tests/session-supervisor-do.test.ts b/src/durable/tests/session-supervisor-do.test.ts deleted file mode 100644 index 5486b1ad..00000000 --- a/src/durable/tests/session-supervisor-do.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { InMemoryDurableRunStore } from '../in-memory-store' -import { - ACTIVE_RUN_KEY, - createSessionSupervisorDO, - type DurableObjectStateLike, - type DurableObjectStorageLike, - type SupervisorHostConfig, -} from '../session-supervisor-do' -import { - runSupervisedTurn, - type SandboxReconnectAdapter, - type SupervisedEvent, -} from '../supervisor' -import type { DurableRunManifest, RunHandle } from '../types' - -const manifest = { - projectId: 'test', - scenarioId: 'persona-1', - task: { id: 'task-1', intent: 'chat', domain: 'test' }, - input: { q: 'hello' }, -} as unknown as DurableRunManifest - -class FakeStorage implements DurableObjectStorageLike { - map = new Map() - alarmAt: number | null = null - async get(key: string): Promise { - return this.map.get(key) as T | undefined - } - async put(key: string, value: T): Promise { - this.map.set(key, value) - } - async delete(key: string): Promise { - return this.map.delete(key) - } - async setAlarm(scheduledTime: number): Promise { - this.alarmAt = scheduledTime - } -} - -class FakeState implements DurableObjectStateLike { - storage = new FakeStorage() -} - -/** A fixed-length scripted adapter — `start` carries the handle on frame 0, - * `attach` resumes strictly past the cursor. */ -function scriptedAdapter(n: number): SandboxReconnectAdapter { - const ids = Array.from({ length: n }, (_, i) => i) - return { - async *start(): AsyncGenerator> { - for (const i of ids) { - const handle: RunHandle | undefined = - i === 0 ? { kind: 'sandbox', runId: 's1', status: 'running' } : undefined - yield { eventId: `e${i}`, payload: i, handle } - } - }, - async *attach( - _handle: RunHandle, - afterEventId: string | undefined, - ): AsyncGenerator> { - const found = afterEventId ? ids.findIndex((i) => `e${i}` === afterEventId) : -1 - for (let i = found + 1; i < ids.length; i++) yield { eventId: `e${i}`, payload: i } - }, - } -} - -function config( - store: InMemoryDurableRunStore, -): SupervisorHostConfig> { - return { - async resolveRun() { - return { store, runId: 'do-run', manifest, workerId: 'w-fetch', adapter: scriptedAdapter(4) } - }, - async resolveOrphan(runId) { - return { store, runId, manifest, workerId: 'w-alarm', adapter: scriptedAdapter(4) } - }, - encodeEvent: (event) => `data: ${event}\n`, - now: () => 1000, - } -} - -async function consume( - stream: AsyncGenerator, - limit = Number.POSITIVE_INFINITY, -): Promise { - const out: T[] = [] - for await (const v of stream) { - out.push(v) - if (out.length >= limit) break - } - return out -} - -describe('SessionSupervisorDO — fetch', () => { - it('streams the supervised events, arms the alarm, and clears the run on completion', async () => { - const store = new InMemoryDurableRunStore() - const DO = createSessionSupervisorDO(config(store)) - const state = new FakeState() - const instance = new DO(state, {}) - - const res = await instance.fetch(new Request('http://do/')) - const text = await res.text() - - expect(res.headers.get('content-type')).toBe('text/event-stream') - expect(text).toBe('data: 0\ndata: 1\ndata: 2\ndata: 3\n') - expect(state.storage.alarmAt).toBe(1000 + 60_000) - // The run finished — the orphan-tracking key is cleared. - expect(state.storage.map.has(ACTIVE_RUN_KEY)).toBe(false) - expect((await store.readStreamEvents('do-run')).map((e) => e.payload)).toEqual([0, 1, 2, 3]) - }) - - it('404s when no run resolves for the request', async () => { - const store = new InMemoryDurableRunStore() - const DO = createSessionSupervisorDO({ - ...config(store), - async resolveRun() { - return undefined - }, - }) - const res = await new DO(new FakeState(), {}).fetch(new Request('http://do/')) - expect(res.status).toBe(404) - }) -}) - -describe('SessionSupervisorDO — alarm re-attaches an orphan', () => { - it('drives a run abandoned by a dropped response stream to completion', async () => { - const store = new InMemoryDurableRunStore() - - // A fetch drained 2 of 4 events, then its worker died — simulate by - // partially consuming a supervised turn and lapsing the lease. - await consume( - runSupervisedTurn({ - store, - runId: 'do-run', - manifest, - workerId: 'w-dead', - adapter: scriptedAdapter(4), - }).stream, - 2, - ) - store._expireLease('do-run') - expect(await store.readStreamEvents('do-run')).toHaveLength(2) - - // The DO still has the run recorded; the orphan-check alarm fires. - const DO = createSessionSupervisorDO(config(store)) - const state = new FakeState() - await state.storage.put(ACTIVE_RUN_KEY, 'do-run') - await new DO(state, {}).alarm() - - // The run was re-driven headlessly to completion and cleared. - expect((await store.readStreamEvents('do-run')).map((e) => e.payload)).toEqual([0, 1, 2, 3]) - expect(store._inspect('do-run')?.status).toBe('completed') - expect(state.storage.map.has(ACTIVE_RUN_KEY)).toBe(false) - }) - - it('is a no-op when no run is recorded', async () => { - const store = new InMemoryDurableRunStore() - const DO = createSessionSupervisorDO(config(store)) - await expect(new DO(new FakeState(), {}).alarm()).resolves.toBeUndefined() - }) -}) diff --git a/src/durable/tests/sqlite-d1-adapter.ts b/src/durable/tests/sqlite-d1-adapter.ts deleted file mode 100644 index 5acdb3e8..00000000 --- a/src/durable/tests/sqlite-d1-adapter.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Wrap better-sqlite3 in the D1DatabaseLike surface so the durable-runs - * test matrix exercises the D1DurableRunStore against a real SQLite engine. - * Catches actual SQL semantics — unique constraints, CASE expressions, - * conditional UPDATEs — instead of a hand-rolled parser stub. - */ - -import type { Database as SqliteDatabase, Statement as SqliteStatement } from 'better-sqlite3' -import Database from 'better-sqlite3' - -import type { D1DatabaseLike, D1PreparedStatementLike } from '../d1-store' - -export interface SqliteD1Handle { - db: D1DatabaseLike - raw: SqliteDatabase - close(): void -} - -export function createSqliteD1(): SqliteD1Handle { - const raw = new Database(':memory:') - raw.pragma('journal_mode = WAL') - raw.pragma('foreign_keys = ON') - const adapter: D1DatabaseLike = { - prepare(query: string): D1PreparedStatementLike { - // D1's ISO-8601 millis: `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')` is - // SQLite-native; keep as-is. - return new SqliteAdaptedStatement(raw.prepare(query)) - }, - async batch(statements: D1PreparedStatementLike[]): Promise { - const results: unknown[] = [] - for (const s of statements) results.push(await s.run()) - return results - }, - } - return { db: adapter, raw, close: () => raw.close() } -} - -class SqliteAdaptedStatement implements D1PreparedStatementLike { - private bound: unknown[] = [] - - constructor(private readonly stmt: SqliteStatement) {} - - bind(...values: unknown[]): D1PreparedStatementLike { - this.bound = values.map(coerceBindValue) - return this - } - - async first(): Promise { - const row = this.stmt.get(...this.bound) - return (row as T | undefined) ?? null - } - - async all(): Promise<{ results: T[] }> { - const rows = this.stmt.all(...this.bound) as T[] - return { results: rows } - } - - async run(): Promise<{ success: boolean; meta?: { changes?: number } }> { - const info = this.stmt.run(...this.bound) - return { success: true, meta: { changes: info.changes } } - } -} - -/** better-sqlite3 rejects raw `undefined`; map to null. */ -function coerceBindValue(v: unknown): unknown { - if (v === undefined) return null - if (typeof v === 'boolean') return v ? 1 : 0 - return v -} diff --git a/src/durable/tests/supervisor.test.ts b/src/durable/tests/supervisor.test.ts deleted file mode 100644 index 8efd0137..00000000 --- a/src/durable/tests/supervisor.test.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { InMemoryDurableRunStore } from '../in-memory-store' -import { - type RunSupervisorOptions, - runSupervisedTurn, - type SandboxReconnectAdapter, - type SupervisedEvent, -} from '../supervisor' -import type { DurableRunManifest, RunHandle } from '../types' - -const manifest = { - projectId: 'test', - scenarioId: 'persona-1', - task: { id: 'task-1', intent: 'chat', domain: 'test' }, - input: { q: 'hello' }, -} as unknown as DurableRunManifest - -const HANDLE: RunHandle = { - kind: 'sandbox', - sandboxId: 'sbx-1', - runId: 'sbx-run-1', - status: 'running', -} - -interface ScriptEvent { - eventId: string - payload: number -} - -/** A controllable SandboxReconnectAdapter over a fixed script of events. - * `start` carries the handle on the first frame; `attach` resumes past the - * cursor (or re-yields the boundary when `reYieldBoundary`). */ -class ScriptedAdapter implements SandboxReconnectAdapter { - startCalls = 0 - attachCalls = 0 - constructor( - private readonly script: ScriptEvent[], - private readonly opts: { reYieldBoundary?: boolean } = {}, - ) {} - - async *start(): AsyncGenerator> { - this.startCalls += 1 - for (const [i, e] of this.script.entries()) { - yield { eventId: e.eventId, payload: e.payload, handle: i === 0 ? HANDLE : undefined } - } - } - - async *attach( - _handle: RunHandle, - afterEventId: string | undefined, - ): AsyncGenerator> { - this.attachCalls += 1 - const found = afterEventId ? this.script.findIndex((e) => e.eventId === afterEventId) : -1 - const from = found >= 0 ? found + (this.opts.reYieldBoundary ? 0 : 1) : 0 - for (let i = from; i < this.script.length; i++) { - const e = this.script[i]! - yield { eventId: e.eventId, payload: e.payload } - } - } -} - -const script: ScriptEvent[] = [0, 1, 2, 3, 4, 5].map((n) => ({ eventId: `e${n}`, payload: n })) - -function opts( - store: InMemoryDurableRunStore, - workerId: string, - adapter: SandboxReconnectAdapter, - extra: Partial> = {}, -): RunSupervisorOptions { - return { store, runId: 'turn-1', manifest, workerId, adapter, ...extra } -} - -async function consume( - stream: AsyncGenerator, - limit = Number.POSITIVE_INFINITY, -): Promise { - const out: T[] = [] - for await (const v of stream) { - out.push(v) - if (out.length >= limit) break - } - return out -} - -describe('runSupervisedTurn — fresh', () => { - it('drains the adapter stream into the durable log and forwards it', async () => { - const store = new InMemoryDurableRunStore() - const sup = runSupervisedTurn(opts(store, 'w1', new ScriptedAdapter(script))) - const out = await consume(sup.stream) - - expect(out).toEqual([0, 1, 2, 3, 4, 5]) - expect(sup.mode()).toBe('fresh') - expect(sup.record()?.status).toBe('completed') - const logged = await store.readStreamEvents('turn-1') - expect(logged.map((e) => e.payload)).toEqual([0, 1, 2, 3, 4, 5]) - expect(logged.map((e) => e.seq)).toEqual([0, 1, 2, 3, 4, 5]) - }) - - it('persists the run handle and flips it to completed on a clean drain', async () => { - const store = new InMemoryDurableRunStore() - const sup = runSupervisedTurn(opts(store, 'w1', new ScriptedAdapter(script))) - await consume(sup.stream) - expect(sup.record()?.handle).toMatchObject({ runId: 'sbx-run-1', status: 'completed' }) - }) -}) - -describe('runSupervisedTurn — replayed', () => { - it('a completed turn replays the logged stream without touching the adapter', async () => { - const store = new InMemoryDurableRunStore() - await consume(runSupervisedTurn(opts(store, 'w1', new ScriptedAdapter(script))).stream) - - const neverAdapter: SandboxReconnectAdapter = { - start() { - throw new Error('start must not run on replay') - }, - attach() { - throw new Error('attach must not run on replay') - }, - } - const replay = runSupervisedTurn(opts(store, 'w2', neverAdapter)) - const out = await consume(replay.stream) - expect(out).toEqual([0, 1, 2, 3, 4, 5]) - expect(replay.mode()).toBe('replayed') - }) -}) - -describe('runSupervisedTurn — chaos: cross-worker resume', () => { - it('a turn killed mid-stream resumes on a fresh worker with no gap and no duplicate', async () => { - const store = new InMemoryDurableRunStore() - - // Worker 1 drains 3 of 6 events, then its isolate "dies" — stop pulling. - const sup1 = runSupervisedTurn(opts(store, 'w1', new ScriptedAdapter(script))) - const partial = await consume(sup1.stream, 3) - expect(partial).toEqual([0, 1, 2]) - - // The dead worker no longer heartbeats — its lease lapses. - store._expireLease('turn-1') - expect(await store.readStreamEvents('turn-1')).toHaveLength(3) - - // Worker 2 picks the run up. The sandbox container outlived worker 1, so - // the adapter still has the whole script. - const adapter2 = new ScriptedAdapter(script) - const sup2 = runSupervisedTurn(opts(store, 'w2', adapter2)) - const out = await consume(sup2.stream) - - expect(sup2.mode()).toBe('resumed') - expect(adapter2.attachCalls).toBe(1) - expect(adapter2.startCalls).toBe(0) - // Worker 2's stream is the COMPLETE turn, each event exactly once. - expect(out).toEqual([0, 1, 2, 3, 4, 5]) - expect(sup2.record()?.status).toBe('completed') - // The durable log holds the full sequence with monotonic seqs — no gap. - const logged = await store.readStreamEvents('turn-1') - expect(logged.map((e) => e.payload)).toEqual([0, 1, 2, 3, 4, 5]) - expect(logged.map((e) => e.seq)).toEqual([0, 1, 2, 3, 4, 5]) - }) - - it('dedups the reconnect seam when the adapter re-yields the boundary event', async () => { - const store = new InMemoryDurableRunStore() - const sup1 = runSupervisedTurn(opts(store, 'w1', new ScriptedAdapter(script))) - await consume(sup1.stream, 3) - store._expireLease('turn-1') - - // adapter2.attach re-yields the boundary event e2 (inclusive resume). - const adapter2 = new ScriptedAdapter(script, { reYieldBoundary: true }) - const sup2 = runSupervisedTurn(opts(store, 'w2', adapter2)) - const out = await consume(sup2.stream) - - // The re-yielded e2 is idempotent on append → not double-forwarded. - expect(out).toEqual([0, 1, 2, 3, 4, 5]) - const logged = await store.readStreamEvents('turn-1') - expect(logged.map((e) => e.eventId)).toEqual(['e0', 'e1', 'e2', 'e3', 'e4', 'e5']) - }) - - it('survives two successive mid-stream deaths', async () => { - const store = new InMemoryDurableRunStore() - await consume(runSupervisedTurn(opts(store, 'w1', new ScriptedAdapter(script))).stream, 2) - store._expireLease('turn-1') - await consume(runSupervisedTurn(opts(store, 'w2', new ScriptedAdapter(script))).stream, 4) - store._expireLease('turn-1') - const sup3 = runSupervisedTurn(opts(store, 'w3', new ScriptedAdapter(script))) - const out = await consume(sup3.stream) - expect(out).toEqual([0, 1, 2, 3, 4, 5]) - expect((await store.readStreamEvents('turn-1')).map((e) => e.seq)).toEqual([0, 1, 2, 3, 4, 5]) - }) -}) - -describe('runSupervisedTurn — lease + heartbeat', () => { - it('renews the lease while draining a long turn', async () => { - const store = new InMemoryDurableRunStore() - const renew = vi.spyOn(store, 'renewLease') - let clock = 0 - const sup = runSupervisedTurn( - opts(store, 'w1', new ScriptedAdapter(script), { - heartbeatMs: 10, - now: () => { - clock += 100 // each call advances well past the heartbeat window - return clock - }, - }), - ) - await consume(sup.stream) - expect(renew).toHaveBeenCalled() - }) - - it('aborts without writing terminal state when the lease is lost mid-drain', async () => { - const store = new InMemoryDurableRunStore() - vi.spyOn(store, 'renewLease').mockResolvedValue({ ok: false }) - let clock = 0 - const sup = runSupervisedTurn( - opts(store, 'w1', new ScriptedAdapter(script), { - heartbeatMs: 1, - now: () => { - clock += 100 - return clock - }, - }), - ) - await expect(consume(sup.stream)).rejects.toThrow(/lease lost/) - // The supervisor that lost the lease must not mark the run failed — - // the new owner owns the terminal state. - expect(store._inspect('turn-1')?.status).not.toBe('failed') - }) -}) - -describe('SandboxReconnectAdapter — conformance', () => { - it('start yields a running handle with a runId on an early frame', async () => { - const adapter = new ScriptedAdapter(script) - const first = await adapter.start().next() - expect(first.done).toBe(false) - expect(first.value?.handle).toMatchObject({ status: 'running', runId: 'sbx-run-1' }) - }) - - it('attach yields only events strictly after the cursor', async () => { - const adapter = new ScriptedAdapter(script) - const out: number[] = [] - for await (const e of adapter.attach(HANDLE, 'e2')) out.push(e.payload) - expect(out).toEqual([3, 4, 5]) - }) - - it('attach from an undefined cursor yields the whole run', async () => { - const adapter = new ScriptedAdapter(script) - const out: number[] = [] - for await (const e of adapter.attach(HANDLE, undefined)) out.push(e.payload) - expect(out).toEqual([0, 1, 2, 3, 4, 5]) - }) -}) diff --git a/src/durable/turn.ts b/src/durable/turn.ts deleted file mode 100644 index 1fdb6f83..00000000 --- a/src/durable/turn.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * `runDurableTurn` — a streaming, backend-agnostic, checkpoint+replay durable - * turn. The single reusable primitive every product's chat handler routes - * through, so per-product durability code drops to zero. - * - * A **turn** is one request→response unit: a producer yields a stream of - * events and, once drained, exposes the turn's final text. `runDurableTurn` - * wraps that with a `DurableRunStore`: - * - * - **Fresh run** — no completed step for this `(runId)`. The producer - * runs; its events forward live to the caller (streaming preserved) - * while final text accumulates; on drain the text is checkpointed. - * - * - **Replay** — a completed step already exists (the worker died after - * the turn finished but before the response reached the client, and the - * client retried the same turn). The cached text is emitted as a single - * synthetic event; the producer is never constructed — no LLM call, no - * double-billing. - * - * - **Mid-stream crash** — a turn that died *while streaming* leaves step 0 - * in `running`/`failed`. There is no partial-stream checkpoint (the - * substrate checkpoints JSON values at step granularity), so the turn - * re-runs from the top. This is the honest durability ceiling: a - * *completed* turn is free to replay; an *interrupted* turn re-runs. - * - * Generic over the event type `TEvent` so a product can stream its own NDJSON - * shape or the runtime's `RuntimeStreamEvent` — `runDurableTurn` never - * inspects events, it only forwards them and reads `finalText()` after drain. - * - * Lease: a turn is a single step, fast enough that the heartbeat in - * `runDurable` is unnecessary — `runDurableTurn` claims the lease once via - * `startOrResume` and releases it on `endRun`. Concurrent workers on the same - * `runId` are rejected with `DurableRunLeaseHeldError` (the client retried - * before the first attempt finished); callers surface that as "turn already - * in flight." - */ - -import { canonicalHash } from './identity' -import type { DurableRunManifest, DurableRunStore, RunRecord } from './types' - -/** The live side of a turn — what a fresh run produces. */ -export interface DurableTurnProducer { - /** The turn's event stream. Forwarded verbatim to the caller. */ - stream: AsyncGenerator - /** The turn's final assistant text. Read once, after `stream` drains. */ - finalText(): string -} - -export interface RunDurableTurnOptions { - store: DurableRunStore - /** Stable per-turn run id. Convention: `chat::`. The - * same id on a retry is what enables replay. */ - runId: string - manifest: DurableRunManifest - /** Stable per-isolate worker id. Defaults to a fresh `deriveWorkerId()` - * per call when omitted — fine for single-attempt turns. */ - workerId: string - /** Lease window in ms. Default 60_000 — a turn rarely runs longer. */ - leaseMs?: number - /** Human-readable step label. Default `turn`. */ - intent?: string - /** Builds the live producer. Called exactly once, on a fresh run; never - * called on the replay path. */ - produce: () => DurableTurnProducer - /** Synthesizes the single event emitted on the replay path from the - * cached final text (e.g. a product's `{ type: 'result', data: {...} }`). */ - replayEvent: (finalText: string) => TEvent - /** Optional live accumulator. When the producer's `finalText()` is only - * valid after drain, this lets `runDurableTurn` also observe each event - * to build the text — return the running text or `undefined` to ignore - * an event. When omitted, `producer.finalText()` is the sole source. */ - accumulate?: (event: TEvent, current: string) => string | undefined -} - -export interface DurableTurnHandle { - /** Drop-in stream. Fresh runs forward producer events live; replays emit - * exactly one `replayEvent(cachedText)`. */ - stream: AsyncGenerator - /** The turn's final text. Valid after `stream` drains. */ - finalText(): string - /** True iff this turn replayed a cached result (no producer ran). Valid - * after `stream` drains. */ - replayed(): boolean - /** The durable `RunRecord` for this turn. Valid after `stream` drains. */ - record(): RunRecord | undefined -} - -const STEP_INDEX = 0 - -export function runDurableTurn( - options: RunDurableTurnOptions, -): DurableTurnHandle { - const { store, runId, manifest, workerId } = options - const leaseMs = options.leaseMs ?? 60_000 - const intent = options.intent ?? 'turn' - const inputHash = canonicalHash(manifest.input) - - let accumulated = '' - let didReplay = false - let finalRecord: RunRecord | undefined - - async function* stream(): AsyncGenerator { - const { completedSteps } = await store.startOrResume({ - runId, - manifest, - workerId, - leaseMs, - }) - const prior = completedSteps.find((s) => s.stepIndex === STEP_INDEX) - - if (prior && prior.status === 'completed') { - // ── Replay path — producer never constructed ────────────────── - didReplay = true - const cached = prior.result as { finalText?: string } | undefined - accumulated = cached?.finalText ?? '' - yield options.replayEvent(accumulated) - finalRecord = await store.endRun({ runId, workerId, status: 'completed' }) - return - } - - // ── Fresh run — produce live, forward, checkpoint ──────────────── - await store.beginStep({ - runId, - stepIndex: STEP_INDEX, - intent, - kind: 'llm', - inputHash, - }) - try { - const producer = options.produce() - for await (const event of producer.stream) { - if (options.accumulate) { - const next = options.accumulate(event, accumulated) - if (typeof next === 'string') accumulated = next - } - yield event - } - // Producer's own finalText wins when it is populated; otherwise the - // live accumulator's value stands. - const producerText = producer.finalText() - if (producerText) accumulated = producerText - await store.completeStep({ - runId, - stepIndex: STEP_INDEX, - result: { finalText: accumulated }, - }) - finalRecord = await store.endRun({ - runId, - workerId, - status: 'completed', - outcome: { notes: intent, metadata: { chars: accumulated.length } }, - }) - } catch (err) { - await store.failStep({ - runId, - stepIndex: STEP_INDEX, - error: { message: err instanceof Error ? err.message : String(err) }, - }) - finalRecord = await store.endRun({ runId, workerId, status: 'failed' }) - throw err - } - } - - return { - stream: stream(), - finalText: () => accumulated, - replayed: () => didReplay, - record: () => finalRecord, - } -} diff --git a/src/durable/types.ts b/src/durable/types.ts deleted file mode 100644 index 617fa6da..00000000 --- a/src/durable/types.ts +++ /dev/null @@ -1,310 +0,0 @@ -/** - * Durable-run substrate: the typed contract for checkpointed agent runs that - * survive worker crashes, deploy rolls, OOM, and transient transport errors. - * - * The model — directly inspired by Absurd (Postgres-backed) and Cloudflare - * Workflows — splits a run into ordered, idempotent **steps**. Each step's - * result is persisted before the next step runs. On resume, the runner reads - * the prior steps from a `DurableRunStore` and fast-replays them (returning - * cached values) until it reaches the first unfinished step, where execution - * actually resumes. - * - * Three boundary disciplines: - * - * 1. Step results MUST be JSON-serializable. No closures, no class - * instances, no live streams. The store treats results as opaque JSON. - * - * 2. Step intents MUST be stable across replays. The runner derives a - * stable step id from (runId, stepIndex, intent). Mismatched intent at - * the same index = `DurableRunDivergenceError`. - * - * 3. Non-determinism (now / uuid / random) MUST flow through the - * `DurableContext` helpers — `ctx.now()`, `ctx.uuid()` — so the values - * are checkpointed and identical on replay. Bare `Date.now()` / - * `crypto.randomUUID()` inside a task fn breaks replay equality. - */ - -import type { AgentTaskSpec } from '../types' - -/** Caller-facing kinds. The runner uses these for telemetry + querying. */ -export type StepKind = - /** Logical step that ran user code (the default for ctx.step). */ - | 'logic' - /** A wrapped LLM call. */ - | 'llm' - /** A wrapped tool call. */ - | 'tool' - /** A wrapped readiness probe. */ - | 'readiness' - /** A deterministic clock or uuid read. */ - | 'deterministic' - /** A suspend-for-event boundary. */ - | 'event' - -export type StepStatus = 'pending' | 'running' | 'completed' | 'failed' - -export interface StepError { - message: string - code?: string - /** Optional stack — stored for diagnostics, NEVER replayed as an exception. */ - stack?: string -} - -export interface StepRecord { - runId: string - /** Monotonic 0-based index. Position is the load-bearing identifier — the - * same intent string at different positions is a different step. */ - stepIndex: number - /** Caller-supplied label; intended for human reading + log correlation. */ - intent: string - kind: StepKind - /** sha256 of the canonical input fingerprint at begin-time. Used to detect - * divergence (caller changed inputs across replays). Empty for steps where - * the input cannot be canonicalized (e.g. ctx.now()). */ - inputHash: string - status: StepStatus - /** Re-entry count. Increments each time the step begins. */ - attempts: number - /** JSON-serializable result. Present when status === 'completed'. */ - result?: T - error?: StepError - startedAt?: string - completedAt?: string -} - -export interface EventRecord { - runId: string - key: string - payload: unknown - emittedAt: string -} - -/** - * A pointer to a substrate run that outlives the worker isolate — the sandbox - * container is orchestrator-managed and survives a Worker death or a Durable - * Object migration. Persisted on the run row so a fresh supervisor re-attaches - * to the in-flight run instead of re-prompting. - */ -export interface RunHandle { - /** Which substrate owns the run. `sandbox` runs are reconnectable; - * `tcloud` runs have no cross-process replay endpoint. */ - kind: 'sandbox' | 'tcloud' - /** Orchestrator-managed sandbox id — stable across worker isolates. */ - sandboxId?: string - /** Sandbox conversation/session id. */ - sessionId?: string - /** The substrate run id (the sandbox SDK's `executionId`). The replay - * endpoint keys on it. */ - runId?: string - /** Lifecycle of the substrate run as last observed. */ - status: 'running' | 'completed' | 'failed' - /** Last substrate event id seen — the adapter's reconnect cursor. */ - cursor?: string -} - -/** - * One event in a run's ordered, replayable stream log. The supervisor drains - * a run's event stream into this log as it flows, so replay is guaranteed by - * the substrate rather than by the sandbox runtime's own buffering. - */ -export interface StreamEventRecord { - runId: string - /** Monotonic 0-based sequence — the store's ordering + cursor. */ - seq: number - /** Producer-supplied stable id — the dedup key and the substrate cursor. */ - eventId: string - payload: unknown - appendedAt: string -} - -export type RunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'suspended' - -export interface RunOutcome { - pass?: boolean - score?: number - notes?: string - /** Free-form bag of run-level metrics — surfaced in OTLP / TraceStore. */ - metadata?: Record -} - -export interface DurableRunManifest { - /** Stable per-product id (e.g. 'legal-agent', 'creative-agent'). */ - projectId: string - /** Optional scenario / persona / session id — surfaced in telemetry. */ - scenarioId?: string - task: AgentTaskSpec - /** Input payload. Hashed into the run identity so two runs with the same - * runId but different inputs raise DurableRunInputMismatchError. */ - input: Record - /** Free-form tags surfaced into RunRecord / OTLP. */ - tags?: Record -} - -export interface RunRecord { - runId: string - manifestHash: string - projectId: string - scenarioId?: string - status: RunStatus - createdAt: string - updatedAt: string - completedAt?: string - /** Stable per-worker id holding the lease. */ - leaseHolderId?: string - leaseExpiresAt?: string - outcome?: RunOutcome - stepCount: number - /** Pointer to the in-flight substrate run, when one has been registered. - * A fresh supervisor re-attaches by it. */ - handle?: RunHandle -} - -/** - * The durable-run substrate. Implementations: in-memory (dev), file-system - * (eval harness), D1 (Cloudflare prod). All stores share this exact contract - * — swap by changing one factory call. - * - * Concurrency model: at most one worker holds a run's lease at a time. Lease - * renewal happens on a heartbeat; on lease expiry, another worker can - * `startOrResume` and pick up. Steps committed by the prior worker survive. - */ -export interface DurableRunStore { - /** - * Begin or resume a run. Returns the canonical RunRecord, all previously - * completed steps (in order), and the lease deadline. - * - * If the run did not exist, creates it with status='running'. If it existed - * with a different manifest hash, throws DurableRunInputMismatchError. - * If it existed with a live lease held by a different worker, throws - * DurableRunLeaseHeldError (caller can retry or back off). - */ - startOrResume(input: { - runId: string - manifest: DurableRunManifest - workerId: string - leaseMs?: number - }): Promise<{ - run: RunRecord - completedSteps: ReadonlyArray - leaseExpiresAt: string - }> - - /** Renew the lease. Returns false if another worker now holds it. */ - renewLease(input: { - runId: string - workerId: string - leaseMs?: number - }): Promise<{ ok: boolean; leaseExpiresAt?: string }> - - /** Load a step by position. Returns undefined if not yet begun. */ - loadStep(runId: string, stepIndex: number): Promise - - /** Record step start (intent + input hash + kind). Bumps attempt count. */ - beginStep(input: { - runId: string - stepIndex: number - intent: string - kind: StepKind - inputHash: string - }): Promise - - /** Mark step completed with a JSON-serializable result. */ - completeStep(input: { runId: string; stepIndex: number; result: unknown }): Promise - - /** Mark step failed with a captured error. */ - failStep(input: { runId: string; stepIndex: number; error: StepError }): Promise - - /** End the run; releases lease. */ - endRun(input: { - runId: string - workerId: string - status: 'completed' | 'failed' - outcome?: RunOutcome - }): Promise - - /** - * Emit an event. First emit wins; subsequent emits return the existing - * record under `existing` and accepted=false. Caller can treat that as - * idempotency-by-design — never double-fire a downstream side effect. - */ - emitEvent(input: { - runId: string - key: string - payload: unknown - }): Promise<{ accepted: boolean; record: EventRecord }> - - /** Load the cached event payload if it has been emitted. */ - loadEvent(runId: string, key: string): Promise - - /** - * Append an event to the run's ordered stream log. The store assigns the - * monotonic `seq`. Idempotent on `eventId`: re-appending a known id is a - * no-op that returns the existing record under `accepted: false` — so an - * adapter that re-yields a boundary event on reconnect cannot double-log. - */ - appendStreamEvent(input: { - runId: string - eventId: string - payload: unknown - }): Promise<{ accepted: boolean; record: StreamEventRecord }> - - /** - * Read the stream log in `seq` order. `afterSeq` (exclusive) resumes a - * reader from a cursor; omit for the whole log. - */ - readStreamEvents(runId: string, afterSeq?: number): Promise> - - /** Persist the run handle — the pointer a fresh supervisor re-attaches by. - * One per run; overwrites. */ - setRunHandle(input: { runId: string; handle: RunHandle }): Promise - - /** Cleanup hook for in-memory / fs stores; no-op for D1. Idempotent. */ - close(): Promise -} - -// ── Public error contract ──────────────────────────────────────────────── - -/** Base class for durable-run errors. */ -export class DurableRunError extends Error { - constructor( - message: string, - public readonly code: - | 'lease_held' - | 'manifest_mismatch' - | 'step_divergence' - | 'step_input_mismatch' - | 'await_event_timeout' - | 'event_emit_race', - ) { - super(message) - this.name = this.constructor.name - } -} - -/** Thrown when another worker holds the lease for this runId. */ -export class DurableRunLeaseHeldError extends DurableRunError { - constructor(message: string) { - super(message, 'lease_held') - } -} - -/** Thrown when the manifest hash differs from a prior run with the same id. */ -export class DurableRunInputMismatchError extends DurableRunError { - constructor(message: string) { - super(message, 'manifest_mismatch') - } -} - -/** Thrown when the same stepIndex re-runs with a different intent string. */ -export class DurableRunDivergenceError extends DurableRunError { - constructor(message: string) { - super(message, 'step_divergence') - } -} - -/** Thrown when `awaitEvent` times out. */ -export class DurableAwaitEventTimeoutError extends DurableRunError { - constructor(message: string) { - super(message, 'await_event_timeout') - } -} diff --git a/src/durable/workflows.ts b/src/durable/workflows.ts deleted file mode 100644 index 1af75efb..00000000 --- a/src/durable/workflows.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Cloudflare Workflows integration for the durable-run substrate. - * - * Two valid deployment patterns on Cloudflare: - * - * A. **Plain Worker + D1DurableRunStore.** Each request invokes - * `runDurable(...)` directly against a D1 binding. Survives worker - * isolate restarts; lease takeover happens via D1 row-level - * conditional UPDATE. The default path; no Workflows binding needed. - * - * B. **Cloudflare Workflows entrypoint.** Wrap an entire `runDurable(...)` - * call inside a single Workflow `step.do(...)`. Workflows gives you - * retry-on-throw with platform-managed exponential backoff and - * survives full Workers deploy rolls. Use it when the task can take - * minutes to hours, or when you want the Workflows dashboard for - * observability. Inside the step, `runDurable` still uses D1 for - * step-level checkpoints — so a half-completed run resumes from - * its last checkpoint on retry rather than restarting from scratch. - * - * This module provides the surface for pattern B: a thin helper that - * converts a Workflows `WorkflowStep` into a `DurableContext`. We do not - * take a runtime dep on `cloudflare:workers` — the integration is purely - * structural typing. - * - * Example (pattern B): - * - * import { WorkflowEntrypoint } from 'cloudflare:workers' - * import { runOnWorkflowStep } from '@tangle-network/agent-runtime' - * - * export class LegalChatWorkflow extends WorkflowEntrypoint { - * async run(event, step) { - * return runOnWorkflowStep(step, { - * workflowName: 'legal-chat', - * taskFn: async (ctx) => { - * const ready = await ctx.step('readiness', () => probeKnowledge(...)) - * const answer = await ctx.step('llm:turn-1', () => callLlm(...)) - * const shipped = await ctx.awaitEvent('shipped', { timeoutMs: 5 * 60_000 }) - * return { answer, shipped } - * }, - * }) - * } - * } - * - * Step ordering, replay semantics, and divergence detection inside the - * `taskFn` are inherited from Cloudflare's Workflows engine — we - * intentionally do NOT layer a second durable store inside this path. - * Pick pattern A or pattern B per agent; do not mix. - */ - -import type { DurableContext } from './runner' - -/** - * Structural subset of Cloudflare's `WorkflowStep`. Mirrors the public surface - * documented at https://developers.cloudflare.com/workflows/build/. Defined - * here so this module imposes zero `cloudflare:workers` runtime dependency. - */ -export interface WorkflowStepLike { - do(name: string, opts: WorkflowStepConfig, fn: () => Promise): Promise - do(name: string, fn: () => Promise): Promise - sleep(name: string, duration: string | number): Promise - waitForEvent( - name: string, - opts: { type: string; timeout?: string }, - ): Promise<{ - payload: T - timestamp: number - type: string - }> -} - -export interface WorkflowStepConfig { - retries?: { - limit: number - delay: string | number - backoff?: 'constant' | 'linear' | 'exponential' - } - timeout?: string | number -} - -export interface RunOnWorkflowStepInput { - /** Logical workflow name; used as a prefix on step ids for filtering. */ - workflowName: string - /** User task — same shape as runDurable's taskFn. */ - taskFn: (ctx: DurableContext) => Promise - /** Optional per-step retry / timeout policy applied to ctx.step calls. */ - stepConfig?: WorkflowStepConfig - /** Optional clock — defaults to Date.now. */ - now?: () => number -} - -/** - * Adapt a Cloudflare `WorkflowStep` into a `DurableContext` and run a task. - * - * Every `ctx.step(intent, fn)` becomes `step.do(, fn)` with stable - * names — Workflows checkpoints + replays based on step name + position, - * matching our model. - * - * `ctx.awaitEvent(key)` becomes `step.waitForEvent(key, { type: key })`. - * Caller is responsible for emitting from the platform side (e.g. via the - * Workflows REST API or a sibling worker that publishes events). - * - * `ctx.now()` and `ctx.uuid()` go through `step.do` so the values are - * captured in the platform's checkpoint state and remain stable across - * replay — same invariant as our own stores. - */ -export async function runOnWorkflowStep( - workflowStep: WorkflowStepLike, - input: RunOnWorkflowStepInput, -): Promise { - const stepCfg = input.stepConfig - let counter = 0 - const stepName = (intent: string) => `${input.workflowName}/${counter++}:${intent}` - - const ctx: DurableContext = { - runId: `cf-workflow:${input.workflowName}`, - projectId: input.workflowName, - async step(intent: string, fn: () => Promise): Promise { - const name = stepName(intent) - if (stepCfg) { - return workflowStep.do(name, stepCfg, fn) - } - return workflowStep.do(name, fn) - }, - async awaitEvent(key: string, opts?: { timeoutMs?: number }): Promise { - const timeout = opts?.timeoutMs ? `${Math.ceil(opts.timeoutMs / 1000)}s` : undefined - const ev = await workflowStep.waitForEvent(stepName(`event:${key}`), { - type: key, - timeout, - }) - return ev.payload - }, - async emitEvent(): Promise<{ accepted: boolean }> { - // Workflows events are emitted from OUTSIDE the workflow (via the - // platform API). A workflow that emits its own events is an - // anti-pattern — surface that fail-loud rather than silently no-op. - throw new Error( - 'runOnWorkflowStep: ctx.emitEvent is not available inside a Workflows entrypoint. ' + - 'Emit from the sibling worker that drives the workflow.', - ) - }, - async now(): Promise { - const iso = await this.step('deterministic:now', async () => new Date().toISOString()) - return new Date(iso) - }, - async uuid(): Promise { - return this.step('deterministic:uuid', async () => { - if (typeof globalThis.crypto?.randomUUID === 'function') { - return globalThis.crypto.randomUUID() - } - // Cloudflare runtime always has Web Crypto; fallback is defensive. - const bytes = new Uint8Array(16) - crypto.getRandomValues(bytes) - bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40 - bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80 - const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('') - return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}` - }) - }, - } - - return input.taskFn(ctx) -} diff --git a/tests/workers/entry.ts b/tests/workers/entry.ts deleted file mode 100644 index 502cac59..00000000 --- a/tests/workers/entry.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Test-only worker entry. Builds a `SessionSupervisorDO` over an in-memory - * store + a controllable scripted adapter, so a vitest-pool-workers test can - * drive the real Cloudflare DO host under real `workerd`. Not a deployable - * Worker — referenced only by `wrangler.workers.toml`. - * - * The adapter is parameterised through the request URL (`?events=N&runId=…`) - * so the test injects different scenarios without recompiling. The in-memory - * store + scripted-adapter factory live at module scope; they survive across - * requests inside one workerd run, matching how a real product's DO would - * share a substrate binding across requests. - */ - -import { - createSessionSupervisorDO, - type DurableRunManifest, - InMemoryDurableRunStore, - type RunHandle, - type SandboxReconnectAdapter, - type SupervisedEvent, -} from '../../src/durable' - -interface TestEnv { - SESSION_SUPERVISOR: DurableObjectNamespace -} - -const store = new InMemoryDurableRunStore() - -const manifest = { - projectId: 'test', - scenarioId: 'persona-1', - task: { id: 'task-1', intent: 'chat', domain: 'test' }, - input: { q: 'hello' }, -} as unknown as DurableRunManifest - -function scriptedAdapter(n: number): SandboxReconnectAdapter { - const ids = Array.from({ length: n }, (_, i) => i) - return { - async *start(): AsyncGenerator> { - for (const i of ids) { - const handle: RunHandle | undefined = - i === 0 ? { kind: 'sandbox', runId: 'sbx-test', status: 'running' } : undefined - yield { eventId: `e${i}`, payload: i, handle } - } - }, - async *attach( - _handle: RunHandle, - afterEventId: string | undefined, - ): AsyncGenerator> { - const found = afterEventId ? ids.findIndex((i) => `e${i}` === afterEventId) : -1 - for (let i = found + 1; i < ids.length; i++) yield { eventId: `e${i}`, payload: i } - }, - } -} - -export const TestSessionSupervisor = createSessionSupervisorDO({ - async resolveRun(request) { - const url = new URL(request.url) - const runId = url.searchParams.get('runId') ?? 'r1' - const events = Number(url.searchParams.get('events') ?? '4') - return { - store, - runId, - manifest, - workerId: 'w-fetch', - adapter: scriptedAdapter(events), - } - }, - async resolveOrphan(runId) { - return { - store, - runId, - manifest, - workerId: 'w-alarm', - adapter: scriptedAdapter(4), - } - }, - encodeEvent: (event) => `data: ${event}\n`, -}) - -export default { - async fetch(request: Request, env: TestEnv): Promise { - const url = new URL(request.url) - const sessionId = url.searchParams.get('session') ?? 'default' - const id = env.SESSION_SUPERVISOR.idFromName(sessionId) - const stub = env.SESSION_SUPERVISOR.get(id) - return stub.fetch(request) - }, -} satisfies ExportedHandler diff --git a/tests/workers/session-supervisor-do.workers.test.ts b/tests/workers/session-supervisor-do.workers.test.ts deleted file mode 100644 index af9f1d76..00000000 --- a/tests/workers/session-supervisor-do.workers.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { SELF } from 'cloudflare:test' -import { describe, expect, it } from 'vitest' - -/** - * Test the SessionSupervisorDO end-to-end under real workerd: real - * `DurableObjectState`, real `ReadableStream`, real `Response`. The - * fake-state suite in `src/durable/tests/session-supervisor-do.test.ts` - * exercises the supervisor logic exhaustively (fresh / resumed / replayed / - * orphan re-attach via `alarm()`); this file's job is to prove the same DO - * host works against the actual Cloudflare runtime, catching any platform - * quirk the structural fakes miss. - */ -describe('SessionSupervisorDO under real workerd', () => { - it('streams a fresh supervised run end-to-end through a real DO', async () => { - const res = await SELF.fetch('http://test/?session=s-fresh&runId=r-fresh&events=4') - expect(res.status).toBe(200) - expect(res.headers.get('content-type')).toBe('text/event-stream') - const text = await res.text() - expect(text).toBe('data: 0\ndata: 1\ndata: 2\ndata: 3\n') - }) - - it('a second fetch for the same runId replays the same stream from the durable log', async () => { - await SELF.fetch('http://test/?session=s-replay&runId=r-replay&events=3').then((r) => r.text()) - // Second fetch — the run already completed, so the supervisor enters the - // replay path and re-yields the logged stream verbatim. - const replayed = await SELF.fetch('http://test/?session=s-replay&runId=r-replay&events=3').then( - (r) => r.text(), - ) - expect(replayed).toBe('data: 0\ndata: 1\ndata: 2\n') - }) -}) diff --git a/vitest.config.ts b/vitest.config.ts index 0c08e073..280bf587 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,11 +1,7 @@ import { defineConfig } from 'vitest/config' -/** Default Node-pool vitest run. The workerd-only suite under - * `tests/workers/**` runs separately via `pnpm test:workers` against - * `vitest.workers.config.ts` — it imports `cloudflare:test`, which only - * resolves inside `@cloudflare/vitest-pool-workers`. */ export default defineConfig({ test: { - exclude: ['node_modules/**', 'dist/**', 'tests/workers/**'], + exclude: ['node_modules/**', 'dist/**'], }, }) diff --git a/vitest.workers.config.ts b/vitest.workers.config.ts deleted file mode 100644 index ba9252ef..00000000 --- a/vitest.workers.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config' - -/** Separate vitest config used by `pnpm test:workers` so the main suite stays - * on the default Node pool; only the workerd-only `*.workers.test.ts` files - * run inside the real Cloudflare runtime via `@cloudflare/vitest-pool-workers`. */ -export default defineWorkersConfig({ - test: { - include: ['tests/workers/**/*.workers.test.ts'], - poolOptions: { - workers: { - wrangler: { configPath: './wrangler.workers.toml' }, - }, - }, - }, -}) diff --git a/wrangler.workers.toml b/wrangler.workers.toml deleted file mode 100644 index 68ff9c26..00000000 --- a/wrangler.workers.toml +++ /dev/null @@ -1,17 +0,0 @@ -# Wrangler config used ONLY by the `test:workers` vitest pool — it pins a -# real `workerd` runtime around the `SessionSupervisorDO` so the DO host is -# exercised under actual Cloudflare semantics (real DurableObjectState, real -# ReadableStream, real Response). Not a deployable Worker. - -name = "agent-runtime-workers-tests" -main = "tests/workers/entry.ts" -compatibility_date = "2025-05-01" -compatibility_flags = ["nodejs_compat"] - -[[durable_objects.bindings]] -name = "SESSION_SUPERVISOR" -class_name = "TestSessionSupervisor" - -[[migrations]] -tag = "v1" -new_classes = ["TestSessionSupervisor"] From 9e362d5726abb578dcc8d2c42ebe0b7513e85139 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Fri, 22 May 2026 18:37:16 -0600 Subject: [PATCH 2/4] chore: drop historical framing in chat-engine docstring --- src/durable/chat-engine.ts | 20 +++++++++----------- tangle-network-agent-runtime-0.16.0.tgz | Bin 0 -> 130985 bytes 2 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 tangle-network-agent-runtime-0.16.0.tgz diff --git a/src/durable/chat-engine.ts b/src/durable/chat-engine.ts index 2697fcda..cc545b15 100644 --- a/src/durable/chat-engine.ts +++ b/src/durable/chat-engine.ts @@ -1,17 +1,15 @@ /** * `ChatTurnEngine` — the framework-neutral chat-turn orchestrator every - * product chat handler routes through. Owns the parts that were copy-pasted - * across legal / gtm / creative / tax: the NDJSON `ChatStreamEvent` line - * protocol, the `session.run.*` lifecycle vocabulary, the persist / - * post-process / trace-flush hook ordering. + * product chat handler routes through. Owns the NDJSON `ChatStreamEvent` + * line protocol, the `session.run.*` lifecycle vocabulary, and the persist + * / post-process / trace-flush hook ordering. * - * Execution durability is NOT this engine's problem. The substrate - * (@tangle-network/sandbox + orchestrator) owns it: `box.streamPrompt({ - * executionId, lastEventId })` buffers the stream by `executionId`, - * replays strictly after `lastEventId` on reconnect, and never spawns a - * duplicate. `AgentExecutionHandle` is the typed pointer products - * persist; this engine just wraps a producer that already speaks that - * primitive. + * Execution durability is the substrate's concern: `box.streamPrompt({ + * executionId, lastEventId })` from `@tangle-network/sandbox` buffers the + * stream by `executionId`, replays strictly after `lastEventId` on + * reconnect, and never spawns a duplicate. `AgentExecutionHandle` is the + * typed pointer products persist; this engine wraps a producer that + * speaks that primitive. * * What the engine owns: * - the `session.run.started` / `session.run.completed` / `session.run.failed` diff --git a/tangle-network-agent-runtime-0.16.0.tgz b/tangle-network-agent-runtime-0.16.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..987220ac117f36a343cbf5418c79a71003b4718a GIT binary patch literal 130985 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

< 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 literal 0 HcmV?d00001 From 5862cda734f2c084f2307c5fac53d2f474bd35d3 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Fri, 22 May 2026 18:59:12 -0600 Subject: [PATCH 3/4] docs: clarify AgentExecutionHandle is forward-looking (SDK exposure pending) @tangle-network/sandbox@0.1.2 PromptOptions does not yet surface executionId / lastEventId. In-call reconnect is automatic; cross-process reconnect requires either bypassing the SDK (raw HTTP + X-Execution-ID header) or a future SDK release. Doc clarified to reflect actual state. Also: fix stale 'durable substrate' framing in src/index.ts and README.md headline. --- README.md | 9 ++++---- src/durable/execution-handle.ts | 39 ++++++++++++++++----------------- src/index.ts | 12 +++++----- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 1e2b0795..83081620 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ Production runtime substrate for domain agents. Owns the task lifecycle (knowledge readiness, control loop, session resume, sanitized telemetry, -durable runs across worker / DO crashes, canonical `RuntimeRunRow` -persistence + cost ledger), the chat-model catalog + admission, and the -declarative `defineAgent` manifest — so domain repos stop inventing their -own. +canonical `RuntimeRunRow` persistence + cost ledger), the chat-turn +engine (NDJSON envelope + product hooks), the chat-model catalog + +admission, and the declarative `defineAgent` manifest — so domain +repos stop inventing their own. Long-running execution durability +(reconnect, replay, dedup) lives in `@tangle-network/sandbox`. ```bash pnpm add @tangle-network/agent-runtime @tangle-network/agent-eval diff --git a/src/durable/execution-handle.ts b/src/durable/execution-handle.ts index a2af1c8d..0bfdfd8b 100644 --- a/src/durable/execution-handle.ts +++ b/src/durable/execution-handle.ts @@ -1,29 +1,28 @@ /** * `AgentExecutionHandle` — the typed pointer to a substrate-owned, long- - * running agent execution. + * running agent execution. Products persist this id so a client retry of + * the same turn can land on the same substrate execution. * - * Execution state lives in the substrate. `@tangle-network/sandbox`'s - * `box.streamPrompt({ executionId, lastEventId })` (and the orchestrator - * behind it) already buffers the event stream by `executionId`, replays - * strictly after `lastEventId` on reconnect, and never spawns a duplicate - * execution for the same id. agent-runtime owns this typed pointer so - * callers (chat handlers, replay glue, telemetry) can talk about a run's - * continuity without depending on the SDK shape directly. + * State of the world (2026-05-22): * - * Usage: + * - **In-call reconnect** — automatic. `@tangle-network/sandbox`'s + * `box.streamPrompt` extracts `executionId` from the response's + * `execution.started` event and replays via the runtime endpoint if + * the stream drops mid-call. Callers do not pass anything; the SDK + * dedupes replayed events transparently. * - * const handle: AgentExecutionHandle = { - * executionId: deriveExecutionId({ projectId, sessionId, turnIndex }), - * sessionId, - * } - * for await (const event of box.streamPrompt(prompt, { - * executionId: handle.executionId, - * lastEventId: handle.lastEventId, - * })) { ... } + * - **Cross-process reconnect** — partially supported. The orchestrator + * route `/agents/run/stream` reads `X-Execution-ID` header + `Last- + * Event-ID` and replays from its event buffer (10k events, 2-min + * post-completion retention). A product that wants this today must + * bypass the SDK and POST directly with those headers (see + * tax-agent's `sessions.ts`). The public `PromptOptions` does not + * yet expose them; once it does, products plumb the handle through. * - * On reconnect the product persists the last event id it acknowledged and - * re-passes both — the orchestrator replays from `lastEventId + 1` without - * re-running the agent. + * Until the SDK surface lands, this handle is most useful for product- + * side persistence (D1 row, session metadata) and tracing — derive the + * id at turn start, store it, and the wire integration is ready when + * the SDK is. */ export interface AgentExecutionHandle { diff --git a/src/index.ts b/src/index.ts index c5118aba..381b1548 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,11 +44,13 @@ export { runChatTurn, sandboxAsChatTurnTarget, } from './chat-turn' -// ── Durable-run substrate ───────────────────────────────────────────── -// Step-checkpointed agent runs that survive worker crashes, deploy rolls, -// rate-limit cascades, and transient transport errors. See ./durable for -// the full contract; in-memory + filesystem stores ship out of the box, -// D1 store + Cloudflare Workflows adapter land as opt-in subpath exports. +// ── Turn-lifecycle helpers ──────────────────────────────────────────── +// `ChatTurnEngine` wraps a producer with the `session.run.*` envelope + +// NDJSON framing + persist/post-process/trace-flush hook ordering. +// `AgentExecutionHandle` + `deriveExecutionId` are the typed pointer +// products persist so a client retry of the same turn can land on the +// same substrate execution. Long-running execution durability itself +// (reconnect, replay, dedup) lives in @tangle-network/sandbox. export * from './durable' // ── Errors ─────────────────────────────────────────────────────────── export { From 13a12831daecba280d1645e401311587900801e8 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Sat, 23 May 2026 05:03:06 -0600 Subject: [PATCH 4/4] refactor: senior-quality cleanup of chat-turn surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-1 audit critiques applied: - ChatTurnEngine class + chatTurnEngine singleton → handleChatTurn function. The class wrapped a single stateless method; the singleton was the singleton-of-a-stateless-class antipattern. One symbol now. - Renamed to `handleChatTurn` (not `runChatTurn`) to disambiguate from the existing `chat-turn.ts:runChatTurn` sandbox-stream primitive — different concern, different name. - Dropped `AgentExecutionHandle` interface — only the executionId string is used at call sites; the wrapper type bought nothing. - Dropped `AgentExecutionHandle.lastEventId` — the public sandbox SDK does not surface it, and no consumer used it. Adding back when SDK exposes it. - Dropped `ReconnectableAgentStream` — exported with zero callers, premature abstraction. - Dropped `ChatTurnHooks.accumulate` — unused by all consumers; the producer's `finalText()` is the single source of truth. - Default `log` to `console.error` so swallowed hook errors don't vanish silently when no logger is wired. durable/ source now: chat-engine.ts (handleChatTurn), execution-handle.ts (deriveExecutionId only), index.ts (exports). README + concepts + the chat-handler example all simplified to match. 179 tests pass. --- README.md | 62 ++--- docs/concepts.md | 81 +++--- examples/README.md | 2 +- examples/chat-handler/README.md | 16 +- examples/chat-handler/chat-handler.ts | 38 +-- src/durable/chat-engine.ts | 288 +++++++++------------ src/durable/execution-handle.ts | 73 +----- src/durable/index.ts | 26 +- src/durable/tests/chat-engine.test.ts | 83 +++--- src/durable/tests/execution-handle.test.ts | 12 +- src/index.ts | 13 +- tangle-network-agent-runtime-0.16.0.tgz | Bin 130985 -> 128141 bytes 12 files changed, 257 insertions(+), 437 deletions(-) diff --git a/README.md b/README.md index 83081620..d9d40c0e 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ pnpm add @tangle-network/agent-runtime @tangle-network/agent-eval |---|---| | `runAgentTask` | Single-shot adapter-driven task with eval/verification | | `runAgentTaskStream` | Streaming product loop with session resume + backends | -| `ChatTurnEngine` / `chatTurnEngine` | Framework-neutral chat-turn orchestrator (NDJSON + `session.run.*` envelope + product hooks) | -| `AgentExecutionHandle` + `deriveExecutionId` | Typed pointer to a substrate-owned execution — the contract for cross-process replay/reconnect | +| `handleChatTurn` | Framework-neutral chat-turn orchestrator (NDJSON + `session.run.*` envelope + product hooks) | +| `deriveExecutionId` | Stable substrate executionId for `X-Execution-ID` cross-process reconnect | | `startRuntimeRun` | Canonical production-run row + cost ledger | | `defineAgent` | Declarative per-vertical agent manifest — surfaces, knowledge, rubric, run fn | | `resolveChatModel` / `validateChatModelId` / `getModels` | Router catalog fetch + fail-closed admission + precedence resolver | @@ -53,26 +53,20 @@ console.log(result.status, result.runRecords) ## Chat turns -`ChatTurnEngine` wraps a product `produce()` hook with the -`session.run.*` lifecycle envelope, drains the producer stream through -the NDJSON line protocol, and calls the persist / post-process hooks -after drain. Framework-neutral: the engine takes already-resolved -values, never a `Request` or `Context`. +`handleChatTurn` wraps a product `produce()` hook with the `session.run.*` +lifecycle envelope, drains the producer stream through the NDJSON line +protocol, and calls the persist / post-process hooks after drain. +Framework-neutral: takes already-resolved values, never a `Request` or +`Context`. ```ts -import { chatTurnEngine, deriveExecutionId } from '@tangle-network/agent-runtime' +import { handleChatTurn } from '@tangle-network/agent-runtime' -const executionId = deriveExecutionId({ - projectId: 'gtm-agent', - sessionId: threadId, - turnIndex, -}) - -const result = chatTurnEngine.runTurn({ +const result = handleChatTurn({ identity: { tenantId: workspaceId, sessionId: threadId, userId, turnIndex }, hooks: { produce: () => ({ - stream: box.streamPrompt(prompt, { executionId, lastEventId, ...sandboxOptions }), + stream: box.streamPrompt(prompt, sandboxOptions), finalText: () => assembled, }), persistAssistantMessage: async ({ identity, finalText }) => db.insert(messages).values(...), @@ -87,30 +81,24 @@ return new Response(result.body, { headers: { 'content-type': result.contentType ## Execution continuity Long-running execution durability — reconnect, replay, dedup — lives in -the substrate. `@tangle-network/sandbox`'s `box.streamPrompt({ -executionId, lastEventId })` buffers the stream by `executionId`, -replays strictly after `lastEventId` on reconnect, and never spawns a -duplicate execution. agent-runtime owns the typed pointer: +the substrate. `@tangle-network/sandbox`'s `box.streamPrompt` +auto-reconnects in-call (extracts `executionId` from the response and +replays via the runtime endpoint on drop). Cross-process reconnect — +worker dies, a fresh worker resumes the same execution — requires +either bypassing the SDK and POSTing directly with `X-Execution-ID` +(see `tax-agent/sessions.ts`) or a future SDK release that surfaces the +field on `PromptOptions`. + +`deriveExecutionId` is the convention helper for the stable id the +product persists alongside its session row: ```ts -import { type AgentExecutionHandle, deriveExecutionId } from '@tangle-network/agent-runtime' +import { deriveExecutionId } from '@tangle-network/agent-runtime' -const handle: AgentExecutionHandle = { - executionId: deriveExecutionId({ projectId, sessionId, turnIndex }), - sessionId, - // lastEventId set on retry from the client's last-seen id -} -for await (const event of box.streamPrompt(prompt, { - executionId: handle.executionId, - lastEventId: handle.lastEventId, -})) { ... } +const executionId = deriveExecutionId({ projectId, sessionId, turnIndex }) +// pass as `X-Execution-ID` header when calling the orchestrator directly ``` -The product persists `executionId` on the session row so a client retry -of the same turn lands on the same substrate execution — the -orchestrator's buffer replays the stream instead of starting a second -prompt. - ## Chat-model resolution One primitive every chat handler needs and was hand-rolling per repo: @@ -156,7 +144,7 @@ export const myAgent = defineAgent({ knowledge: { /* requirements + provider */ }, rubric: { /* dimensions + weights */ }, run: async (ctx) => { - /* product-specific run — typically wraps chatTurnEngine.runTurn or runAgentTaskStream */ + /* product-specific run — typically wraps handleChatTurn or runAgentTaskStream */ }, }) ``` @@ -260,7 +248,7 @@ Runnable in [`examples/`](./examples/). Every example imports from - [`runtime-run/`](./examples/runtime-run/) — production-run row + cost ledger - [`model-resolution/`](./examples/model-resolution/) — router catalog + fail-closed admission - [`agent-into-reviewer/`](./examples/agent-into-reviewer/) — pipe one runtime's stream into a reviewer agent -- [`chat-handler/`](./examples/chat-handler/) — `chatTurnEngine.runTurn` + `deriveExecutionId` (the centerpiece production pattern) +- [`chat-handler/`](./examples/chat-handler/) — `handleChatTurn` (the centerpiece production pattern) - [`production-trace-sink/`](./examples/production-trace-sink/) — `createProductionTraceSink` data capture ## Tests diff --git a/docs/concepts.md b/docs/concepts.md index ec7f2b8d..538fc396 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -21,14 +21,15 @@ rest. Read this file once and the rest of the API falls into place. └───────────────────────────────────────┬─────────────────┘ │ ┌───────────────────────────────────────┴─────────────────┐ - │ Chat-turn engine ─ ChatTurnEngine.runTurn(...) │ - │ NDJSON + session.run.* envelope + persist/trace hooks │ + │ Chat-turn lifecycle ─ handleChatTurn(...) │ + │ NDJSON + session.run.* envelope + persist/trace hooks │ └───────────────────────────────────────┬─────────────────┘ │ ┌───────────────────────────────────────┴─────────────────┐ │ Execution continuity (substrate-owned) │ - │ box.streamPrompt({ executionId, lastEventId }) │ - │ agent-runtime provides: AgentExecutionHandle + deriveExecutionId + │ box.streamPrompt — auto-reconnect in-call; X-Execution-ID + │ header for cross-process. deriveExecutionId is the + │ convention helper. │ └───────────────────────────────────────┬─────────────────┘ │ ┌───────────────────────────────────────┴─────────────────┐ @@ -40,7 +41,7 @@ rest. Read this file once and the rest of the API falls into place. Each layer composes the one below it. You can use the bottom layers alone (a raw backend + the model catalog), or the whole stack -(`defineAgent` → `chatTurnEngine`) — they're the same primitives +(`defineAgent` → `handleChatTurn`) — they're the same primitives nested. ## The task lifecycle @@ -60,51 +61,29 @@ the cost ledger — all substrate. Streaming is the same shape: ## Execution continuity — substrate-owned Long-running execution durability — reconnect, replay, dedup — is the -substrate's job, not agent-runtime's. The `@tangle-network/sandbox` SDK -+ orchestrator already implements every primitive a 15-minute turn -needs: - -- `box.streamPrompt(prompt, { executionId, lastEventId })` buffers the - event stream by `executionId` at the orchestrator. -- On reconnect with the same `executionId` and a known `lastEventId`, - the orchestrator replays strictly after that id without spawning a - duplicate execution. -- The SDK dedupes replayed text deltas and tool completions on the - client side; observers see exactly one of each. - -agent-runtime owns the typed pointer products persist: - -```ts -interface AgentExecutionHandle { - executionId: string - sessionId?: string - lastEventId?: string -} - -deriveExecutionId({ projectId, sessionId, turnIndex }): string -``` - -The product persists `executionId` on its session row so a client retry -of the same turn lands on the same substrate execution — the -orchestrator replays its buffer instead of starting a second prompt. -A retry with a stale `lastEventId` (or none) replays from the start of -the buffer. - -What lives in the Worker: - -- auth / access control -- product DB writes (the assistant message, run row, side effects) -- prompt / profile composition -- routing (which backend handles this turn) - -What lives in the substrate: - -- the long-running execution -- event buffering keyed by `executionId` -- replay-on-reconnect -- dedup across the reconnect seam - -The Worker stays a routing + persistence layer. It does not host +substrate's job, not agent-runtime's. The `@tangle-network/sandbox` +SDK + orchestrator already handle it: + +- **In-call reconnect**: `box.streamPrompt` extracts `executionId` from + the response's `execution.started` event and replays via the runtime + endpoint if the stream drops. Transparent — callers do nothing. +- **Cross-process reconnect**: a fresh Worker can resume a prior + Worker's execution by POSTing to the orchestrator's + `/agents/run/stream` with the `X-Execution-ID` header. The SDK's + public `PromptOptions` does not yet surface this; products bypass the + SDK and call the orchestrator directly when they need it (see + tax-agent's `sessions.ts`). +- The orchestrator's buffer is 10k events / 2-min post-completion. A + retry past that window gets `execution_not_found` and re-runs. + +agent-runtime owns one helper, `deriveExecutionId({ projectId, +sessionId, turnIndex })`, that produces the stable id the product +persists on its session row. + +What lives in the Worker: auth, access control, product DB writes, +prompt composition, routing. What lives in the substrate: the +long-running execution, event buffering, replay-on-reconnect, dedup. +The Worker stays a routing + persistence layer — it does not host execution state. ## The agent manifest @@ -159,7 +138,7 @@ agents because nothing in this list is baked into it. 1. `examples/basic-task/` — the smallest end-to-end. 2. `examples/sandbox-stream-backend/` — what streaming looks like. -3. `examples/chat-handler/` — `chatTurnEngine` + `deriveExecutionId` — the centerpiece chat handler. +3. `examples/chat-handler/` — `handleChatTurn` — the centerpiece chat handler. 4. `examples/runtime-run/` — the production-run row + cost ledger. 5. `examples/model-resolution/` — pick + validate a model. 6. `examples/agent-into-reviewer/` — pipe one runtime's stream into a reviewer agent. diff --git a/examples/README.md b/examples/README.md index 68e3825c..c966b058 100644 --- a/examples/README.md +++ b/examples/README.md @@ -16,7 +16,7 @@ which needs an `OPENAI_API_KEY`. | [`runtime-run/`](./runtime-run/) | `startRuntimeRun` + cost ledger + persistence adapter | | [`model-resolution/`](./model-resolution/) | `resolveChatModel` + `validateChatModelId` (fail-closed) + `getModels` | | [`agent-into-reviewer/`](./agent-into-reviewer/) | Pipe one runtime's stream into a reviewer agent (the "2-runtime" pattern) | -| [`chat-handler/`](./chat-handler/) | `chatTurnEngine.runTurn` + `deriveExecutionId` — the centerpiece production chat handler | +| [`chat-handler/`](./chat-handler/) | `handleChatTurn` — the centerpiece production chat handler | | [`production-trace-sink/`](./production-trace-sink/) | `createProductionTraceSink` — production data capture (RunRecord + OTLP + feedback) | ## Conventions diff --git a/examples/chat-handler/README.md b/examples/chat-handler/README.md index 2d8e0cc1..2610b0a5 100644 --- a/examples/chat-handler/README.md +++ b/examples/chat-handler/README.md @@ -1,18 +1,8 @@ # Chat handler -The centerpiece production pattern every product chat handler implements. -`chatTurnEngine.runTurn` wraps a product `produce()` hook with the -`session.run.*` lifecycle envelope, drains the producer stream through -the NDJSON line protocol, and calls the persist / post-process hooks -after drain. - -The engine owns no execution state. Long-running execution durability -lives in the substrate: `@tangle-network/sandbox`'s `box.streamPrompt({ -executionId, lastEventId })` buffers the stream by `executionId`, -replays strictly after `lastEventId` on reconnect, and never spawns a -duplicate execution. `deriveExecutionId({ projectId, sessionId, -turnIndex })` gives a stable id products persist alongside their -session row. +`handleChatTurn` wraps a product `produce()` hook with the `session.run.*` +lifecycle envelope, drains the producer stream through the NDJSON line +protocol, and calls the persist / post-process hooks after drain. In production, `produce()` is a thin wrapper over `runAgentTaskStream(...)` against a real backend (`createOpenAICompatibleBackend` / diff --git a/examples/chat-handler/chat-handler.ts b/examples/chat-handler/chat-handler.ts index 9ab05c2d..80bbe914 100644 --- a/examples/chat-handler/chat-handler.ts +++ b/examples/chat-handler/chat-handler.ts @@ -1,29 +1,19 @@ /** * Full chat handler — the centerpiece production pattern every product - * chat handler implements. - * - * `ChatTurnEngine.runTurn` wraps the product's `produce()` hook with the - * `session.run.*` lifecycle envelope, drains the producer stream through - * the NDJSON line protocol, and calls the persist / post-process hooks - * after drain. It owns no execution state — that lives in the substrate - * (`@tangle-network/sandbox`'s `box.streamPrompt({ executionId, - * lastEventId })` handles reconnect/replay/dedup). + * chat handler implements. `handleChatTurn` frames events with NDJSON + + * `session.run.*` envelope and calls product hooks after drain. * * In a real product, `produce()` calls `runAgentTaskStream({ task, - * backend, input })` with a real backend (`createOpenAICompatibleBackend` - * / `createSandboxPromptBackend`). Here we yield a small scripted stream - * so the example runs offline with no LLM. + * backend, input })` against a real backend + * (`createOpenAICompatibleBackend` / `createSandboxPromptBackend`). + * Here we yield a small scripted stream so the example runs offline. * * Run with: * pnpm tsx examples/chat-handler/chat-handler.ts */ -import type { ChatStreamEvent, ChatTurnProducer } from '@tangle-network/agent-runtime' -import { chatTurnEngine, deriveExecutionId } from '@tangle-network/agent-runtime' +import { type ChatStreamEvent, type ChatTurnProducer, handleChatTurn } from '@tangle-network/agent-runtime' -// ── The product's `produce` hook — yields the turn's event stream + a -// finalText() once drained. In production this is a thin wrapper over -// `runAgentTaskStream(...)` against a real backend. ────────────────── function produce(userMessage: string): ChatTurnProducer { let accumulated = '' const reply = userMessage.toLowerCase().includes('missing') @@ -46,24 +36,12 @@ function produce(userMessage: string): ChatTurnProducer { } async function runTurn(userMessage: string, turnIndex: number): Promise { - // The execution id products persist alongside their session row so a - // client retry lands on the same substrate execution. The chat-turn - // engine itself does not need it — it goes into the producer hook - // when calling `box.streamPrompt({ executionId, lastEventId })`. - const executionId = deriveExecutionId({ - projectId: 'demo-agent', - sessionId: 'thread-42', - turnIndex, - }) - - const result = chatTurnEngine.runTurn({ + const result = handleChatTurn({ identity: { tenantId: 'demo-tenant', sessionId: 'thread-42', userId: 'demo-user', turnIndex }, hooks: { produce: () => produce(userMessage), persistAssistantMessage: async ({ finalText }) => { - console.log( - `[persist ] turn=${turnIndex} executionId=${executionId} chars=${finalText.length}`, - ) + console.log(`[persist ] turn=${turnIndex} chars=${finalText.length}`) }, }, }) diff --git a/src/durable/chat-engine.ts b/src/durable/chat-engine.ts index cc545b15..57336a2f 100644 --- a/src/durable/chat-engine.ts +++ b/src/durable/chat-engine.ts @@ -1,39 +1,27 @@ /** - * `ChatTurnEngine` — the framework-neutral chat-turn orchestrator every - * product chat handler routes through. Owns the NDJSON `ChatStreamEvent` - * line protocol, the `session.run.*` lifecycle vocabulary, and the persist - * / post-process / trace-flush hook ordering. + * `handleChatTurn` — framework-neutral chat-turn HTTP orchestrator. + * Owns the NDJSON `ChatStreamEvent` line protocol, the `session.run.*` + * lifecycle vocabulary, and the persist / post-process / trace-flush + * hook order. Returns a `ReadableStream` body the product hands to its + * platform `Response`. * - * Execution durability is the substrate's concern: `box.streamPrompt({ - * executionId, lastEventId })` from `@tangle-network/sandbox` buffers the - * stream by `executionId`, replays strictly after `lastEventId` on - * reconnect, and never spawns a duplicate. `AgentExecutionHandle` is the - * typed pointer products persist; this engine wraps a producer that - * speaks that primitive. + * Execution durability is the substrate's concern: `box.streamPrompt` + * auto-reconnects in-call; cross-process reconnect via `X-Execution-ID` + * is the product's job. The producer this engine wraps already speaks + * that protocol — the engine just frames the events. * - * What the engine owns: - * - the `session.run.started` / `session.run.completed` / `session.run.failed` - * event envelope around the producer's events - * - NDJSON encoding into a `ReadableStream` (the body every - * product returns, React Router or Hono alike) - * - calling the product's persist / post-process hooks in the right order, - * after the stream drains, with the assembled final text - * - never throwing into the HTTP layer — a producer failure becomes an - * `error` + `session.run.failed` event pair, the stream still closes + * Hooks (`ChatTurnHooks`): + * - `produce` — build the backend event stream + * - `persistAssistantMessage` — write the assistant turn to the product DB + * - `onTurnComplete?` — post-process (proposals, citations, …) + * - `onEvent?` — per-event side channel (e.g. DO broadcast) + * - `transformFinalText?` — pre-persist transform (e.g. PII redact) + * - `traceFlush?` — handed to waitUntil so OTLP export lands * - * What the product supplies (`ChatTurnHooks`): - * - `produce` — build the backend stream for this turn (sandbox / router - * / tcloud / runtime — the engine does not care which) - * - `persistAssistantMessage` — write the assistant turn to the product DB - * - `onTurnComplete` (optional) — post-process (proposals, citations, …) - * - `onEvent` (optional) — per-event side-channel (e.g. DO broadcast) - * - `transformFinalText` (optional) — pre-persist transform (e.g. PII redact) - * - `traceFlush` (optional) — handed to waitUntil so OTLP export lands - * - * Framework neutrality: the engine takes already-resolved values (`identity` - * tuple, a `waitUntil`), never a `Request` or a `Context`. The product's - * thin route adapter does auth + parse + access-control, then calls - * `engine.runTurn(...)` and returns `result.body` as its platform `Response`. + * Framework neutrality: takes already-resolved values (`identity` tuple, + * a `waitUntil`), never a `Request` or a `Context`. The product's thin + * route adapter does auth + parse + access-control, then calls + * `handleChatTurn(...)` and returns `result.body` as its platform `Response`. */ /** The NDJSON line protocol every product chat client already speaks. */ @@ -57,65 +45,45 @@ export interface ChatTurnIdentity { export interface ChatTurnProducer { /** The turn's event stream. Forwarded verbatim to the caller. */ stream: AsyncGenerator - /** The turn's final assistant text. Read once, after `stream` drains. - * When the producer cannot populate this synchronously after drain, - * return '' and use `ChatTurnHooks.accumulate` to assemble text from - * events instead. */ + /** The turn's final assistant text. Read once, after `stream` drains. */ finalText(): string } export interface ChatTurnHooks { - /** - * Build the backend stream for this turn. The engine never inspects which - * backend this is — sandbox container, tcloud router, direct runtime, a - * test double — it only forwards the events and reads `finalText()`. - */ + /** Build the backend stream. The engine forwards events verbatim and + * reads `finalText()` once the stream drains. */ produce(): ChatTurnProducer - /** - * Persist the completed assistant message to the product's own store. - * Called once, after the stream drains, with the assembled (and - * `transformFinalText`-transformed) text. - */ - persistAssistantMessage(input: { identity: ChatTurnIdentity; finalText: string }): Promise - /** - * Optional post-processing after persistence — proposal extraction, - * citation validation, credit metering, etc. Product policy; the engine - * has no shared logic here. Errors are swallowed + logged (post-process - * must never fail the turn that already streamed successfully). - */ + /** Persist the assistant message to the product's own store. Called + * once, after drain, with the assembled (transform-applied) text. */ + persistAssistantMessage(input: { + identity: ChatTurnIdentity + finalText: string + }): Promise + /** Optional post-processing (proposals, citations, credit metering …). + * Errors are swallowed + logged — post-process must never fail a turn + * that already streamed successfully. */ onTurnComplete?(input: { identity: ChatTurnIdentity; finalText: string }): Promise - /** - * Optional per-event side channel (e.g. Durable Object broadcast). Runs - * for every event the engine emits, lifecycle envelope included. Errors - * are swallowed — a broadcast failure must not break the chat stream. - */ + /** Optional per-event side channel (e.g. DO broadcast). Runs for every + * emitted event, lifecycle envelope included. Errors swallowed — a + * broadcast failure must not break the chat stream. */ onEvent?(event: ChatStreamEvent): void | Promise - /** - * Optional pre-persist transform of the final text (e.g. PII redaction). - * Affects only what is persisted; the live stream is never altered. - */ + /** Optional pre-persist transform of the final text (e.g. PII + * redaction). Affects only what is persisted; the live stream is + * never altered. */ transformFinalText?(text: string): string | Promise - /** - * Optional live accumulator. When the producer's `finalText()` is only - * valid after drain, this lets the engine also observe each event to - * build the text — return the running text or `undefined` to ignore an - * event. When omitted, `producer.finalText()` is the sole source. - */ - accumulate?(event: ChatStreamEvent, current: string): string | undefined - /** - * Optional trace flush — resolves when OTLP export completes. The engine - * hands it to `waitUntil` so the worker isolate stays alive for the POST. - */ + /** Optional trace flush — resolves when OTLP export completes. Handed + * to `waitUntil` so the worker isolate stays alive for the POST. */ traceFlush?(): Promise } export interface RunChatTurnInput { identity: ChatTurnIdentity hooks: ChatTurnHooks - /** Worker liveness hook (`ctx.waitUntil` / `executionCtx.waitUntil`). When - * omitted, trace flush is awaited inline before the stream closes. */ + /** Worker liveness hook. When omitted, trace flush is awaited inline + * before the stream closes. */ waitUntil?: (p: Promise) => void - /** Optional structured logger for swallowed hook errors. */ + /** Structured logger for swallowed hook errors. Defaults to + * `console.error` so failures surface without product wiring. */ log?: (message: string, meta?: Record) => void } @@ -132,109 +100,97 @@ function encodeLine(event: ChatStreamEvent): Uint8Array { return encoder.encode(`${JSON.stringify(event)}\n`) } +function defaultLog(message: string, meta?: Record): void { + if (meta) console.error(message, meta) + else console.error(message) +} + /** - * The engine. One instance is stateless and reusable across requests — all - * per-turn state lives in `runTurn`'s closure. + * Run one chat turn. Returns immediately with a `ReadableStream` body; + * the turn executes as the body is pulled. Never rejects — backend + * failures surface as `error` + `session.run.failed` events. */ -export class ChatTurnEngine { - /** - * Run one chat turn. Returns immediately with a `ReadableStream` body; - * the turn executes as the body is pulled. Never rejects — backend - * failures surface as `error` + `session.run.failed` events. - */ - runTurn(input: RunChatTurnInput): ChatTurnResult { - const log = input.log ?? (() => undefined) - const { identity, hooks } = input - - const body = new ReadableStream({ - start: async (controller) => { - const emit = async (event: ChatStreamEvent): Promise => { - controller.enqueue(encodeLine(event)) - if (hooks.onEvent) { - try { - await hooks.onEvent(event) - } catch (err) { - log('[chat-engine] onEvent hook threw', { - error: err instanceof Error ? err.message : String(err), - }) - } +export function handleChatTurn(input: RunChatTurnInput): ChatTurnResult { + const log = input.log ?? defaultLog + const { identity, hooks } = input + + const body = new ReadableStream({ + start: async (controller) => { + const emit = async (event: ChatStreamEvent): Promise => { + controller.enqueue(encodeLine(event)) + if (hooks.onEvent) { + try { + await hooks.onEvent(event) + } catch (err) { + log('[chat-engine] onEvent hook threw', { + error: err instanceof Error ? err.message : String(err), + }) } } + } + + try { + await emit({ + type: 'session.run.started', + data: { + sessionId: identity.sessionId, + tenantId: identity.tenantId, + turnIndex: identity.turnIndex, + }, + }) + + const producer = hooks.produce() + for await (const event of producer.stream) { + await emit(event) + } + const rawFinal = producer.finalText() + const finalText = hooks.transformFinalText + ? await hooks.transformFinalText(rawFinal) + : rawFinal try { - await emit({ - type: 'session.run.started', - data: { - sessionId: identity.sessionId, - tenantId: identity.tenantId, - turnIndex: identity.turnIndex, - }, + await hooks.persistAssistantMessage({ identity, finalText }) + } catch (err) { + log('[chat-engine] persistAssistantMessage threw', { + error: err instanceof Error ? err.message : String(err), }) - - const producer = hooks.produce() - let accumulated = '' - for await (const event of producer.stream) { - if (hooks.accumulate) { - const next = hooks.accumulate(event, accumulated) - if (typeof next === 'string') accumulated = next - } - await emit(event) - } - // Producer's own finalText wins when populated; otherwise the - // live accumulator's value stands. - const producerText = producer.finalText() - const rawFinal = producerText || accumulated - const finalText = hooks.transformFinalText - ? await hooks.transformFinalText(rawFinal) - : rawFinal - + } + if (hooks.onTurnComplete) { try { - await hooks.persistAssistantMessage({ identity, finalText }) + await hooks.onTurnComplete({ identity, finalText }) } catch (err) { - log('[chat-engine] persistAssistantMessage threw', { + log('[chat-engine] onTurnComplete threw', { error: err instanceof Error ? err.message : String(err), }) } - if (hooks.onTurnComplete) { - try { - await hooks.onTurnComplete({ identity, finalText }) - } catch (err) { - log('[chat-engine] onTurnComplete threw', { - error: err instanceof Error ? err.message : String(err), - }) - } - } + } - await emit({ - type: 'session.run.completed', - data: { sessionId: identity.sessionId }, - }) - } catch (err) { - const message = err instanceof Error ? err.message : String(err) - log('[chat-engine] turn failed', { error: message }) - await emit({ type: 'error', data: { message } }) - await emit({ - type: 'session.run.failed', - data: { sessionId: identity.sessionId, message }, - }) - } finally { - if (hooks.traceFlush) { - const flush = hooks.traceFlush().catch((err) => - log('[chat-engine] traceFlush threw', { - error: err instanceof Error ? err.message : String(err), - }), - ) - if (input.waitUntil) input.waitUntil(flush) - else await flush - } - controller.close() + await emit({ + type: 'session.run.completed', + data: { sessionId: identity.sessionId }, + }) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + log('[chat-engine] turn failed', { error: message }) + await emit({ type: 'error', data: { message } }) + await emit({ + type: 'session.run.failed', + data: { sessionId: identity.sessionId, message }, + }) + } finally { + if (hooks.traceFlush) { + const flush = hooks.traceFlush().catch((err) => + log('[chat-engine] traceFlush threw', { + error: err instanceof Error ? err.message : String(err), + }), + ) + if (input.waitUntil) input.waitUntil(flush) + else await flush } - }, - }) + controller.close() + } + }, + }) - return { body, contentType: 'application/x-ndjson' } - } + return { body, contentType: 'application/x-ndjson' } } - -/** Convenience singleton — the engine is stateless, one instance is enough. */ -export const chatTurnEngine = new ChatTurnEngine() diff --git a/src/durable/execution-handle.ts b/src/durable/execution-handle.ts index 0bfdfd8b..cb278274 100644 --- a/src/durable/execution-handle.ts +++ b/src/durable/execution-handle.ts @@ -1,64 +1,3 @@ -/** - * `AgentExecutionHandle` — the typed pointer to a substrate-owned, long- - * running agent execution. Products persist this id so a client retry of - * the same turn can land on the same substrate execution. - * - * State of the world (2026-05-22): - * - * - **In-call reconnect** — automatic. `@tangle-network/sandbox`'s - * `box.streamPrompt` extracts `executionId` from the response's - * `execution.started` event and replays via the runtime endpoint if - * the stream drops mid-call. Callers do not pass anything; the SDK - * dedupes replayed events transparently. - * - * - **Cross-process reconnect** — partially supported. The orchestrator - * route `/agents/run/stream` reads `X-Execution-ID` header + `Last- - * Event-ID` and replays from its event buffer (10k events, 2-min - * post-completion retention). A product that wants this today must - * bypass the SDK and POST directly with those headers (see - * tax-agent's `sessions.ts`). The public `PromptOptions` does not - * yet expose them; once it does, products plumb the handle through. - * - * Until the SDK surface lands, this handle is most useful for product- - * side persistence (D1 row, session metadata) and tracing — derive the - * id at turn start, store it, and the wire integration is ready when - * the SDK is. - */ - -export interface AgentExecutionHandle { - /** Stable substrate execution id. The same id on retry → orchestrator - * replays the buffered stream rather than spawning a second run. */ - executionId: string - /** Substrate session id, when the execution is part of a multi-turn - * session. Optional — not every substrate is session-scoped. */ - sessionId?: string - /** Last event id the caller acknowledged. The substrate replays strictly - * after this id on reconnect. Omit on first attempt. */ - lastEventId?: string -} - -/** - * Structural contract for a substrate-backed reconnectable stream — what - * `box.streamPrompt` already implements. Provided as a typed shape so - * substrate-neutral helpers (e.g. a chat-turn producer) can be written - * without depending on the sandbox SDK directly: - * - * const sandboxStream: ReconnectableAgentStream = { - * open: (handle) => box.streamPrompt(prompt, { - * executionId: handle.executionId, - * lastEventId: handle.lastEventId, - * ...sandboxOptions, - * }), - * } - */ -export interface ReconnectableAgentStream { - /** Open or resume the stream. The implementation forwards - * `handle.executionId` + `handle.lastEventId` to the substrate; on - * reconnect the substrate replays from `lastEventId` without - * re-running the agent. */ - open(handle: AgentExecutionHandle): AsyncIterable -} - /** * Derive a stable executionId from the run identity. The same * `(projectId, sessionId, turnIndex)` tuple yields the same id — so a @@ -67,8 +6,16 @@ export interface ReconnectableAgentStream { * prompt. * * Format is readable, not hashed: operators grepping orchestrator logs - * for `gtm-agent:thread-abc:3` find the run without translating an opaque - * id. Substrate executionIds are not a secrecy boundary. + * for `gtm-agent:thread-abc:3` find the run without translating an + * opaque id. Substrate executionIds are not a secrecy boundary. + * + * Wire integration: + * - `@tangle-network/sandbox@0.1.x` PromptOptions does not yet expose + * `executionId`. The SDK auto-reconnects in-call by extracting it + * from the response `execution.started` event; products do nothing. + * - For cross-process reconnect today, bypass the SDK and POST to the + * orchestrator's `/agents/run/stream` directly with this id in the + * `X-Execution-ID` header (see tax-agent's `sessions.ts`). */ export function deriveExecutionId(input: { projectId: string diff --git a/src/durable/index.ts b/src/durable/index.ts index e1d32509..8f862aa9 100644 --- a/src/durable/index.ts +++ b/src/durable/index.ts @@ -1,20 +1,19 @@ /** * Turn-lifecycle helpers for `@tangle-network/agent-runtime`. * - * Execution state — long-running agent execution, reconnect, replay, - * dedup — lives in the substrate (`@tangle-network/sandbox` SDK + - * orchestrator). agent-runtime owns the layer above: + * Execution state — long-running execution, reconnect, replay, dedup — + * lives in the substrate (`@tangle-network/sandbox` + orchestrator). + * agent-runtime owns: * - * - `AgentExecutionHandle` — the typed pointer products persist so a - * reconnect lands on the same substrate execution instead of starting - * a second prompt. - * - `ChatTurnEngine` — the framework-neutral turn lifecycle: NDJSON - * framing, `session.run.*` envelope, persist / post-process / trace- - * flush hook ordering. Wraps any producer; the producer talks to the - * substrate. + * - `handleChatTurn` — framework-neutral turn lifecycle: NDJSON framing, + * `session.run.*` envelope, persist / post-process / trace-flush + * hook ordering. + * - `deriveExecutionId` — convention helper for the stable id products + * persist so a retry of the same turn lands on the same execution. */ -// ── Chat-turn engine ────────────────────────────────────────────────── +export { deriveExecutionId } from './execution-handle' + export type { ChatStreamEvent, ChatTurnHooks, @@ -23,7 +22,4 @@ export type { ChatTurnResult, RunChatTurnInput, } from './chat-engine' -export { ChatTurnEngine, chatTurnEngine } from './chat-engine' -// ── Execution-continuity contract ───────────────────────────────────── -export type { AgentExecutionHandle, ReconnectableAgentStream } from './execution-handle' -export { deriveExecutionId } from './execution-handle' +export { handleChatTurn } from './chat-engine' diff --git a/src/durable/tests/chat-engine.test.ts b/src/durable/tests/chat-engine.test.ts index 31d80308..7d90b6ce 100644 --- a/src/durable/tests/chat-engine.test.ts +++ b/src/durable/tests/chat-engine.test.ts @@ -1,16 +1,7 @@ -/** - * `ChatTurnEngine` tests — the orchestration contract every product chat - * handler depends on. Covers: lifecycle envelope, NDJSON encoding, - * hook ordering, the failure envelope, the per-event side channel, - * the pre-persist transform, the live accumulator, and swallowed - * hook errors. - */ +import { describe, expect, it, vi } from 'vitest' -import { describe, expect, it } from 'vitest' +import { type ChatStreamEvent, handleChatTurn } from '../chat-engine' -import { type ChatStreamEvent, ChatTurnEngine } from '../chat-engine' - -/** Drain an NDJSON ReadableStream into parsed events. */ async function drain(body: ReadableStream): Promise { const reader = body.getReader() const decoder = new TextDecoder() @@ -32,7 +23,6 @@ async function drain(body: ReadableStream): Promise void) { return () => { onConstruct?.() @@ -46,12 +36,12 @@ function textProducer(text: string, onConstruct?: () => void) { const IDENTITY = { tenantId: 'ws-1', sessionId: 'thread-1', userId: 'user-1', turnIndex: 0 } -describe('ChatTurnEngine', () => { - const engine = new ChatTurnEngine() - +describe('handleChatTurn', () => { + // intentionally exercises the public function via the same name used by + // consumers it('wraps the turn in the session.run.* lifecycle envelope', async () => { const persisted: string[] = [] - const { body, contentType } = engine.runTurn({ + const { body, contentType } = handleChatTurn({ identity: IDENTITY, hooks: { produce: textProducer('Hi there.'), @@ -72,7 +62,7 @@ describe('ChatTurnEngine', () => { it('runs hooks in order: persist → onTurnComplete, after the stream', async () => { const order: string[] = [] - const { body } = engine.runTurn({ + const { body } = handleChatTurn({ identity: IDENTITY, hooks: { produce: textProducer('answer'), @@ -89,8 +79,9 @@ describe('ChatTurnEngine', () => { }) it('a producer failure becomes error + session.run.failed, stream still closes', async () => { - const { body } = engine.runTurn({ + const { body } = handleChatTurn({ identity: IDENTITY, + log: () => undefined, // silence the default console.error in tests hooks: { produce: () => { async function* stream(): AsyncGenerator { @@ -111,7 +102,7 @@ describe('ChatTurnEngine', () => { it('onEvent side channel receives every emitted event', async () => { const broadcast: string[] = [] - const { body } = engine.runTurn({ + const { body } = handleChatTurn({ identity: IDENTITY, hooks: { produce: textProducer('x'), @@ -127,7 +118,7 @@ describe('ChatTurnEngine', () => { it('transformFinalText alters what is persisted, not the live stream', async () => { let persisted = '' - const { body } = engine.runTurn({ + const { body } = handleChatTurn({ identity: IDENTITY, hooks: { produce: textProducer('SSN 123-45-6789'), @@ -143,35 +134,10 @@ describe('ChatTurnEngine', () => { expect(persisted).toBe('SSN [REDACTED]') }) - it('accumulate builds final text from events when producer.finalText() is empty', async () => { - let persisted = '' - const { body } = engine.runTurn({ - identity: IDENTITY, - hooks: { - produce: () => { - async function* stream(): AsyncGenerator { - yield { type: 'message.part.updated', data: { delta: 'Hello' } } - yield { type: 'message.part.updated', data: { delta: ' world' } } - } - return { stream: stream(), finalText: () => '' } - }, - accumulate: (event, current) => { - if (event.type !== 'message.part.updated') return undefined - const delta = typeof event.data?.delta === 'string' ? event.data.delta : '' - return current + delta - }, - persistAssistantMessage: async ({ finalText }) => { - persisted = finalText - }, - }, - }) - await drain(body) - expect(persisted).toBe('Hello world') - }) - it('a throwing persist hook is swallowed — the turn still completes', async () => { - const { body } = engine.runTurn({ + const { body } = handleChatTurn({ identity: IDENTITY, + log: () => undefined, hooks: { produce: textProducer('ok'), persistAssistantMessage: async () => { @@ -186,7 +152,7 @@ describe('ChatTurnEngine', () => { it('traceFlush is handed to waitUntil so the worker isolate survives the POST', async () => { let flushAwaited = false let waitUntilCalled = false - const { body } = engine.runTurn({ + const { body } = handleChatTurn({ identity: IDENTITY, waitUntil: (p) => { waitUntilCalled = true @@ -206,4 +172,25 @@ describe('ChatTurnEngine', () => { expect(waitUntilCalled).toBe(true) expect(flushAwaited).toBe(true) }) + + it('swallowed hook errors are logged to console.error by default', async () => { + const spy = vi.spyOn(console, 'error').mockImplementation(() => undefined) + try { + const { body } = handleChatTurn({ + identity: IDENTITY, + hooks: { + produce: textProducer('ok'), + persistAssistantMessage: async () => { + throw new Error('db down') + }, + }, + }) + await drain(body) + expect(spy).toHaveBeenCalled() + const messages = spy.mock.calls.map((c) => c[0]) + expect(messages.some((m) => String(m).includes('persistAssistantMessage'))).toBe(true) + } finally { + spy.mockRestore() + } + }) }) diff --git a/src/durable/tests/execution-handle.test.ts b/src/durable/tests/execution-handle.test.ts index e18fb958..dff7cbce 100644 --- a/src/durable/tests/execution-handle.test.ts +++ b/src/durable/tests/execution-handle.test.ts @@ -3,19 +3,19 @@ import { describe, expect, it } from 'vitest' import { deriveExecutionId } from '../execution-handle' describe('deriveExecutionId', () => { - it('is stable for the same identity tuple — a client retry of the same turn lands on the same substrate execution', () => { - const a = deriveExecutionId({ projectId: 'gtm-agent', sessionId: 'thread-1', turnIndex: 0 }) - const b = deriveExecutionId({ projectId: 'gtm-agent', sessionId: 'thread-1', turnIndex: 0 }) - expect(a).toBe(b) + it('is stable for the same identity tuple', () => { + expect(deriveExecutionId({ projectId: 'gtm-agent', sessionId: 'thread-1', turnIndex: 0 })).toBe( + deriveExecutionId({ projectId: 'gtm-agent', sessionId: 'thread-1', turnIndex: 0 }), + ) }) - it('differs across turnIndex — turn N+1 cannot collide with turn N', () => { + it('differs across turnIndex', () => { expect(deriveExecutionId({ projectId: 'p', sessionId: 's', turnIndex: 0 })).not.toBe( deriveExecutionId({ projectId: 'p', sessionId: 's', turnIndex: 1 }), ) }) - it('differs across projectId — two products sharing a sessionId do not collide', () => { + it('differs across projectId', () => { expect(deriveExecutionId({ projectId: 'a', sessionId: 's', turnIndex: 0 })).not.toBe( deriveExecutionId({ projectId: 'b', sessionId: 's', turnIndex: 0 }), ) diff --git a/src/index.ts b/src/index.ts index 381b1548..4f3b9ae5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,13 +44,12 @@ export { runChatTurn, sandboxAsChatTurnTarget, } from './chat-turn' -// ── Turn-lifecycle helpers ──────────────────────────────────────────── -// `ChatTurnEngine` wraps a producer with the `session.run.*` envelope + -// NDJSON framing + persist/post-process/trace-flush hook ordering. -// `AgentExecutionHandle` + `deriveExecutionId` are the typed pointer -// products persist so a client retry of the same turn can land on the -// same substrate execution. Long-running execution durability itself -// (reconnect, replay, dedup) lives in @tangle-network/sandbox. +// ── Chat-turn HTTP orchestration ────────────────────────────────────── +// `handleChatTurn` frames a producer with the `session.run.*` envelope +// + NDJSON line protocol + persist/post-process/trace-flush hook order. +// `deriveExecutionId` produces the stable id products persist so a +// client retry can replay the same substrate execution. Long-running +// execution durability itself lives in @tangle-network/sandbox. export * from './durable' // ── Errors ─────────────────────────────────────────────────────────── export { diff --git a/tangle-network-agent-runtime-0.16.0.tgz b/tangle-network-agent-runtime-0.16.0.tgz index 987220ac117f36a343cbf5418c79a71003b4718a..e0f7e48f6c8b2fdbd9c93a9566afc7fa9cbd7566 100644 GIT binary patch delta 126464 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

&Hf=2If851*Nt7^xB14HQ?d%Gg&3KFA z{B$=(5%`kT2|a=B3WcGa6svI$3^?$QyDHh+z@?Wc@p*ceh(ev$?bmBp*Jw^_t~USb zN1o3+HLleZ<8+$52kYaz$iPv&*vc5p0PhUyI$J9SuZW(zc54;<1@UgNhPGd3+ zfi>u<+I*kM3k#QTO0b8uVQ3h6HU>UzvbwaVe*wSmnC~p~a+G_J3dE_His(<(S1v%G zQ&KTq7)#H6rEDTi1OD-&bxS4xY0BwVYeh6ITecMyw>2`O+Cr6^>PgXA5rU{|d9_}3 z?B7=^hW*$Z1OPo#Exi=n7B!d}Y_&SI)EOfsTd9Efj?Aekjb}-fEJBE%L(3u>e3=Kv ze^`TU{BRvi$;Zf&S=+7<$J{o#y2W6ybk4e_` z{TAKzaj@@N9iO7^Mn`ZR)Xl}7+{Ap6J{9Gw(=_g(qti75571M) ze3Ml3wk5n(i#}L^=l$e@aqwNsT|93u-d}sjqqUU$dfbx1Q^(nY;qL zVtPf(H62|U0I;M@;j8y|uD|No5*R%KWbyJpf~g6@q0y<6?26z|&sTp=mt1jh!^{{u z25Xc^xL5wl+S&p(GnQ^jm%lponH#?T#KZ9u>0B8}i-(LYbG_3vSL`QHI=Sq)J&pFY zxxDDINJmabF(PLCiaFw3T8;{@p2~ri;oG;JtB`hsowKW2t7_W&W2*x&r!Bh$*uv5L z(y!_MrVx|=hk}18&PPa#QeLBcMx+A#(!&IZTzNoJRtlNt=cyp2+qR-?hwjaAt{CqNZRIw zryz&nqyl0cn+mNP4s?AyXg!50G4DG=u284R!y@_#FpYn;dr+gZ1|HGu1#B%ZlUj~y z?dt9tmGf%dShe*Lc0d$wWUc#chtI8;4QEWPjJGiJ8*Dq0*DuR^`SQ0JsdYBP>qy98 z@9_}ooJZjVJgX|&;QGY=#ln~q#Vj=saobzu7)+I9lUSRVrLNjNU!VqTN#Z$|!%tKs zQ$nqy$`gMY-8bzL*KVHD1ZvWbeCps) z6Du-8%#+dUxkf>%A-fkfJ*H3Q?c?tT;E9Mpn_D~=^Du*OAPQe4DW;A=j)1Yv71*Dz zfaH)c5ycG~KMyfg4fOSTJ^icvezM!A=^h<|Vt%YS^t^ ziFb6|kZmI^;u4%~pBj&?c59YYt*(04!WJQQO|N%8hx=bZ5PmvMF7VlM6aTOEp{tW_ zoJW1-@g!dWB2WBugi2HH-63|v$z^_V$}00Gmk&jaem}I_VuS-IG|_wdAoDfBdH3BZMOqYJvVv8 z?my_@g2tk)E4-qH^rvLl7GKk{3L$YQtvAyRbP>r{Yq&t(;%uL`x?CGfyF24NdzgRl znZJ?A`>G+`v(|2-Xbt;C5GD}>nAW4bw!JWlZqpQc^Z4Y(lc1q6-%@RF%LXpa`B!y( z#xW6^CllO!o|L2G0bHb_ZBu$eb|+u$x6(%woOE>m%*>*l}tr z?Sv!LO_MVP($T~AVuUiMPlqFfcdH2{vUL%y2c3zYvCVP+8)yJ``bE_ zb644KHFcGD!14R{+5h+-SnE2);r;vU{re0`=(F4vr{?r>e`t;-|BYsT#&^igi&mF- zLVmy;UeDP0@ojhwJx3Zl0B^AQ75tkQ$qY8(+bk>i@>XpZPRl$Yv$@QJPNIL>T3WXd ze9*BT^bpV8Jn*!NR{)xpRkYi-7sb!X=1H-z+k{cU}yq=58B_QgRD!Rll2uP>s>`y#cSJ9Y7TjpLu`nY(rQYg4*21 zprI7gFyTQA>*8uwommGB%9Pj~O}MA&13R|G=?CA&d-15C># zDJH8lEF^zB#o9)zWr_)c)Dod!6F@R1Uu}j~gpQLJV_>33OwffyI4twB;y5Il`XV;b z5O6&zr;PEIeP!`uK^BV|`w9T#p5=&gvKaAI=`tg6cdN*E{Pfmm|>}=FP>%P*| z_79rw+I@yPFrS~TNjNdYr+3~2@FDs)@dA1;A-Zd)t}n^43;z^sI(-sfxUU>1?^{pq zI^bSkF3*shR(MR zmTp*%B*+PQ^915KZmjUlll+f46!uHsJZO%<*6PCQ0{y@;fa+yNMUzR893LuV-2x%f zep6%SY-rXio&x~nL1t=DnpNLk)~vzymy?8$Qh$o^quY5_Tsr}4hr&kr-@qxN(?Dtc z2RcCjO!#EsXGZ{Ha*+Dw|$K-dkhnyJB-+O}SR-$6&OOno0*Vm%I8lP;re+}g~o z(X6;#phj(fmge<<{89~-GJ zxPR_JYCAl>_7Dmd4%T37Uz&aPJ|us{gbAe%&c(1ilao|K5`eoO#1|*!obm$*aPm8J{F57zRC5;FpS_h{yW zpUOPbNJozXwKr8`m@Js9gX}1t6)`3i9e-mjRNoV|*=6*P(q-&**4raz z_&x-j6}=fC<0CK)9n^iXni_{o^`3NsoBO@%S0;>gTTr&q=#X5z>#>1P?GWM-pP zOiNkVwJY!}^O89=))*jE0bn*CUgI~jF2cCZg09`)%ogS5FpLJbK|ZFAac=Ng?p9Qt z_?%O-CLSR}YSfAb*bR|9bPK1i{eKdJ^M_a)w(%Z#D~U+PB!2qZo|UkPE_t~MH$g#I zeuUi__ZbJ99R-@o^7G*R9r}{LKVf889EVPNvnpdF6nVYfliYasG{{*f*>W5?UaTPR zUB*~=f+bi>JU$Q4w2P{n$Q(iLp-pWsHoMDp4q4aOJ#RhH?jjjuTGXzuXn){c9`Qc_ zVYnN)MoCa(O8RLp5q}5Rc0jlZGKVLFjKBZ}Wwu#bdv=~AaD~-=SKo~WyQWHM*X z5;veR=OY=zvJ|LjxY$Ek3b03I5BMysAX_Y53QQpxmP*Jrps__6Q3W>YElXR6Tiq;I zcDfUAna8{_9q+=)OUl9D@YEPPdRLvGQzECSDG~mpX~6YQy2kL|ER&0tD1ZOl#>ih+ zL71lIyLUklXm8;l2pVeVfSHXAz)RGs(*OG3FqJ$~Sl~QElCw~S>X*34d&K8eu0F=` zQiT1+2E*@_BuJ6fDVI@EZW-~rAuH+U73Tm$&qh6f=2$WRo&!wANFHqn)DvXcBO6pX z;8^{$t)0c8lu9(9jDJ;{P3w`ZGdbTmb`{5nYV8H5f&Ssfo3UyI_7?7y^PW|$ zb*~%xdF6QC*Yu@#AI$X+|8>O{8pDQW9~6*@romccvV0kdE1d95YpZRWs{L>`W=H?6t(Wj|vk7_AA`F&t0OJb_Fut&vd9wq_ z$xN*^iy>Y3A5y;5B+oGI({Yl0G={^ae%UJ_v5;iDTo%b9sU)#l6RtU+Y{StE9E~7% z;5o)KCK+btGuqfN%}X(@gjLu}GT1#SncyYv zk(3AvY6YBPGDDQcT!9s%b#Tk7p1{YY4BwXo>gXk+v1SByS1j`)L{#-SEWiYaY7RhD z*l6Ouos{edkcguQfefm=%&K_|ImpGX7!ZoQOWj2Xhe@IX;zEi}8==YXs(-$}3D=(n5Y?YS=8H~W*P_&TJl2!Sz~Q{XVbKC8TMOvuK&)?Q5K!1uq9bymI(&`(g~Zy*-|U{03~V_uqGmJIMs<0#@R!ZD!zyUXT(dAqSXVbqjBKqx4O4&iG)!bKk|)jq$~Bs&`nz{`dimjqVQ6%ehnsCod1X>hR$B1V4QlX0z$4bUu0* zs5GCI#uu%Pz$TDGl2UtExx|aGoU^EPR-jpYn#GReWmsVP>aJtQ5vJ9Ph|ln2Bo?_i z3k#3s!h-UD6`;_n=BveN*;_3)_%9Grn3Eij)`d#L(-=IC65DzpHJ%h9un|D-l|Vhc zOMygQm1_6!VB`Cf1CrEm6t4-YA!Ia|BCyVzy!Z%!N$~<;Y9R*|+PpyCoUW!*Jjo*? zC9umcas+$E%+LBtsGxvBl_t}eQqqDSzvVDps_|X}wvQi^WSfNrC{U-S`IGLO=YJLH zVYD0Bl`iv@O()Qs?DUbMJ=vKtP1n4XnNAi_fO^BQr?9GuaIfR4VL2V)!RpUqTo>PTcXoGIm;+liPD9`(Gv}1;It4h|tht{24EHs- zwSm2CcSp5%NtaU_YWF^50LaSost(BP6GCpD$(uMD;$Cs~g-AG~r#X$-Es za*4;XKPdqe8B(rBrE{I)XdByoYoQ-`ibykc^oH*c=XFUTQDbk%-_ptLlfvMD18L00 zk30T7H7(+}M_zuuPEk_WR+`#cfG}>Cef~JCCsbo+8idOyM9HV1YoLKscsKGK44-#A^=D zq#{nQD#R^k|04Xkz6+H36A!z%&lk1sfD)CbF}e`b9X2Zmg5Z53deaZ1KaGw^@5Izw zsCG#4EeNVWU6s6K@ZQAsmU{M7TzzDphIxMrD; z;KoU>bG^VzT-I0>-1*VM4$Med>m?VEF`6`>UNxpu#rjyFb?cABE7jK~?rU7E*7Zu$ zUEMxAgPJ?ELcrj$+j2O`Gt*To9&~qyyrwAjqU0R7Iw=&3IGU6#Q-3X9_ws@~pIcc+ zC9$^dPI@V}mGW~)#GxO4F7bvP+&hZur_Bk%@gy5Z17|k_A6M@Yg?aZCg#Du)8DSOz zap-&+kDBY`FJX|{YlzCAMQz~nk%5vJ=T>SFUWS)zM2I{Bd`e9sEA$VPP z#wH`rZ0$CwZ@Q0Rw|@mDA?#jQ{HqdEp2Jd)l5m#gC7h2m!uk>{o-5D*WH`-k9Su6A zuTR2yGTOJBebBaNjD@?ZJWzXjOFR{QGK#@PuFnUya57mfR=6MXiOn2SN^xc(*%V4# zmUkF)^TN}qhteOi?+M_oVxOa=h$mHgsj-ponss4)0Wk%uLVui?sBuWfWD7uJujyM< z@kKWrVXWX!U^WU>7G-l>w+68d=r*<=WFW5TIhs4PaB~c=VWlOA!u-VSrb7k>Uz;&D z5HgWc6g#NH0mUQOC(SMs95<58QAOSUdj~t8QUjExkXHvx4_eJY69e4E+Zst1G2;l> z&cKv(WJc+r+kbFEfki@|o+uW`(~Y9yKxJU(e5uBK2LKd#L10zl$&^enZ>W>cW6QW1 zYO2xzk|{D(4o*csfM&w!9~`9wH5UzZ?&l3Mt`Anvl1R0+jSY5t2W2=S$@aqIE-BuV)OJl88%>^9&spL~-6p*A`AL#M6(HaRky!(yB6 z9>^7DjqRk<_{Mf+N%NUpgTtTO+oE>A%x9()>hF7tWh!Oq(6GH-J13ZLlW?KhLth?- zmuVgvX7jAwKCkg)x&!X`6_Z789U*Yka2COEjvk!zTXcDb=ru{6ZGb;y%kHo<8-X#` zb)>C;9FYYJh-HmelZ~QGe*(r>+CUS#=v8Bo?X|+hbRVJUe`on|>#;Qn8qcrI)%MJ0 z^95?3wFeQ{Fm0HYM%)mWsrO8+)~KfhIT|<*0nHH}^uGQ?Z;`Ns-ss6SzMvOf%kjH& zx`WPab}O1((0{4Gnfdlo6|EUi%e>WUswwz-;T$uIX zd}vj;OqLv0goggf$0=NS@C9%MQ4AY!LjbAmN^)nxlTA{wbB{^dvz8QPcoy4UpOCk} zA&Gfq8IjiR7Pd`^Xlb4RTUhL1eFZ}Shm(Zkwp(z3ZgnMdFTCPaYJIP+v<;u<>}a{9HX%2F%%_gqW}QfgZ&w+Ya<25KCEp zZVIzm+Z5#oe^x=)_jZj2;mb!`Hc}TH(dd7!4e?xB|CZ{ENnT4XYn;tOOq=ZWCwjEa zABg4#y>m1-FsIUEb~WA(&_7JZii^W}jnDYKZPoXR*{`@Xn)A9!{hp}=e@Y*W z{T;cUoqL)Gw;>~{ADgT3yX z!Qc)3NHZD?Ucl>vt-)Y_IfPH&bPooD18H{nc&GasD&2>F@S7L#%^uW)&%PfFzK0^O z@dbQR?Lw7%R%v;Y?LZ3$(Cnx?84M=$HIo~rJ%2yeKO-|i^dQ}lPwDrO^>Z{B{t4=Y zPL6nfFLwsRLp^URoU${d?kh8DUE7lAVfBW84!VyHd^Q znrifHQeF6uuB)Ne$D`YH4%up5t-7v^>gpytgF%8cTJ8Wd?$h_?(*v5TJ>puPwpT(Z zYDpJo-|(v;=&Ql-V(1q{u7k1bGLLU6vp*PqYx@3N|241(@*0}f{f1}hPB(6#k{KYj z;*koUHBG=iu6NPMqlr*br zUsr>~#%>8|e%W=zAJWoM7qAih*z8G84f9>AH+7w1xpPCu%!;t9r7qpy^(Vp5iHQeG z{BLx+hE96)-Jf>`gP(bS@#Q;%LB3-&yU|TV+~++UHlATo(<=Xy4WBi^;R!4+mKQn7 zi&rc!aOobALXC8)Db&L`iw#>};Z10V#<8yz8|?CdI|BTPoM!ymIkVqDT;_)OGE%g7dzc#h+{CdFeRxcKk;JRuPID#=`XXy z>&E*~;_>QhcK#+B7Z=4q1B7y59_z4vM`vtU+~eJ~yVGHM(f>MEM1-Eue>ltZf` z?38m}nLoG2yEhpA<9hK65+pqy*pou5M$6yOG(#9QSS81HOTlQQVE@yS}(XIx&0j!4fp! zw@ki|MhpFgpB@hemm{+;J}2Ftza2$LCCk9UrlEcvYW+#CJoRHPXl zTzN&j)e4ukHLzOuGk-1o#oz4N-yoT=+c-d3#=i{LeLWfwDa95Ca>d9Yt+H!wI2%!G z*oIIx+re3~>G!sQes3N6S;zLbs8`v7N*~uXwJr?j?D`{k>uN2$+u}ks($!c1G*w2w zS+|jYP}l=*{Ex6Yo5oQ+Uq23<38sLN#duKGI=)d2;G5-h96Z$a5Bjg?Xxn>b88r2X ztbNT81tp$g+E<`;876=l|LHd)6f51>q?r*zK5cf;1e;$&u(z&;v~H`uk^9ZKUJr&R z)F-3I8UxvDv~ltbnSsBCC7}Ntn7@eq?DH9az4(c(Dn=)It!d{xn>q`PKi)A00{t)| z8k%RX;F+^ceMbWhUHx3|n#K~WRn*bIBCg2RpV)x;8pW<<&U(ul;?Mf&h|KMKuC~F_ z;#l|n(3z9rfXs=8T^(ftH^$Mxsu$OXQ5B?_YWQ0;L%WY-qIGtk??nr>`eL5d7VPkU z?71baG!M`xa!GMUD!SQH4lPa85;7J$)wdFxwdG2}G$llu?^1X4c@-WWQu9=@v1Ln9 z`Dx^nb++T$D{DO#yK8+G&Aw+om##kCr2!dL^EKJtWUZOgr#Xz83Ju3w+k^hvSTyPP z%ch^!8h!<;F%4H_&;bnQ*c5cmQZ$c$zYMK1v(aEM8)=FZ&;8};G3B1E6cVj@`?v1f zpd)|8MfZS=kI$_`fRVE1^ugMbk-g5D^p}xE2h{Hl+-+bF_68##(Xy$fHJuUlzD#1m zvXjp63h4ldc%bk9UJ*@)F%+L#z2|AuM{>)?VlESK2wc+GNz7TE*YN1u=hjt!n@1O| zG&a_NeUTw<*hi1L#Uy2=T3@qRtr&`%Wwxf3pSTrs-RHC-mxIXbiFLL&6fp)SGcL1) z&z>9I-#^5xvy_AH!np5MygcEG~LZqHmqnU=5RA#Tc12TfDo zG7mW``^2aD{$OYX*5LR1%e#y;L&UM$*WNUF#n7^}m<8aB`0);jCwy&7wc&*=qhy)l zUCxW2hAs!7)hy)uCRf_g3>oe-?YOp4vcI5VpgLwP;*UFgHb)|S!v=wWnhn%FWk250 zr)=JREh`NQtJ>AFr(3>4<<3(=U$GQ^su&E z9>9H(`Qj(mUo5f5pZFTo$I)Q$aU@4v&xrGSF#P3tU5Db`oz7GsKK6Z2kxjVd;!O$r zCpz55h!0_&bQdjJdg#A@H+=j`=G;Dp`J?)II2imq{G8*|*>h)zG@IrQXU|a>jrt^K zDYmTGBtgy1kOcdJf63L|#x9+|^fvj{QiEKhr|#Rlb^`T>&ZPg0p+6q2ANuF2U2j=F zFh9N!g-e{0%z4D@2gfX^kO3|%-GBzTuUX4;?h)FjLo!&3N5LL{pQc`2zlXdbxitI- zcauD@q!Fq-#z-JXNxGEY^_6NvCVVzR8=*GSYFf>AEGcX*G>j>lh@nSP?D|E;c;gi` zR!6$f8aURg7oygDJN$K1Vfw*>wBD18T9@T0=oNf8?7Bwa`y;uwiP19oiiQ0GIV@w1 z%y4tvC+ypyxo0m0cg$tETa)UqGk>PQ!=a^$Vui>EMqC&^HsxO99_(49(5j+0V*Tt- zhV?D0kJILfD~_HE+d3ANWo;82>BG07dQ-pMb%aXzuBFAhHUYiGZ}60JU(Ee-g9YaO+?OLY}zf^y=w=j{L;t81VvQhkogTk@CBD(&P&U1v2Dfo7m?$Jl) za!Mb&UhuPn!5)-N#JItGy?^fHI=ge zSu#ho&64RxPLk~L%Ic80NZGge&-6rh2f1`)+2?RQc^2V)v=@0w5GuQ~A@_Djf~k(O>3yLC&kVH>)p-E(&M;i*6F89H*=1~toIqaPQzKY#C8^x*Bwxqdj~ z^V>wlbcBIX+A6fA62YQO(s`#zj{2Nv=UODz9A^2mqIU3G3Mqm3z zk_PwfuidXKlfATu6dsca* z|IR4Jjl8F6sps?8@PFQGbgysf+AFVXHLk30w)Vx@>%m~PYcwfa37l}ZL_L13&z3fm zx!W+wEv$)HUJkEstcgWN)p{%LHoMMc_VAyqC6A?%l?{ViO~tmpb)v6^?T;N|J>IeA z;1wwQ+N;W(sdg;erf${H+g3}AE0SkDcZIwyLOe&bSDF)zPJiNbhdklm;6}cu&%qu;R;^i^~75W!jv{Vi2? z)3oO8<_QDWj(=>`px26in+<)A-9wJWhkG@R#AcH{?Q&!lG;He>{AK7%$2~sqS>B$e zQt=KaSHAAFj(e{lar{`+(X5|<=-cBlWadxvmvKn&aof{n76xT$N4Rn~KOq$(jIl)oAh1`d3z zv)cHn3V$U@K%P;?K}o#1TmpPJ;J+fGU9KjOipEM=I9U~Cau)X*^SGFt0+{yUC13#Q zENqg2RJGHP!rKx54x_v=7iYzZHvvq48BYL3y_7r*4+08SH;K6+3u{sWzz8C-E{h~D zo=X#i3d|{RFd@Vt<7z<-5D<+(ai&iU)qy$l3V)NPcsH3&?N208vXkDEi$4z3cbw^_ zRt!^^JoL;Tn9fnPd+A;bCZjIb4{!E^my$qmX+F2 zm>FclG+a)?=`>Ff*qsu4wkaEL57BH-FC}(brsWS-o<7LVn8uvyTY#_-EkHriNdOJl zFn?J;HXu1U;Ak4283@8I;1CE6`X<>h;=cpxR;b?Jfyub1BAB0h+$QF6y=(FW5kg9q z8|YrB7@^bitmf|exRe29FedjUdIy%iElXC5|b$O?GX)*hCa zdgi9J-fu^(f1tZrasgEaNiyt(TFlbRN*?8ihSqCg(g zr*4x3=&;SmY*S4#0E-Tw>i~h`e9@uQq>Ogc=OliJ!(%cq(g&-+ z-Z_FxF^Q4{LkW1duXGzd(}sf;B7Z=M8qLFPUI0W4e=v}R*A2q5Fmr3|h_Vv~N^=&# zDHVBIs(F4cJ7%fMIHqc8!ZBHr$$hRjR9K228H-g$GNCIOKPVY-3|t5R@uy*xEKE&h zj(LnHMRJOnf?~#+=M8&VnE$wBBz`UJVH>vo}wv)$3mq!o`Qwz{PNZ-$`)FB$z5@+1zhCE#NbN_+Di?k4Kf`C2V5Y}TwekEjZ>OMeBKntJhc3NX^yOzR<-cCgG1^BDC@^0;tblzghHVpS2_ zAuejBYoW9XI;C%2WI%$>Yv)5fvr_)r&}nup0&Hq6`BUn$bdbr zlNgGC=x@hbB(ARX6p0#pR%*$E1cO|tpj-4^<%B>30V0>t!ho>urho8RG({EdsiVcT zWmHLdqV(^~zjuHIh^4Xv7Zc5pRE9!+duHcHzRM}6#eAU|C?E6iZz zmb9Muh4plEQT^L52stTdx)}dzn&NBO3_iv*>#JLGSktcbUxgBe*&hc+JvC>ZfB9GI ztaZ;Mn1`f=Jv?IR6;Gj0YJFeNHG1K*=0$=;)!*}JMy~^N1hlrF@4eEz4K?$77Z}&t zl+38V32ar}AGdwhs~_?7$gR_2zpnW|;l%XoM{a%ZkKI0qTBF~qeUsd{l7IL$FqR&& zdwTSb`7V58tj~#Bu@1{?&)h;z?p6`c0Szf9A6mwbJT1L#`e3Ei`Id+paapXSmd4 zdvKInmaZ^He}c&JrYM_W(T1jt0Y@7ax)sT zpDI({pK2)J&)B+Strk^ZVnsnwCCA~uWmSSaT<1v|O~QhHWO+t;q&1uS?R`>y)t&3R ze*{{Ahnab8aVW3z?-(95HZx!$?5FzkpNS@NvVd2cnn83Wj^Qm?E@QA<(T8|QFckZd z{^-JSp@2NaSGWz~pGG`$Pdm+B)AZC_qcN0QcUyhahbzqw2plQ|w-`iY$`Jm5fR`mn-z)^n0`I1unQy_OS5s|#3 zU%vV78iiGNwZ~n(QE8PBQA1A(O1jst zSlntX@BMrgvha2&Qv_&N`{A1@ZKQZK*B8DIl$zQLCq7(2pzKQ}tAB zdVBd2cJc-SlEgC?N3ea8Ws!w|m9R@)J=1K&-6ZrcELoxrB z#l+G^^^op8Rrj^!!%;IVWwtCP!9=eT^~9{JFRBu#9V}PnyuFm;mX*fFf3}r7Ypo66 zHHlb!YlNz1RH`iCrR4HbBlpQeDH9eMZDdjtKlmc)>Mj0tOW$4iw;6&Z3Y-B@&)kLo zxyybHEx%N_-Ja|zWKwB6G*;47$&<6jd4Saq`_UL^eT3UvCf*}ep=LGK{$}&#lsyFJvrMEoIi;! zk!s%z2KO*B5C9%b=p%+?m=I(Re}h1OiM@GA!46z)4Oz%wwP=KqQ4^w#6%DZm;O7l+ zR#v=tQGrrYPe`(+EIwR(&U;|0x!CP3MKlW$)nw?I>AM-)}dkm)`i$<2c& zUDXynK`pd8B8A?gIj}o@DfDFrZ2@um1E(V!U)Yh+u5}JDk=GIve!np$ z+#r*TQconmvi=hJ@Nnl#H8`W+?gLUH>*@8;7dCo;r;&MK*ZT(i&_SK}lTW|>W!pOHImmB!sv#!=)l#BsUrX%*kmrk ze}-7x8Gsyf){?C&NI2@g^Olrh@D`frX0^?Y|mxOsVL3_EL_ zZ5FRKUdbLVcXL$qRX!VBWlwUSyf*0Gx)YCogY|08H%>SHu;X+Yl@Je6+0v~%9^NxN z>|puMZx90dS>(_F1%j^um1|_|LUR56e_9#47(%i-dkMR^BQ`k0?<`}Duy+cd{kAez zAVVHE%h-=Y064vG&RGPVwBb^4o$SeF-m7ZGbv<6#!B7qOt%p@_Xd`n4j~ zoF5$VSq=a4;?om}XaBv#r^lXqnmzv#()0E>E_(t+_;D?cOS-$JsNlHVAASLje~W|v zF`2KjkG3<2bN{EyAe`fw%r2ZEimN+xq`K{<{hPhrM8`@=jMCm2n9l zxDQXm$;UX0+=r8SSoPrU*m(*Oe{ZL0yb)i-lNH?3_U2(0rEy~+tRl{45ItpnvB4F*#%Vm*IlQiyuJ3?M8!ffJJTf||MWO3<^qgZABZ@>`f`OnKR zORD4-x5~0~e^*5~iF>C-0*;F}0Vw@_SpgX|Rt-iDsVcn4vwU%h-XP;D(6Y5SQv14J6!88!=eH=6S(CUSe+i$HVr4>M>06vQ*lzw z<3hz36>xwW51<31voOWpjseW4M=&dKq~>uEW545K27^c@gcJqvMlc=ubgU-xcmj|+ z6x`1vQK$JTi^Afv1Mp4Ke;5EsL`#WTY|P7?rzn51C|uo9}6G4KoD1w zorOgbW|f+)!XheFdt3|ELh_XJe3eEj1G=d>#^$#=;|?QKEy5&I%M|_{^S~MH>JpKB z5QZXL009?CS%n{C1^p_3N6W6t@(L@`RGxqX1Wdl3F*Oa7bXCNqe;LFv4-clogyEbc zk;6JQxB|5Zm!+DdaR~5ARjxvUA2Mh@YLZ8>YM+H^5@C31SD^>b3_JSc6sDrnRp)@- z(OVHxm-RW;CZtEe5bOt{Iq5#+QCzAvOB3`IS;P*|)p?O;Gr~n8>C%-yR8@poiNHN1 zpgL*>0f^vMWKVfDe-9xvCrMK^2@8mXq=9-YI?SVZ3@{>-A}`CHECUitMX4rXrj{Z2 zb!NQ%5KXD|V2kHb?dIDfy zu(!aho~~vZx2_BqBxs}qNuEuB^MZhAu-RfJ4?NH3?T`yxkXHEQpk2*Ryi9 zj0=t8(K6D>c(0C!VMdfhPWk$i+m|anafsgc^(Vhee^)}^v96N)=3;rkh7h=QtEV0n zt&)B-2yo>-vi;ZAt%pH_07MwY_ID>Ihg~&0IvjQp`eqUqYVUB^L2=6=#41!-p7qMr zWD=JIPfpC^+!a4601st5tlVU@w!Ig3$Eqdo96#;MbN zlyOpGHy_fYkk)C)grFue>sUjgwTutWGl=R2vx?|tilQuj>#PG z&_Ez#g|J4$G|kWZYRo_^?ao*LoW>I6-tLUM&M#G+CzH6V;%d@3`>bjlK?HqR#WzpB zJ%yh$WAzEL#ZmJl5tZ+B*Q)m{ztucWfhg)8&@#z-i+GV2muk$T<6;**NKuM;`xHyZ8ROAJ70Z0rwpkZ0i z<96?N)iN(DfLiP7w+E*Hh+1|`C*5rnj&a2_oWv}67@2Xr4)M|@Kvy*o#5_cKfiM-S z3Hfp3?p^iwiJSr~PSW1PS$ys`fP0U>f6z%xurRlL?D54SuEHp+!u77k9Ov$|W5)$$ z`8k9Z3-fr;6PSDL-HxCZ?lj3q&`uxTEtX!T@4x_-_ZS34!zof90n(6b%7Wr-Iim2i{zuwgfmk*b$L+=pp#eO!7|PU zd&7LO46DTNDZT8K)g>5jzy!ex46fZciPEP# z=3O+Jq^gY5ctTbg!Qh_cf7zsnE80~NbFEzA#tNj5ttld-hCT-2aIhv;uQM=*1$`z*&y`h(|^eDPfoB~ zuHI^Q_*t9P%07)0tR42L1+n9{`^{tG+Mm5^VX@W++V0>KcJn7xj=mutxJ4^aOg_n% zx9L~Dgz{IN8;UwxHIwA}o!KLNlVErPb)M|x7dX>5O@(^e$WKi(v~5q(EUtF)i#8%| z^|=)^vI3j-)xjdEo-{GAYJZ9K>X{W`4&4QKBlv$aH8iaDX@1eC0oBi>y1|CFfchY0 zg*%^!%8H6@NI-wveT@OAk2NjfEC`#ar^m&4o;QEY=1^DU&?nz4Q)7y z(0^7}EOS-hP%r=jGU;%W=XQ)?N|&{&`b1&@=DrVL$g#T8Zv}TwVo^~ph$dkTTvR|? z<#)Ar{}iHQ#IGCz-zA>@?MY1E@ybw7iAaI`-rjZ}l-ogAwR`tF+rfnH>uhpXVfitj zyGg0O0qPJwA{0B)Uw^&DYFQ7YuhuYiheO62lWO#e3}``GG}_*V;=_Eg(96I=ysVRi zVqU3*j=F?!R&Gy9T-j4}wMOQO;&=oWKef!$WI}hAXuV9(8sJ^zs5V?;sT6W)lwEE0 zBef=3#QCZ!JAvAzxL!;-@>s#u%wk!UWaR`&pT${~gej_n`G0gus}pg+=`P@Y*;W7V z|MTGS|Dk>&ybd)Xa)y<9xLF43IIh%q5nc?q>b|-^hQLkzAC@j)T>*=@nbRSe+{NgfYjI4+4iH&`(5>D^I=y#y0}m|UglQwkkj6r=4WvSVPnD> zOii*_#8DDfF`*Y*d#f?#FpuH9AJViV{{n-Y0MxvhvLu_Nt4OXmrbz*pe~?pvrx>7> zp$*t^^lVsWSgLdnF5a#APBOJ9#hOT0K9f9)q*+t@{eR8PO=%#ZN_MD@{dy}%w@I9U zz6*9~r!`aO!%eHwMR>8k!haw@+bP`c;%!o<-fec({ST(H&EUaf)ka?JNS(y9wc3s? z)}yP>sz_#lY#)-*UDw(NhU^08!9U-2fuG=?2k_5B_~((dg&$JQF$K)qz&X@`XWRpn z33maRzkkPsR_9@w_9kgQ`KY2ej4&w%lWT8kviD45usg~}Y$aA~C+00=9*3CC2Nrok zMg?`AWKn)jjLILw-Q6kq8iZ{BzRSxsvgZ8(y6LnZs6~=>0`-RP+pyM9%~#pXETvQE zzx2pyi}Z~I76Y!n{1gqQXa-*vd9H0$ft!THlS+@N&OwGkv7_F3bKt3NpxO^rS8!K%M2g4@)zP zFMpN*goY&oe}SsI>Vdj@S4n%Sr|w&*Dihfu_rq!)ERw7pUo53BfxR%TkA4zlYySGK zl;;nf1qP^LrzYDlcZ*z_JxQ$N7yL8!&3rHUoQuY!a(g1k* z7?0V+BGUwfHs+Wb?VWr#I)z!@TBe-yzRwpVA?s#F?>AF;E956eviR%7bPQ?)t9YM5K82`vfQl6Q?d zOr9Egt0_CO!Dvo{wa@I08ZdoNa@EDN*I`3v7I61$w}j)-3h34*KgEVQw^RgGq(PkO}Vu?O<9^wxSFnP z!YOXVR23k*w2jl#SgwVjib#QU45zRJzW$`gf3+ybI?aWCioZ38En_yfsV8BnKH=r@ zblatFC%~s3;JBvkR_*)7&)YsEP=B706A6+C0PCBFZO_zpP{d)GXEI~#1ID{btD{F| zf8-!J+b;tCuH$y4&B`_b#5m<6QqEsOa*8aLmRnu70zm1`vxz>LnSr|}Rn3cL$jor` z-s3uRoC1*`EQ7Q+Cnk?ri~KCUO_!Z>T@9wB+vV>9LNiP@W6ih8N7*e{Ta-@d9)gpGW2Lc0{zvrv!Yxt?8*2 z;sqc~@3Q-vN$oU__*U$1(71$**KqAM{3?53vw3cfnf=LcXqXF^$@lT4ZJ7hS_}4{h zKSHy+{h(&fLtSofu>@{qfq#dOvaHq7%!qCA-r(k;jAxNJ2ycYygz3I9lueng^IQ&u|~? zJju-ds@hcmD9TYzci)-(a73U`AG>z#+TVM%QmdNI@};i(SGWnS=6}%4mSB=68OsQ? z&VhOd<9#g!MFQrn_Fav;+ula;tZ#>G|5m?*?@$Ee4~g5VeZu@4rd9mI*H(?Cq@CI) z-dp&xnU%JE`P_IzcYXt0@E;z(m#H@pbEWGeh2#5Hby%zB5c^o60Q@c5w`Bn(4<=uH zl%?`v8Wi9_I!(HpCx0iKUu^bGr_;Md=l)G)8=Lr@@zi$m80uO6$MNCI0UfyX?53;q zVSGBR9t?geEvA?$fb6KpqR$%!`aLv(x@M>a%|QX$S(ZuB;kARlyQU~?&;lxrsWC2T zrK>aoNgj)&T;v(t!AJa8s1BEwP}t#E{J0*=o6Su$@?lvBbbnpXLcN-0SxG49o86bk zW)miyktI=e#fqWcz`P`HT^kQjB+7a3=Ot7nO)nGk^@k5+p;a=p>MsfMKvl>W0D+ufbm6!nB@4 zM4BN1xTZn?FMs7Eib3rQx?sLF9L)0qq6!A)MVhg%vmlemMqu4$acK&H5+W{Lr&&30 zopEwnnifkQTb|tHcii^?6OtsQ5e60RM|~~?YpuGSucz*^nDzaph8+cmrt|9X_)Vwp zzhuqMF#O1v(XzV83${iN&145EyMY8Ehn=qP+ettCvVX(TG#gNHsRPyp$U#Zo>6<^7 z+Gm)LWbpemk8g&W1_kehul7Lgv)18RH|Ex0ScmB*MbYz@>-S@2bKPWf(~KAgHt4oQ zxf~G=idQDZ9Lo~HVT^OJGe10}r<9OJ$z>gJrc&UT@vNm0hG~0&G#n$!m znSbIGPca~Ecv5zQ4tllWJ{78)n`XC&Qc~`-b2y+n5EynO;VS{dUQQE^Z5XVe1YB89 zrVQO}4~R&08<78FfYUokSwX2*Kx6dD$>%nXrhhbX4$3B~4a}5p(71y{`C>v3P#Ug< zF$x0|ws9}5=wyfG{iKyQSm{`l+R`V;g}QM(-?n;d`H7)nUqkm!Q)3&U*63x^)gTTG z#Kug}DM@_AL2nXqsF^5uTCnNX+HHo|2-X(R-jCLv``9C?cYqF0)W`MLM@+205aM`~ z*nfQSQ60u#e>ENE6*`cd*TX)UtKa^yn)hve;%E>3lKD#E*w`&k))U%P8w{m)IHi&s zmWaH{RRZ_6)nk#Aol$w&=zZ~12VQlE5pD6c^c|9wi-uKy(vqkvuyl|``_UuTXu&V! zf$byfv(naD+|ld@c=oGkzGxpnTEN3l1%Hc8T4boffP5cCYl*O`FGjCpTmKm1CnALj z3>%jitZqB_J(SOt0u`4uYy&{Jb~iKo#Q8uI%7MnTzL}x&t=T<5VVXGiu-c&2zo!*@ z8f&k8MhpA(S5cbK@6SL7oMY@jXI0fT3g5#rj97rSY;&@ApHN9hp{>*b)p-QnNPkCZ z&cU(WYzbOnJACx$k+GFB-P0{KWg!&XrmLH@PtVerA@_Nrbm!DHUkd|>$R4o6dq)6A zO#uAw<78Gv!3%m8B>acgpqj*~{56^_lP=71U_C}^Z?K!%D*>USb>i}qe5f2GJhIEV z?bU?6JA{s;mZrVe_RQJK**itthktl`+6V4%c?TpQ?m;Dk+xkw>wFiY9emh)$1MtFW z{0l)4{FC2<98<4kD4hs~*p_yr6-&EwScNKgfFQVRA+B*3%!AtZv56wxm^W)D$KAR{C~Oa{|9z} zR?2(8#YAKm@@3_3L!m2af67LHOBy)lhZIDfLrobA*l=O4Q8Te_U%2{`dZ%has=uZS zw-rU~Djozs+%)W??EdJ2?`#M2be_Dy$<{iNS8W~H_qMZTkv8W*tY z@SEn%1c|z>fBDcYmxJ<#j(;}OTKTzRVEpGzEQ}#G#&5U=BNJQ%zu}ZfeY*D?mKg9h z@;Z2YhE96n$&$t^PnK(R-;<@jR3}UK*LLP{m3-bg>2Er(D$KnZA}^0kA102w4Q1Hl#G;~4%7;{M82Je zh*b^7Xv~M#5TkH}547`EoPaa0czFSC?9vlgbpCV+d1lO(=^)KUGuYFo0m^*@Eo#y? zSAaC;)>>?63gGV%cYhVhg2tZWm`4HEuhVIr(j8;3H!>`vJ}|(B#vN2eI`8%ZFjI$H zkTtDjOc_H;RsJZ{2|{-i4BW6noU6s|2H4D&<;BpP<~gMC>VFN*-I!X(4tw>K$qr{% z1AL?7rpfAA0J5L%&@Z=bMTs{dr4Z!Gh{*kLzz>At-r(mv&3}R;IOKH^ZrR%yr}68r#>qTC z?H<0|d)K0AR@P1yB4D3l@kqj7$f5P_&_6r6n5R<&CG|Am2lY1K!U$nDgS5o|ce!QH zK}sI|^!VM1;r0G2@HC1c$Pyw>K*!sp^T3n8k=dQ-a_O-t%1s=@u|@~5sgUqLA)@YfBK*Uw+}v5G|6mp!(kwSsJtl0V0?}; zBFL{O!!Tx5wb?}^g)Q)3kg*oc)|eRCfOB(Ra(`AdaKM1zAIfI{iMOH~(*?gn7ZSWE z(!4MiDJ0&y=^I$|EQ!w(b2f{p&ij|MvL8*sm(Ae^%$#oEYhRq|2} zBbrE}V@lUq!p}`&^d~lr38f&x%dm6_%G}j6p}sIP1`Kh0yyv7O%1xptg8m=NQyQ>u z4u8h4I0acrxkwh^<3uSxpw2F&po22UluZ-;7&yjAK!1AUQoKQepytG4Cz1e!LDdf* zP;_?f@lnFLo86mZp1tmg(@gBMwTTTI9>~3vz=+LQ_J}#JPcdQAstm zp|yACYYT$*UdeG44CZ4?xGiCM_HD`$aGW1cQaeAj$6ro8c*p=s36~ZN77xb$NMOCV!}@nFN${ zg6YNteJIp4npVqbHoJku?)haxf%(V$KP{57HQ-z0cC5FqlS~kC?#Y?a>Q|M4ZVljYERC6P4JM z)8U7_N8coa^MRRMRMny!Zhy*EF`yd+t)b=S)f0K1l$(!c)zdV7bY4B3)Q0CygD@$N zE}xRCHeslbDG+=|$<`pNS+CYRJpB$;lbCr`8vvggX7`xMyY=J89MF#(TF{hA2luh1 zU38K(^U*||6~TaZ{=u!2s*qS%f|J{W7!W)=v1NfN!!^g}j+K|X$$ye2xsriLLi*-r zo94xVjdHQj4X$HKQmD}pqrhPSd?P^0-!y|Qv!sPu4nx$g(JsnY7f>n8Fmj!iiM=|o z*&ycJ3~7nSz}?C4R=V5{0ic7vVIXo5o>4Mf33~xgUQFZ%;jZlB>|gv%v8J`*mkPg(uq;`kPhzP1Dx${przquVb&>XUYh z5rvzZ4haD26Mw4jpi<~VTylZQCIMpvyUqZ&fvr_60{52-Mc^|?9FL@5pFA~=##J8n zPm>g|e=?m#%NXhq#Z;n4%!uslj<`Vp=_-JGE*KxBHC~diux)oOnp;ojw=GlK7GGED zyrbr!B7#p>g$P&=@f99Fyn_V)zCT2T~Yl2qYo*7z0k_%E1 zn(7DNXxU`w7;+b;5=433)*pe*{}zI6NRuiizpo!Z?y10=;3IzSGJA z5(s6DoQLK!23$~V11{UevZ#wwq_8WJt2DXZoPXu#n_TFDH8sqB;B9QhtFsj8SL92U z{zd1KMAUB?h&T87b9-0UxbB|cP+xN3WuF3$y`n$9(GBrvyR*pp9#5OllOFgn9F?Ya zVJD%I!1ZI_sIJ1+VQ--Z!1ZR|sNTZXVQ+HxxL%y+pa~l(eR~6k3cJ*oZqOzJPgHQ! z@qh8&=F45Uf5@tfm|GyfM+vM;DJ}xO0dc^pGKYsa6LLkt88}gSjKTi~I3RlU&D%7q z{)~3FXP@8@bcgFs-~vQb2%Uf${Cku&^7;ahmRwarhmMbiJ#)a7XSKsmi_6a^IQ__n zFzqz{aF>S!&mUU0c#yRCJEZXk-^qs)N`ELrueXAr2N=+Ifmma*e5sHnx*kZpxd|vc zWP`fRHj4R3Cuj+ua0NO*Tg2-{bOQ>)TIfDMXG?pNNgd)vYld*VnBcKLW80gubHjD$ zJ?}IvsTSqiZvE!YdTpU*t`99Py60_y!7uY{17THyek@C7gc03~Br2-YBto%1pMTFu zbOdRh>_?aB!j$PO$tucGD#J8cO>YhQUCwTt5tNU}EQ!kGMLxZ>ImNW5iAsEU_rA?V z)KXr^<4ZlC4yb*B#W~x`O;JWQzu*veq$^1PM+cG{J;Sr^|E} zgQy&+b0#ElM`WU_>0SrsATPOUC4URy(F|5_V=khrMAhajrM?d-VFu)d-0e+#<-psc z7g7x`#$1T86Zs`*x>Nds+gWD5$YtbN?9fpM@bW)ras%`gEZDkVXci&-Aaq;@D<}+0HyS#(M42UKxNltcGlGN~pt&VRgK%SnI&rEHiYDP=pX!%L?06<>amt;u5QkkD62b1DQ~ zc`w+kqnx)YXr-vwvS~|rO+eoWW|MtadI$c5b?w7l3PszNi&-L00#X1B zYG+cq2T3(b8d^$OVyzf+Rz}Yvs`u+2gINe`-nuzky(F4a3-AVj7JoUIV3`|0;N6ru zBah1t&UvC)a>+UW5CQ5rgyf@kx?o0;6h;?Od6C2b?gWP_&3Tb57MLSi&A`m^^U@Y& zo19nk4Ls;WvZ`%Fr_Px)X~P_8k@+I}`x0O{fFi&l9QI>M*F*}f zGMN_1^u|_Dc5C_pa(@h`SRj^C?AQ$BwX)KDCjQ6c!IQyPxp71)mrv<;o!z&(l?vBBD0IUTJ4y8&%sPUrGBXxp0V>VRUU&{_w;4~_j@Ii&09yT?bbanQz)PVKE-c0e z)~gv47L-D9Lk_XZB;8ynZSuuO%7w z@ij5FxV(Ydr+?l~&`uv-XS9JVNFsNmO%p6y7I~FV^I0F;3V4BV556RWvMYCal~1G7 zXmp7a#G z(xrjn@_#7amhbMk9`g+5J7y!$_Qao;hjU#Cp98r_yj}X{zy9a{>goQdN=SX7*k4j? z@XA2BKD~%Ckh{9*Jg|FcPK!K>r%_p{9`SYpgnTX$Y|=UQSR@-(@h2_0!&m$Jrbyyw z8gw^Jp2M*T=P2qQN{|N%jkj5qVs5;{H!ofprhkISDY$D}D2HLnkyba5!sRltShm4d z$(VR~z!PD1n0H}=r72#&SS>xQ3{VYQC%z6H(na zx;%kRGhdNt40T_zM)L+IZARLZp_ElN(a7##Fz8K?Bo$0qCKQl}y4?wI)oE3>QjSg0 zyMMiL|VeEYB=-UwDJuAJiYT z73Hl zzJm_89sR_6j?wTA5WV6H;F~vu7NS52(Pb-q@=Vo}VQ-aA>5=3CaCz5J0mR}eDNds* zoeP;IZG{E1l(7T#?zfP;8h8kvQXkXH`+*$6sHi{E1RZ^2k5IM%O#&xXn^=AjVt@Ip zPmqoJbp>eKT$;SQ%K>1gb~OnE-njemjDS%}30MmfS0e?*n$k?vj3&4OR<)yM-Jd+~ zc~_}BWJ^z+>!h{$I2egp)xY;TRO%8-N)HSsj<=K3pu|b^g`dWLllduRGVIzLW&@3^ zn9vgUgD2d4J$u}-v6Akvc9!YAg@2GfhPPp6`jgNjPT$qQKDS)g(;|se08y17mkrnV zK)|p^RkA>~E3dJDI#v{!<>%)~T;~YpON>~AYYK8b@)?%SkfXBn8#fpXa!b5>K<9U3 zJ=c6D#KLO@sGiorv<{5c=066YYNnF2csa?Uk~t*yX8x?NnD->RN@n>Y;eV1-Pz{Ko z=^zOJjHD+!mZU^xaOM>mN}PMV95?|Maad9pF`!*iQ~1}rK+(uJv0j9T4*7u#E9K5{ zO!cIC12Z~1L-rk&%1E};Qcgh<>{} zAM$yI0zoNygI&W=>!}$!@qd6S2j)9u|11LMnj@1Grf|i>7PVr{V&XA85)j#|+Lbv>liq01!FhbKip(I55G& z(OKA9eE?@w$uru~=`6=`KeSj2EvUY-aI3WmT%@9k%nLr5qo^}>NqWQCmXzzz zhm9;o_aV>IG|584^M77u4bLisj@m(#Z9AQEbM|ySJj8(h-<^E%G4K?l5b4{KlWfup zL-*L7>qUNk?=98E*s(xm0^SH7f_1GwCccE0RAHy7QRwq>w9LZrJWMD@sbv}kTz zAP+$bwIPYc9KXI_-;`Mk@Rb>CxAw^9gD5i?7p^0Ih?aKH;!Z>-X0oRglp2625&}sVPCckDWhK57cGEUq1dWh3?+q{38 z&S;_RzT`%Ky9T&Eg%0T<{*jdElW2}DeA!5KRuh^U8Mp(2VaAO6!ou~`un)+H5PtyR z7kFFG_J3Ll1Oku11etW4$JQ0=?1TfiH*avATqI5{uCUM6uUSSj+x zjk~=8?pl|2ZR^>ci0{7r{gK{#;2i;2II(F~=Ifc@0i|K`K0|Jqef8ONDT6h#Mkrpo z3&XDq&ljMri;7PC>qsqO@DL_3P}~V(47=Qi32al+8!s< zSp?!N$UxEb!c3aNpsgeGU>;#rqjF-pm&wg_4ocLo$cmGkCm+Cz3hFqNy{8z}gOl9H z1x~=Y0PH&%PwcE@%3}YwOHt;V2v1JT8HvHy9yET_oHc^~x5;wz;>7_6UQ>-tSza2G z5`TpYxzl%;df*~Gzc5LLsSv1y^a2Tzjw^4;kP-OQH^FR;v=n4oI3SR+>wi>LkhTMD zdJ{W~I{aH~2{H-k)C%%1QgGFt8M|xUX;dZ~X;vm#nO3OuLW4Mf6i4X|%vVp~s;8f3 znaw~MGr3OE^9!)XU#$?Feq{u0+E=8GLQSOr;k+ zUe_wzj*|1e4~w1~8qyXN%UNQmdpm!t48q5?I`Ri;md=-RMjIQvYw@?rp)3$kO-TNc z#OCC4+NAj8b5&v8hVQ1?G4vExBd=>@ywelvWw1(+oB%tQG=6F_@J>z7;7Hi#=zpaE zA*nLDjbV3@^X6ClR1aV~0E<==j!(SdaM@wLg<D*;zh|H;8=L$Q%kDuyjyoF_up{6BoaJnJ!k5p+k5mXMa|Y0=VZ= zd@R8DGzGP}+(a1MK{K7q%@*W?FmrD5`syg63Wsxu=;BB z=FQ&G%lAL*?SK2-8z}BL_&RKJu>bP?@%Zp)4_sp zsG;ADHEJ+pty6DZnJsR;I)61Ff9)sxyBf&#(u2XnmzM9)GaCr@(#{YLc0v^_Ac+8E z^&a(xHMkHR(<;F^1Tcm7_C2!vA&}=5hC(K-qQY#Mxn%=D;b?zhiE3_6L7&letsi*d z0}onR832kZf-OVEE&^*rTq5L)iQt=!{A@#}@RUN@C<_kgi)}&yC4b5l=Rod4;B7gI z2Z71&`u0kvUyjDrk(ya+oxnI_Pib7Xl`#+!4&|{`uO8w zjMm1k!dLHt4lS$WcW58iI}BdE(+i;%7Ommbxv+K_(qX;v+n;2no*oguInnXd4p@X5 z+_H$S)#)UB@{@lp*?)aHM0N8*aW!B7sc}zG4HzX38}8FbehY5Vglx=yERT~z zBIFq3-1%#;P}3IG>s{U$l+l=Y2Sv=LXsR6R6E^bz5V_0oIe)F(rWiV|@xV$No+^I< zPsWjmyPP^(Mbg$b77+TQ3Z2JWpUfS{Nd;m2R zr!Lv2cR)iA@qa2G0R2dl(%y@uyD2d5`DMBoTtsCzjcX*8Gc!nXWK3qQe%mgA@u_{x z_lCVEn(k;_RfJnAvTyhAr!;=2ZyMU5$9;dp9_!mCL@cZ8Aq?UkZH8ul>bH6YENX7a zQU?40YTHvg*uP7dgX>Xk%;hy~hI|}cHU1rUc?}@ObARdz=U!g>C$-n(!M@s(3EaAQ zEL!KW(9;MmsLZpf*BN3pFdlqy}uyvDa4aKF7K$V6)8t>pk%ITSQ1|1=CQ7fj51n0N~$r zwjT3e5TYzVQe|Ykj%YO9m7c8a(yq3pYhQKxKPa-#awT8}-=NHF=yOHkB42@VA_9~C zRWwUu%AaO4FkYa_bWx;p(wvFQ-Iu;kI5`*;CVxwkn0%?PqY^lOaMRE7>ptb?0^Kp{ zAzq|sn5H#xZt;bgKpsMzUpb+KF@0ODdIR;%F=#s6G;J`BWE`0$F+?_tOIZPVK;b-} zqgs3+rP9V?B(1tood{|nb)2bR3V|=iS z?tg@503bhr6-|0FSN;w~qK)%BkIe*qs%B})AvoO$FVTIzA4A+hnG;tjpubF#1*s&| zelBqOlx2aK$cqSXTk?RWtya*TM>8P2J?kCF&d z>Sa0dx8OiJNU|%4JSdm*g+-Ug4Fm`$IqoQkjJQK|Se)Ge zHA?3Z1s|c7gIlI_-kKVeHZ?oT#eqg}gQ`kzz6dNlX_u2-{-3%H+NM*;9Vq_{1CKedE)pm|mn+f-ZvF)*WM> z#~5hiH8o^@de1M@q_nSpVwsQf^|rUr1+@3FE4X7yz06Zdf|{1b2K9hZqQ!VX2mn3d z#NUiwe*0qY{n6puH+x6#-yXg2=6~vC5T0r(_y%$`NFr96xtE948cL36 z35_d6Q%jOv^`%yL$^Oe(?jrXAvuT$%efx>h1>q0`2*@G(wwQI3>}sfB19C!h)F-qY z)Sw2;L;ZzaypmdiSIiiB0q$2%PEIzrIm*4^y?x_J=FbzcYY`=|1kVN$PE)x!5$C+4QPR%pU9ji zMCiG8ncfw<0b6b0Bf(aD+AMAP>7AFEsK6XjDjgd*aC#%%Dv(&)wM}>T(dDVDC9+e7 zUzdh$kYDyx&IQV~FkyUlLw`~7Gf9OuQPMDb33hEhwgC(V<+m_f1jmi3^?IC@ulqwk zT}orMZR`&N>bFq2QzQ$Frn$+gi=;}Y7KvC!L~$YIj0Q<|l@@u1X}T9_R&ow?kFl9Y zHyAwtS3IlKLF1lYVXAV7Gn<`lT;$-HPwW%|rZ+Gc&QEa~!s3*oO@ESmOTzu*CGANK z(=3jG9%js$Q<9j`jk@um*r!1JeNa^j5$^eLR%k`(JDhTZI-~+Vv<}emQ>I^#f0g0^ zMgi3wkH&`_zl36&ThGiC3(YgY#E|7?hP@jzO;wr8T|Itg(#5YG_3@$h7#$3 z`f?ZZ)-$7BT2k#BNPmf7yhrMe9%AN42D92wXHr#2Gtdq(`ce&C9OY)^ERh?};9uXF zCcI^b<|l01dFM5&-L;MFoU4s>HtDIug?r}LO97=XK1LNUp}{*dq_vki!mxn&GsqMe zBbUq20h8{;ZrTK)1*DiZmZXZ)S(F9Arb>UPaAob{FjCFaw}0<&Wtis5P@FrmK;Lk& z^r$3cqJE_YK9J6IT=5{#$Y8_s!F)_?<+4<5m@8~&n!Do&q(&y#$Mu}HLX1WU~lVu@-$<;7D3U&kd zf;hrGT&ECOhkw3J6G){5UiK4Rq61_x$x|RBQJxhL=2nSZDEyJE)n!PV|N1}wFBN$V zzkpFAfv1`ddYt>tE@Z>acb1>0Q!9A6?Id3rh-G47yp6ht5+{|B6F_kQl8;Y6(SaAh z4C`CTh2@%eSZU4NhU8~z&>-C^?a7vZQf+G@8A{j5w||FKln6$-FX&01JFe+gWMMw$ zqF<9;%3qzthU)5HVIYWvOk&W8gB84?_CT#28i1dQd!K74w(1}DUc7k!{P5^tZ}GO)aO2%qUu*_8>>rZZ>^X|)0<$^&Uq#=s z4c%kXxqp=qpl`O!A0A^p%BIP%GLk+Cx#~T1WJ_bD+Kv~x!QvUGkkKG660{th5&?n( zatfRYsM{B_Wy$B$ilCb*QTTbQ#0u$)77?a5T4)(p@ZZsWn$dn-KF2LgY3%gholU5XDKnXT@lo2}ZhrAGjhU}z%z&#pjmwbp<+FfP7^&k%xuj@n%DN-> zG=xV{0e^b+4toSIr%{H1x?Hp&L2Z^|66;^n@kUo_2 z`?8Z_oMg86&cF@|?MPq&|Ds0m80Cc~85nN`WdN2o4Of6CFMkZy3w{m4A$v1eDVY1S zH(O59@5{t^*R=r`2}06LefAyk`Hb7?OES2V`f?|A4*2v^G&@jjJier_AQ%oh&VM)J zv!;tj#;cn{TF0H(ansU~2uN+to!w5<%?%@4h!4tqp0vt>Xq#DULU4)q@pyuWt@>w_8xMQyghrX*-~R zpj<^;(sBmeGbm_L?M$xw%aRJQLAlRNRj*;bsLOXyd6CQj<_pTm2Fjy(RQ`RL6j4mH z@kB+lTBoO3L?R8Q4=-zuC3 z)5S4@cU8H~b)pdJLbluK{jyy0WJ0vC?K9ggQzOp$xS{q>n=*;Th5r1V5txEIP} zbdhICDWHL(i4}U?*I{T z^!hZw@AB2r;eWxaH}A*ajgDY4e}4Sf)7)Upj9y*0*`F1FfD4y(_nIi8SEfda%ZTSG zJ=pd(-!f#}8md1Kijb<>>V@q1Art+Rm;jU|qINYZn*m*KT*P^U1_SipC@=EDGgi%+f3wY&d+qAZW)0LeMi33y*nalSg^ve z?e}vUq<{JAM*i|ypx6EhEPX6mG$j#qR1j&Z;XkqWJzV$pT4#658J#OyhJ zxZO7tuV7|9?HP;PKUhHM`aRDpa!hv0Hycrjmsb72GXMF-M;KhyA=5{-!*4rOZ84C5 zZlu^E^aO=Ot}q#-uJ%dc&~tc0UK=>3I&#*Hwtvwb*=i6pu4#zXowv#G2aFE#Doss3 zn@<;r4DTKUYV96*s*;h%)Q!c+J2=8_-B- zMSng#2K(84A`%~f%p$_r$T;O00C#ZH^V*I4Z>}s>Ba*eFn4D=IWkZe9bQLp(IXM!I z1$u678yvQ+XpUOGloFi+lbGiQhXk7eU&TOQfwhMwZ~q2Kua}Vc6rI=XuHU*^DLyzI zHxoRn0rAP>2ZLfeFVg}*`d}T{Ei5;qz<)+e$rEA0?NKZ*Sm9qTl4%;@DdZ0F!r%$= zWA?#%U)V6;!D0q^RpVnjhX!!3akg0GvfZ@tG9;r}x$FU1nDwHseq-Q|Xlt?3;ctm_ zwbN#ini3wegzYXFiRag`XOz`B*jIeL*AUJGgU#f>LN~b-wt>zJ{IN zKwQC5J9>@|44G+Efnc<|IXn`_5&R-wR1^;ER2$;iKD)X14W{!^LP;F0hQ@9_u4&e<+sfX_LP1mMc{L=0vyOe^e zU<)&G7t)71z1`XXeXpA~1qisL5t6`xnkDghG6Wv_rt_|kY<8O2T-QRIRJXT1^&|PP zNTyYngHKUO*&9^(PC_L#TU(Oy5}NM)eHqQVB&&R>2B1g*+(EALI03xHB!8V);Tz)_ zz>sg~9 zfIYpt?@iKjA@v%W5j)F9et(rhMo;`g+=Q4A+D83YXIMO?=|HG5(Ji9KO3W6u`??(< z8Q0z;A4Sv8>7SY5g2s>8qF46aFn7kKy<$T|3`JDS4e+kV7^911wg55Y01E?|$@fAE zj->5T0?0pc#I9DojED#X$xY}`fsQBH0K%S_(*jH^7_AjSwUkXWIe(qykmqJ%HmSd9 zbd^Naq^Gs|eXireL2vBl@^+tOT{`PPzRT+rl>Kb>;-Y&l+YIP$Ipa)zW*!gzWacUD zEXB(68g2HkavIGN!xezX;|$%m`Cf6#n?7 zi7@gXXH5o7BvhqMPJfMc_e7h^>DJ%wn@zYXi*yS25`@b!5ysg6lg|luC!d>n5@lsu zezCS)ZOtm?mflgleKgLEgUsmi{a}+&vUB&yyMlV|TWCvxIW_(7{=wUW_eXo9-Tjw) z$Hz7W&EvtJ#M7BEy`@we1d-I|Kmi~Kf?+Fg0&gLs(P`U-;D0OXy<)LCrHksvd8Q1! z$3BT}0)iFVUgA6kY55$Y_>->g2lLl>9e-mU*PcByPa4X123ze4czWMDKNn0QkO)}P z%>4AYZ=Sq+3Sox{)|>s}BYRnRQ@pJg?&Qc6AQNeJ{%y3_M^6dbuz@^3AW@D*UMy5h zaSergg2Yz+9)Ec5gIS*Kv<%|ETEAlqYxeq7zUegw8YkeErE}QuMUbKCFu`=ZJ6V*6 zGFkem-xBt50s_}cJyxx$%yXQ?qbY?}*bjm?Ze~^{;imGyw(wzKAJ-r*qOqrlXq+(F zP!wu^8Z3!m)8A2Ojr_#$B*zghVqO?&P1{RY> zS>a7oUQ$I)plh=P)L@k=vV*zHsjl5d3^hnIQ$^)vIcV%jDkorn_&4w=t~WH+#|a3J zkiylLdw(T&E0Mw|QKKz9xkD8cCLB0mMz8k!QUn}T%+qCA<#U7SK~HB%&#`4Y;F65e zzf|DOv%C0BGf}){p9&91+#j+_ssa(_ad$;TO_$ViaSD}*F`*-H{cICT5;H-r11r69 z1wyciAk!)3;xw?xJ|BS>@A)}09FrqrRgbClaesxGFhdRuk4H59`!a=;qbk zXBQ6F3*wZZpMA4;70vJsMN?2-V6J~ydk&3BWURe9T4v#!-KdI2{-oT?l?;)f829}m z&lmU{NoD;@*X!FdDPBVaj~vLQde8)s`|_q7s{KpCNUDq6IbM@%fGAG&qv0JqNK14k zyMK$d{n*M^s*fzq(%)stwU0Dlsk`1U^S-I_<@AD|s&1qramZMoh>spG7K@8G&%N-# zx;J*xlGMPx;;A9Tj??VYy#acn5zjvKQABWY_ z`>g8;9t~jmhG$@h55}f}pZTu%^*vj&8_!`ME*vGaz)vJ_E=9e(;!-J);fxd2L*w^Y zZlu$vj!0#@*2nfdHT8gLuPpy2uH#r&*vWj*zHeu4n}5m_k}T#4^wqexO0@X95Pvqa zwVKd%?f0t$8|?`a+$FHw0A4_$zdNp%u2UGUHCn%+c_0vx*3JvXO3r4So{Vz|EHg!H z_$`Z(2s--HLW+Nfi{Zz$eG8t;cEM9CiM4#XJt&eha6@v0`_Ut6+hsi?c%gUgng+0i zqpZA6Kv}JVjnUsr8sW@~B;EtM3T8E5_``ph*@&0ljPs@e<7+FeE!0_-)E0o!I=M=7 zVAezU(ml;Gz?zXcrbiGoYR)Wj*%2ToKH=3>GMoHs`@ zidQG`(nHgoRv&y=Q}$pB(;d=+K|AWG=hd2#tV!-k1JYz+Z`%XErcMn~ZP04Ex-6WU zuP^}^nyP|9xs*?pFFHYIpjZjDS-fF{LYmk&VvoRw6#l#yGtVKw zt5g#frm=evf}OUwTjiGML7B4JxdjC1`N$E0XG=F?iYOQo_8(0v+h(mngs`E#=D5li z!&;y2oh)wKp=f*K&+fBIqf?adoX(Sq=>9k_tG8ubpIO*K&F`ZTFcf@iFloc{Vkw+Y zS(^?@IL2oUUDL|i254U)s7im51(YzTk_Ccu?b|t7blkYfLWuf3#0cq2a}IJld$+uJ z5z!ydi+s+PwG_u(e@v8n^nlc7-yTWskq=z0v{Q{`E}hU^Cr)ef(`$2epUl(oszhB2 zQsOiwf$w&i7~+BP5I{-J+nea*w&LS;mH zOr7CK#c&MxnYCiWlg@ZH*C_*c&SVjo&np&9y-j+%4MC; zqcrnh(z8J4&ka0Gb((*C=VN`%0>B-$nsB)nC&$+Y3rq=q^LrQ-Y9BV67ku+U!-sbn zE(MwIurqpi+F+n)82NuwteF8h8G}!`?b;F<7I2$sjsHzZh#Edsbfn?iM$y_QHgL1} zUkaANeGz;^oK?^c9`bv3A;V$@wRZX?w}-mz2lbZp*v^x0^>BI%ypSMHouj3b!LKAU{LsjFYt(I9`aiyKc&q&lc$A}#41 z#~R(DV6jg%uZ_8ZJi%<{qFT>)XdUnP==aq&{{Je%RVqS4oCj7E;xu%5A6NESI2kJM z-ea`DU;q4i#81WH{|&W=%5})PH*U4_4|VO#eKj*Q>{>g}sHp6_d8(wo@@zmJ6_A9? zKGMGO;uI7_Qm}svv2~}MZ#xw+|4xKzkz=%jl2Ibe9lc>a;d^vNTS`(|R z@vc?!AuTJ)?;JD)jVv1FJoUN$gP2hyA>u7mps<3D^rn zdThTv7!_48 zxh6Hksj^DTf2p-#%YO~3-R3!YHLFd1ht*ppwexl_mQ|NqYWbg@koUlrkHLI1AsT!8 zwrz`Xzo&hQF-Aw8O(7MCbK}zjy|P~4)~5*RG_n{+-yX2Og4Y8m zAK%Bxtcp541*EUG47W@^RQBRsZ(outS0~GaY*K$vN=b5^#3IKB9v`q8}EOa zy9vSU#IT0--+9*uaIam{Yp#ih_ZGy@^eho^@7N<|B-YxUwpGacz>^vd$xxl1Uw^G> zv+sYoZtTljH9k4{eUlm9>ty+qYkPoJKh&Xi_)ommC`hie?!iNzr^**N;{!hjT0N9k z(Qp$;Cyrd_3kTf3z(@>80!$uL(cN$sh+g=p39|gE4qfTEf%wVE#4gq(Fn(jsY>OZ~A{PRb)MA-6^xKIeK`z3-Xguc{yzGVYT|Gq37Go z`HMsC)Ue6tn%hbm@nMULt_cCmto^XrZ+A_TP|&M#75(UIZGr?0oWuQTv%bdWfWLob ztuGM%HM+9}bD{m}o^=YC&=7&zv`3Fzm9;^H<67lvqX%BiWNW((T*92rRxn&G%_`Dl z@NjH5TefQ6YbKh`sE+gmf`l$D7MD7VDEK1@uyajVsZj!G)%4JoEY$b64!o90)mC*k zECZ6LC*021w>t5xrH8a(+*;AuxbA=Z5ICh8|EHr$ntq&QW}P!{XViAop#iDohYeo+ znEubREv;&6$;Lk`L88f1#)3v}V;!ROa8&6Y(^-o${Z6Fm&S~C@JpJzUNl(|YU3W}d z!?)ghBT@0{iQSJPv7*OOCg<$pCi=}A%Q+p+)2hMf+czC}QdZG?(RoI!R6>7T%NrsW zZ_FdOMWm4{>EpYlkmD$+zg%02o9g!3=x2i}`c8{DP11^2jgeH!^36KyA={z*Ooxa) z{@6sJpE?C%d)|SktV7%$nvr%y?h{b(fF-h~{n!=(@oBIh7xl~)Vsr8{s87ZAifat}>HD2{L|Wd^hSYF{9y<*TV-17KE7Nzk)~N&TrB?~*lYFuhcvh_9em;wS>`j1Lr*7D^GcDmHbX}GP{4Q2IxS^A4u{w zKxc93)4qJLL-^a~;d8ZT{k>`M&qr6_VO^lc0;f$i)bInYLzCF%z?AVjG zL1_iBN|$O!X7k!a>wdg)tc!d$tc6N{BPF9u{g2wp#0@G?JX!2;K4>}Q;M?J(PpErx z^)Ib1D*TM=N4PR%D=dHe9ajp*n$pAm079;nZyu1RRu|Spph3J^Psz}(ULks2rrG6# zYB4O2@hU2!c~T`s`ORv&-#4SFB?`+WFA{4+FF|;^WoIKn*3?#y$S0CN}6WpUc>OIxUxBZfZBzTR(I!jqr72| zff!P2*vu8;+)nwzY&|^_r!nN;I8831*%?^FBTn5x2?0327$z|*D@wk0kzd0DbbKDD zL?@7xY?^1&q{x3tDk%t=sv#d5l`c}rPM!?RmtVfHSv4p_+&qH%J;|3}VrmY^VFCb} zn2l$U;gN}x`ISu*=2yu5HccP_=cI3wMJA9paZ1H8B{2v1xx)y^_e=(6Or3E8}EgD-x<)zDeb=mJ3XGR`Bq^yZppALw-M$K09?Q; zgU6^~fF@X1bTXUG-@~LQ>~R3=rQ~`Nh*{EYn4T`H#LaqA<(ElT?q^WdnEpOoV!dN_ zWu6droGyRoixP(!M>8zv^8K%4qWx^3K~@#aa zq^zC+(|~0yc?FoL6BgjX*{$GE9JXaByh_W4j5ns~{PpjjJJ7%38kBmCDFj;_=CMzI zV&s2?!t@5qk2zileu;$}UI@uqHH0eHZO6g$XiJKZm-FR}s#uXXI~^!V4M|7U7NiQ# z?7-GDq7&&#_|$r*`t!e~`$G_b{r-=ab1Z@+2cLkyX0JBz!SB-%iFFBr=|WMqEWd`o z(=#k}ag!*Iqn->Y7kB_A2oJ$^1T!t&BI19glpzB>y!0ouEhwFIFKnPWk}iYF;IaI> z_W`;4Z$cF9I}CkB{1fn09KW?4R-mIpiYz2$$VRwJ-PhzP04%4{yM5vYrc=V2B<5=a z$lS6_V!d(O$WK>kH2cO!?Pn)`tv)JNCRT4O96a$7eAAJ#RrIiZZ55+9PI(=Y?+kx) z@t>tra|Y(&(#(_M9P4ioO@-nnNUxDFx>9>2y>8cZ{I0CceYfx^zjo)DSIs$VH}F0O zJ!+MnrI1yZuZ|LnqE$tdl`~9$Ciei=w=Yr*#k)*yWZte)2BNVPyWjU7mo)_-iX=cY zgJ&evdyhnqpt`dmkzPYV&k!wFqhf!3TLFuP0bS(&LFOG*eq%n$qsr^#_t?++MIRPB z98{3%t#=8uQ)#*QC`$JPoE-dP&?s5$O3<|JzR;t zc2uVMT;&nA{i&@;z6CB|vYLNE8s84T_*g_)oX<_UJUdH2+)f-ZsxL@)Jq0b3*Yj>v zK`_`ga_?_lvDP>tsRm*brhfJDEWd`Lk}1&Y&CCl(LPadd`v zFv{>zWuoi`Sy0XKbbEjG+sfaCH*lS&m1IoyCH7=s_M7z!yvt9C^2!!8t1KvfVTD$p z>uFG=Gg34lM^Y8hRWkFcfm&9Yja)6 z@3iSwN>(ouyA8HIObE@IRdM5Og;4vo;NabdK{1<98#o4Um;$T9TILkgf_xy6G!S!< z#Fj=t`EnweEH9=N>1{xn2wgl?G=NJTfbdmbKqb$#lF!)AYl{)oQf34qo{l#6Z*6b) zHF?KCN%87cM0$UXH1?ff_@O5900gm|b%Nq(I$#}0_ckO+_>}R~wc9eDWT(GYzRU0n zssixcdZIJc5C?sLwMOCEZFtG$LgDkS`T{EN>>nSpOS}3o)!FS*%xX9J;$sK{k8?Oi z#-ZDTNYWFDa%{QMwBkkDDz4v2?cs$A4L!M|?dFAsD29KmYB04L;R9f69C;2j@tSWNaNrnLFDHX z-4S$=pGh<*69KMwh@=-iE5hnBesezqV?a<;tSLW{rnh&@;}CMd++!k#%WB0;ptoQu zhs)~jxiDQ@b0*xnaURl>oyFA8+;{75fBhH+hmKfxHPYs$`Hy2;qYC7+(;M*eKpu1= zs^x$G`k((xYm+Bv6L~78Q!cC^{dtlFNJ-2`Lf)P0WHuXUxY2|pJq6V44DS0LK~cu` zfV6^la_qRUNo6Q$frk7%hf-MF`N`*n737%X%;w zM;X={cM_uj6PR)|e4Ko4D`uX24gw`S&@+FyJ^5TG;;b*xW-QcS$xEL?D>kuqE!Ymc zr_waxkVa~fxDY(kz)RV7Ldo@R&>KMY+8*#JbS>5=PYvJrC$dhZwweZVAKb1tw{=KX z(?=_3hn+T#b&vV2OUCM-w9L(R*f7dH=4CIYdrVBeX_=YpvT2}(xdnvR*GkmyYGHpp zT@(L%a+3?C`rrcB=CC{HVIyVfFg4*}oP>|W!~ zG;8kb5b;1#iy-hhxfAr*E|ax{E^_VzK5yOTy|o%DjRMSt+(}^=>mnIwbX`Iu{RwYS^Sr5&`iS`p+O&&VD@!xxc za+Xe$?jQQ*$>ZK#j%ml_vBHIQp(;INtMwycbrg7tfj;R1Kmsu;8|<$-L(_kBbVQ>RWi#LTo4QWE|&}Fk7`f72L}LK zth!KMq~!#(dg%VZg@s8u))y<8m_(#TVi}z!EfqQe?#+dq8f!R>UA9a7-B!+Nr5mVv zY2C;1MNp~fes!3F@TZUY=o zsBHTFZV8CB$Y<&F#^Qfh2xp~CYu(vs2BzF7oo(7(rkkw&Y;Z}6oLym(=|v8XFIOOA z5~~(&cQo6@F+5PLXA(XZC&?xX$Jf1Q&s8U6+qd(QYW}&?B2%F6G^&zwbT@Ar;-`+h z?_g^cvU!llN!v$3Gy0aODtI5Zedb!w7nrdVNPXJ}wgI&-6BK_+udpy?^B1ZKeS>B- z-1(PD5+|NlADiLEwuycz2*Wv=>o4ehYgnH}vl&F3y+XuoU6j`fX8Gy3(h%cS_m>Lu zi@T*Xv22QzDsBH%+Rzu0ti{c6MfTp|>N7%s57POr_01>j@4Pm>AO*mB zdH8v5ZQ%NZir;^2Y=W!zy(aRs<=4j=wDi*|=>L)RZ&y|3U5QMeSeV;tuIhVd+v#s? zI&B-=&i}7r>OG2hKiDJ!)EAg6(c-Sq-_Jpx=!6n3(|O4AnCoqa7xYFQZl;{&$&L>tw=QEv@D1WmfWA8T~sm}q`t&~b*`BqOR;oPyR`=Efux={W(7*s^D0qywKO*X z7E2%fmv^C5m=R{}U3!qowWhz~`%ZPas!mHw!l#utUu)M5H{WUydX>ahY-foD$^;Zj z+r~Y@`?X1dE;+VhOq+@z7#3XQ#A?hHu^I+$S_6N%UdfF8#FX?@a`)czVy~;>^hMmL zsC(!u7L>hjsFDocaUIauQe-P+&$m}9UP^0Ux@O2ZzuX{FlAZJPOEUMS@%WX#4sDW| z5Okai#rv@N-uuYDUY~H~NhdWnL{SO>q)US5axC!|^1wEc(h<@^e%l5Qa>L&9;=1FW zeQ19e{8Qi4|0;|97rEzuFR(u3#r#0L!$0#9|FwA!h^rA#y@vokKRXU`IIpoMFq4-i|0Xb#GM;Nfjhf#ZZptO}9}Bd3 z9w#$-jXxB)#0}kd5e*ukA9~=qX`5I_bkBdO(!PdJ>uh>+U}=thV4IvlFd~#UQ#`SL zwjOOQsuWDR|8Q{2L>vmbC$>?hkT}x_JO`d!O;montGY zFXtN`bY^`Ir59@nz3(Z4IOx2LX0~k8KYDv*LT-EA6V*>OOV$Q4wr&J)qx85A*rHH*~HSanN0Ih$e>ugKox&8YZvDU#`KQZVq__9wl<_RF5F9jKHoH$424_SN8E(s z<-I2M(Ykvm*s^WA2p#l?t#3@Q!S4CmJKcz!C(hkwjt8^VJ8`|8U&9ATDo%eRY?N1< zsQLovwYb26`cE56m9Gqinw02p*J;MM#>(!>9ckD!zisa`)p`YCK^G?dnR+4EXPugK zJ9)~DYQ9XmZSYdargCBO)@z@0L*==o+u6s~jX^mqwSZohRP$5vmKMB~kLnIg-dEx4 zHv7N(7#z1K+tCfZWN@pIZI=Js_&goDY=T z8?C7K?sh$ooV6{5HyIl9$RNK$8(ca+2f^`dIovO zmAWjF@yuJNerhMhWhL~a_>ru4i771Xa+fZBg@m-h5ek;3Uj<7P(C(w4KD#o8F4mTp@cQW(XqV9du>CA?IXwvNBGPIY1Xp3p>gA+uU9 z`bY{+-8mZ7u&%qfHTTzd6{-It#W`R5aJ-_-CObcap6k^N zW#AW4S(yp)65K(jw26P9C$54{#;fbdW$||dWYLUb%eQMqx>gRuT;V;2A-93rBF1?J zdenam)9gcrR+A@YT|k;6OJYo%@{5 zdTQRtlygYFORG(@^Nq7vbd^K+cge>lPKxwO>crTiA@x&v`P^7#JggtUHGP{2asOF~ zYi|+f}ANyG`*mVA=twWu!CH1s#s=xGP|qFQ9Z(es*Ri1dPd3mq55Zr3ujk((ZhT2~J8AEf%wzf%!gJK$(B9 zvP!dQ#nn|-tBd_Xeg6rpY=sK|ajf)MR9{GQ+V2Zu! zH!kwds*K0LAr>3QKr$(3lqr)Ujb*TBbbK(ZXTaHf*q9Zj-OzaXO) zn70aYdO?k>gsz>GM2l!qCNb4#`4Si~NONntr%;3df+;wuW|c^Vr96{PdsN|XzMNGl z$rBTB37KF|9(!6YW@$x5nR0(=HiSzd!wOB3<%ndG-2BJ!;mZM0qx6iDXi4ERNR^3s zfT)-vr6y}goYh{Nd9AcneqU{OhRZS~q^zT=-Q5-8)L#)dx3X)0R-ah+PNh0QWCUEq zPQ0vFh$^1g5|n+x;sx95RX%fdg0pEJC&9Q~@=dbUl=K38 z8yJY5OK;bsc61X>K#2>9R*Q7OXR6d~ZzsL52fo{MC#*7P>xaHjU2*P9c~6y*?#g=U zweCR+|NCcKaK$kx)w9FbFgr}(cQ(uOBIt~#=H-x=a*lVbbiB-`vY&YTeAi;wWk&Cy;P6M9n)e$LaZ+c`PO zI=$PIlkDUqo2-eDE|E%rlP3?B9p=$0JSN7oj>;dcY9#K{QKJ&&E zn@z4m-DAwvyu@T>&7Bo{@ZHwT96k}die}41)*b+XXOB$=MY5Ph)1D-wR^e{^{uA>`sTBK0Ho&?i|0#;a6$=%hZnY<~YX~Na5RQ+ktid`i|nV2{& z5?U0b_(Z2}EWCd($s-TsH&pr1?gNP35J+Qi;~KFkv|)>H{G*b3PvR#B5*7K1{qk_#B%dggc_TB zMV6te-wY@&0|HK&o;6JY8}8lOf^(Q7g^FDnlmR9l(O^E0`Ksh{KW$EEV(u zL0LaD&GF0VS%V<=M$eJvDfanVGzp&5svRHXs>j(#Psr5r~Y7 z2%(Nc3g+R=JlIybTiHr8wW2(M5{QizS4ozV3qV{%DV_(^>%p#o=}M^rORm9-(D1eM z_-$A8XZzg2H}xq!^X8;;{x#kpREaL45*4r&gw+E>D*77j2V9;poLO{TG3oop?q>7p(?0rZHN@5fEC3l=cSg`KsmS)2T`rn8cv1at~6Sm?)HZD zqu`};$FbHaP*xjuNVK!1I2qN=B6w|?$BsL4M_ZRIa}T$-O_xsmB)O_KzWDgnSKJRX z`!0FfF;YR*4Ut7HuxMmoZO3iHccrgn8OVQ~F3brfo8cz9$dQUMA8apU>~5daGtX`e zU#eL7hVHfvAW!^RnZw1)%m7zcyeSbhSQfa#?%=vkGn+KOjQ~|Sni9YYh_JkmpjD?( zBt^BHJuOhQ(mg0U(6Fhv5dlZ=%AJ2*?3zx5<+kCG#qZU%!=3+E8wFTJWz;-s%Pp_d zYI>nH@)n(^AU^11jl%#P724C%I*0&8mM?)o}>Kc=wz z+fe6Ca||<2)+XxBuW4Fg4=r;uw`~K1ch{Zpp}wpSJh@~-@FL913{8g;p}&8>O7j_> zoMvFUXQp8RFq6J+(hOzY!uLh9^D9GxL_HaB@I_-%VEXUWCB4OvmEcSNwfCi;F}LaC zT8nVbWj!xg7-MhRkSomQ^$@2Z?6V&R%+Oq2Mk@}OxpRtZp<(+3n=s4o+=J{|llH!# z_%3d*U1B*+hf0)H|GZ`O^<;lp53xn{Ui%ovR8Q}uGe)i6r!eEy1MgJWy$AVq=k9%! zd}2sdVsU<0@HFK2Fn%%3&~)w7FW+IfIrx6X%6<3eVbiNdl(_5aQp;*} zl5U;}xkld&WNLROm|Hxmb(0q7YM)rI^0U4h%r2wZ10OEev46SVm4koFtec0+P>stG zWF^a4ABmb7rNuyl_99O~OOS1#8ekq3mq}5QVn&tAYj z;lPYYzi92{tc%L?83=Qepf6NI7742{v?xAUZ(lfX+V8P5-1YCaH!aJuY0VyZO}1bE z{IusTGwb9pGwbCrGk1UIF*EDsGBfMvGg|>B_}t1m$;-^D z1ZHNPBxbD(uciC?{QyuK%4Z(dN%Lh~sxQVY__0FJsL2;?v_ryY))cUGHA@Ih^c>vu z%jgu1Z{SA2x13pDh(!+TUST}Vt0UjHf0m`uY#w4=%9YXMcEW$CB)js>O@o<-HfG6H zG85wj>8Iw=LY%JXk`jxGXEzzMMXOX@M$_qXzMP?{;)06Vk#(TKx&bC7afbE-F9yN( z1R7PgcB!+qxoP%3Ttv$fq9H*d#&VZz=Z&EprioDXP0BmI37(A8d{vKxQDmo^X@Bu!S&Sk$Jzda168ZP6={laQCQsLV0%`F*puorsMfEpw}| z-i@kcVCzAPH&6lEk}Z0Pzq;_viBsBz{z~*o&uq)bL$7D>33%84d>*0Iy!?Fl`O~LQ zN279Q)IT1Lj{D^f{CGMV?QO!pqfvivG~Vl9?!e#jV9$U4HU8wk4oBnfpN>XvNB#ZL z=%Byc8I4}{r=!t)+)qZM#5VuSxZcO!oBofZ(U1M}ozdue2PgM<2j>43%z6CB=hX0E zG~Tm+jr!@%Xq4{27*qKhIym34Z7#U^{%8zcj1eZ^b7Oc=?a-6?&S*4u@OgqS;gh$A zw47^MP9A?OXLH4Jj&V-*-}7DQ_3P*Tx6tF;(daF0(cx%x2yZUNG#PjUzt89=wt;`_ zK)=}Oy*|7H+E=^5R0yI^c4-m_J3R^dsv6S(PoIv)U+*1Ki_4wSIPM<<6eIZO?P!dk z9$)PAUqfdH@Q*Ae!_f{c{9nLC_i=LArvJle^n-tWTa4|WBk1h4{dwd!{9EwTPWicj z^v4_*??~O)#{clI3Z>bS^8h-+ij$Fbna<_^H?u%GY1BEE-h z`4)Nt*wWU~SGXB}#?3ewjnX|ky<(Tfq7L>)qknVQ_Qx@eX5U>}oZ7qCBe?xA8hwQe z`5AvZI;1(^XZYU3kA?ewWM9+9|2$Smg|7hG^p}SMSGo^Gp&#IgK%*2V@)t%STo%p- z;b><=&&Hz>w!YYf4dI9IkHZoDA$I2Wf6rkC&I4cnAbUDvP{0_l9s8@N0srjz=ax}C z8y`AidA-{|9v#pLpc9Gs{6?Y0A9hCH!byKPIG0wh1@#G>yn^wc!b702{J=XhvVr3# zI{@ti^%{G`HufJx$uU5b`+jZvek~`6j+%ux{o_FU8YnRx2fKroZZ38_x+zokCK}`L zbj*TNg~Q`ty#3TcwHx;3{*hk4zQ^)^v}E+aBI1F4hSPBqvzTSE@8{=8Hi<0qq;Y>W z?r7nN@HwcHQDG;m-rx7yji)2wZr~*8G1s8o%mK0g-+fjcHMT8AJPh+C$m2*$&YNL*#70auCrtIrXjOai>C>pw%h#EO{#p3>c7hhZlZVX}^^STSK5V3RTHM`*40 z8vj|j*SmJa?*R6Ev;KK!v?Yzi+`j&qICJ&diXj5b)pI~@zUhC2giM8pfdykA zckLR`0|$ZoUe1VGZozKOxb3pJKkw2f2)gf;bVGFF=ds&C`|!;Fd8F=mCV3;2KA!9- z6bs0=f88tA_O+7=4@TqXcB075;??vY_?KYX{?9w=O#%Zr_5w#i<`|JnNI|yO|N9Po z{_QC-4NP`_WU|8WvO<3^-6DTMfdYqmjy?RK*5(+rA8{ufjD7?dIRRh_S0kdDJ2vh< znBrxnQTIn6nD)<+YWw48{F1((TH^k8H2$!o&hqQg_&diTz|{W}h2^PCaIZp&weRD~ zvG74~RV*1;<5OBS{P{Qf2q7f@V4GiP_Kby^=-_TRv=kCI+p~W&Wzt&e`GF4LM=LFS z%kckshqln-zO+KZj}vmTvqQT*W;>qjQtR6E_kJ0xJ*I_tOov0$zwCJDVDE?~KeYrg zTst23U25P6cCh~kEK<=8!|M@_?^JQP%*Z-M7su3eZJa%)_RQ1~b{yHiM*Yh%2(wmv z{DENRCi8YQ{?mV+V!!sRg!Xzgj@>B@=WF>UW<~5QWY!0YYj*qO&GW>w*(H|nJlM6fffG6h1l#^St%{AKr_XCcB;e%MiG=>X2nCr-yn zXYGIpuJi-cP`cX!TO(ZzzH{ddhSKb{V|D_4?!jpMVcZCqMk&+EYOIa=*Mo_JgwsF- zRQmwg_4~m@#O*6a_Xr4vo0%JHt=X~;hH2Ul%ol$iJ}4rpbK%JEU1yWNxY`YO%t6)P zG5blj7cSqablfNIE`t5tb)Z$6qM)0m$$DG)1gPt{25J1sj5&~b{LL4qfbxDQz}yA? z?br(0(Le6}{)q9`@@K9yM+01ER=(eRcw7qTeA9)c)%^6?Cfv+pRyJE{iJ<77IVlFP z zv%qEtWv%sV2GXHF(QJqI8Yjv_ABqP511^{$?=&p#9<}&C;1QegtMs2H$I63_P?}TSt4`dM()Zlwjt>)qRla9Ayf+u@)nOqTNSz{UhzoTc3Y=T7su#BF%SN&e4g+?f~wilYz4>fcoBQBM*EP zccnX*haapICfrA0nRK^f@GKNDg&rZ690gWpb?Losxb$8(Tzan^uDx!!^y+3%)@pbv zC{JrZMDsOC;SVIB1mE4wdFW#yaLmcP%H|?>q#f@2ju(XI=!9^QjI=J{>1coS@4MNU z?7u9IMFn{{EqCl@o}&)q=WngeV=AT_KnS+#I%M|nE!(wo_)ldI4ix)N+s#bCw;y(8 zwDET|k+-AK<6Rj+bUKO+2^9bPC|G*D-m4J)-rG0i-l6Tcc%B{QcdJ7f;Iq16b<_ES zjTCKgD-LXw0pRTEw21JtHXwfp&vfYqtj3|0=t66C_xO-A6YCoWn2h}Bn^+JpttmZt zVSD|+rc_6GeX}H!DL8-aq@as~L#-Nv zamYe@J!T<7FeDvY7=0M4y`OTwuRRh4VzO(K5+`_U?MKq;$^Ba$^R4>YbG!nh-Ers? z;_>h3!}+ZhLlPjWuR{T(QF?F^hbQya@jIuLxPY+TNHmOURv#TJB%WbBuXk&p{oj{C|Nt^*N!-UckGCHf^%ME-ejKS zoOQ`uE3*nDWxKogoZX6OH&K_YysTW;Z>=PKQFgzG^j0(DG&D1y!*{VWSoJ>*+YNNn z&vj_u>1gN6wN1ezMMj6$On7->T{Hl}e7>W;5cKDr4p8xkhkt)m;i5>*5_>n&nS#xh z4O;?P-}^Ax<0a87X;*21c;D**N6qM#D5DKM6oxQVs(}Q80VC+~utSDmUN}7Lr&_Qh ztP!G`LsVXOP+0A33EYA^`xMslo2f!4`u8X9uWvDS{v9d>2fXjE#2nw>9$;u6!pBaF z#W?9TX!>AW0VIEG=pu=OFNQRS!Z01hpR{qiWvY53^b>(Y_h4&9KD{XNwud{|D=fbK zIZra^D)W881e001cez8IJ$Bn6R(F>DR4UM7$auE~>ljWI+C~(uni-YsC~-Lo%?pDc zFs;samG1V6?RzqsDuO!=X(hhnUcQXF?^lBQ^)fQIG*N#z#4}-5>!H%Lf|uSgp@`k0 z4T|AoPe*R=T7Zy=LN!oa$GPtx5nuz^uBf9MggDwo{{Bj-6#@I3i_NGOiemXht8XL> zAGz*N@zW71-~!>z#ef%&un6@)dMx}*`2)mX{QdUe?XFtPQ}2v>>GwfsY*ROZ1EPW@ zojVC*&@X>N%)_^yB#!;H`Hq;N{$jopKY0qqnRLqacF0kA(YKtaUEjelb%2|4_Z-rJ zIXRs-e*M5k|mLpmL9 z-39QeN<@7-CtV1Az=Fl)9JS1}JmpN_fS?4~J~8 z`vH>-dDO7*Zoy?Zwek}OUh8KNoO6HuZS2IpNqn;Hr^jeQh*JU_ZikaUQDL|w z=E1koLP2tuBpHc?$+d5SZ4;!+K&at2YDUZ{q{IW>*#`l_ndtfP?$%Ot)4tz%&Zd$z zeB?83@aa>pgt^qUV~$|{%Z}?eHCyi}CwAb2$+Z9ar2`>r13psB#Ih21uF|UTX&rx~ z@<^JYf-i+G0`9=zTR}~GgZXNQbWXb9ZEgN-83sLckt|2Ld-1LkXO|NSlmN1)Y;0$+ zy4(u6E#xaLN;Jd0vOZ9r>%cED=Qx;WB_fPZl{?RGeOi0@3tJR@;4u@ee-ph5v6orA z@!r+BJ=xj;*?+@~L8yOQWTN+*Dt2?Ktucq_Zu_2)Q1g2=r#}hCK};*3 z%ongASk2SzAkoG#)2L6c2Woa?Kgc|ZpqMh`LEzSo{>p($yzvV_ao)D&#J*`v&P{tz-kJ*|8?lOA2JIG@+$J3sK3fDDjp#56rbw+px1vYb49fv z{&NY=%1jB0mhx<$vhTwC|G#lYFC$O)(N1t=@Ji;Nl@Wdn|7Jkq^$r?(LPWe9ncXQj z6!E|q0 zB1EBM!!MQp*NIBb#Eh)fm|uT~@yA_d#q)PxItXy;vZ6$2M0-!7lR`s*PF(J)myf}< zDX++G@UOsZJ>F5V(*x{_W3-8Qjnvk1FRn{=$G`_TXA%I;~@-h@% z!A5qW0GH6cC*&+j$G(5Uj74E0x#q;_=R;AAl(Sp%l|_w|?+K?56d&w^D<&Y#I2nuH z@go9cHl?xPX7G0g=Cz9hM%GCVmkXEh&LP~2IACt z>+7c~t{5O?*0q1JFKZ?k+`w!4#UKKccb8W&#IuCbKTn)}^!kfPR@xZ9fhhCFuDgNLAVEfxa zs78e}#V{2tb7YumRORqB8>SK`@;qBRJ_3*VAMUV(ae#m4rR19H>IKgGCaP;}SYq!q zT-0MmwSDQ74CK<~jxvCoW19RGht$2;h3Z662EZ_sM9-E)8XY8As{yB2gkTT;%g(EI zqKuq}0;`CS@&VL>A?+c&Tu;S*I28f|^TNg7C{=NQFb4WiX95nr3peU0Q02x4get^>0L};7v6Ci z7|QHo5l5K@5Q#Y~26mwQKHOFI!p*NZs(YjZC-O>^UhW9@Ek#iteaA|R4;+2tlondx zm+Vsei=PO>{yB3<6io4Aey&-hpyPx)N{y!g-~Vvf3X%JCcYFKO-NJ@=5%xPC$2?n;y1nw2U?`CUR4B5Y z2&*oCFaaeI@rJIww8s%>7ccqPr30$}UM-fY>>-xMuJX=CTBG+F>aDFxRmk?xp7xu& zx{MB#56Af|emN}()AIXZ%jd&s2Q19?Ct_i(D>#56rKQ&bYMAcC?jm44igqHj$)13` z;)~wIRV2d1>N9yle5G-5_-z|RN&VC)cDnYO*ZyLFMzF57MxS};ck}RDWwA*AMcfO2 z!9h~XdP&@tA#jaMAk`DOBYeG$h3Qg>5s-L@#kwL4qOL`TGOt*)%MYyeN6X1jWS$aa^{4q9VDd#HtnneqP4=Q}s7m3SBe^-&7LbN15$4HWVZ zwH089je$rRkhI1?op4ww(SUET8irrAKiH;l<{xV-{8!WXMa7r04Nj%91!2 z+-({D4tjs!78a)%^P{lBZbNy097f7x8^vjzU({Ts_%HRIyTb0ShM~BES&a(Z!mjju z^!Z36HY*BT4qM+7^!k$b=Am41woedgtTis-rN>(dSK14=x{GGGXq&iqLU4x;_@277 zj0;#US6^D7NiRcs7pbI+yT;0Pu*96a3%7T}J@{>8R;y+r+a^>+l$zgvvQ1woR_TM- zTa?cs(-KK4#ke9l@oo^0a~N79@-<174^mVqCmwSrU)<6I|NBMjat!J_AKI^Vkm8YwOGvAPN1a?o+`7E-N7M7a*IlL2pZM}PR3Lc+->4H z8=!5&cg_GxFWnEQ&aqj4PzXriz8es#W9d-@yk*?Hf1%?~G1Z-V--N9BeiSL%*EPiS z^tG+|UbN_95J1DnfU* zy5S>iT1C6V^eCD*)p)m~Ux(SbnQ|hAEG1OD2k+?(wDi*Bk6!wJt@?c+Ig}L;Fw1q8 zcy8sVWH!vgk{;5rXshqJA{@L|A|AFM@vx7g=Y7O->S(_pF&P}f`z;7}mrqqBu*EUg z@J`;s7MojSqcJP)@p^dYjU)eY-B}<04Y$qG4fNY@n=JubA$e3g+xus(Djn2r-eL4A z=FFr>POs$@h)NcJ>=8#P!MwYW%jH19{388XPT(rFGrHYn-t-|)=>W5_5-?sZ5~lzd zF-zNS=&F%7b_lpp71Ptjwq+cW2{9io|CZqQ{de6+-VJfnxRTZQ;+33&#_*vnDz+Vd zz!OJycgosR>QDnEqC(+bucCt~9F0IV>oywsPU|yx)|6;}{e!;-$q00y-P#ICM%uqK z-7NjUa=HoUHGBGT#Iy8+ONd9bnUA5MCQgiCS`xF~8T4lbUGfcV}G6hg?qPJc;eoAAdW zS{iOl&ze}p0|$RwXV_2Z@28OnL8?B531pw^JmfcjSSIn9i@^Bg;Fmt!>0$U=l;sgD zCC{Pc;!A#OK2(o>$C`$#9}+?R7#!p{_QCeRZbdcmopvzaX*gs-!ur^I_y1OqMwp?tPG8|G2y z$+@Z(@=awqYB=cdHBZs8^AfL%pW@q_{&MLn$1D>)iwK;Ve+b=_e+*%fCy|dTbTt1- zx$azyVi6S3X~Pt?6oSmcW4AoAz-4}ch}OD)H@j3D?W-EU;yOgi0M_)W`0O6*P_GZH zX+vN+LLs>LTAN70)V;guj!x8ev9DV^@=bFkZ$t+LzN!gQgp;RKY2#^E(rH;W2{L#d ziOgz0AnWe4r#-NK-GfRJe^jMW_lWBTo>eDy&thlTE8c1AoTuQ;TCV4C;Kh)4L@BX< zAe-K~G2US@=z}vHjttV|s!9(Go&L_;-KTVGQa3_AnYZY5QtD;HVvNKqp-PnZ^zQQ+u8b@w9 z>7WinYxiSn=NU_#8xGqXdtLKjd+?XQ2_T^_d{Mtv%$RHA9d)IT2tdM?TT zZ@%Exq6$Nz=L7+JIZ$(*(jrS&4KW>Pp#MyO{-{3ZgYGiOiBfHUJqo(DS&bwQVId0A z0oDaSsBNLW#tjxXp0<*>PbM}s>yCy)`Jo1hIo!4fIYf_OVcvmXW^62Xex23+y} z8jTp**xBCaquavO{Nt7?*C^!TnXdYeB%_Cphb>oQa-d_-;6ykNeN(}2s48&2B}Gu> z&KrU+h)pm4DXxnw7o^8PF5=742V(`_WVc(U6Ta{K5#Rj7r>*VFF=F$7MutRiR?xG! zTD2@{qK+DtaL6smEw_#Huh5TuJP58q&7zVCxddj;7q@lZBVNJX+O|mg^jB@6@OJr) z7wYD(`3L8IyW*2NmcNf-Q0)rSb455ZF}-e^9!aQr-9A0)`%88!n`^_?_FcKvX?yG1 z+awp7t-Im~$1QD$9WF9|Hwd|}>yjs*$f=Vzur4{(Gt$(fih(UNB1Dhl_QwwlzHJ^A z3HpY}Q@!HhJfKMPuDapj$L!EZ+ujF<(6U`C6sDjHvO#xl;Al$H+(L3U?UrBK*}ohA z`tJI%Lsq*hHm;rGJO(%Is+gzJLM(Yy=6EN zep_QeT~=@FgV8C|3PJq$8~Sn@ zjM{FU5AiSP-4{Id#X#BLng#-p@WWkkYzEvR;G>tfQHtOL%D)!rw@#Rtbb5pVRHohy zf~(Fuf6IWJPq+Plz}w$oI8mSW$y3n0b{kuwQ&WG@8b;oSXkdHc!1ls{?fHQTsBV8- z%4~C3U^gfv5!8*Ks-oKWlX%~c4wSbYU=ajTPFkbJORs4Js5l+vFRLqsJ{ksieO;_F zbZvZbSJy^kr6lSme@P$UG?RvtY~iBGx6f4I9Q@NQAt1GXJ=_e*lm*1}Xz&r4vB8iX znZNRB|9qUiLTDew1O5yLobZ4jxIiKr8Q>)096lp~T~`V9lw|x8YS2n21Fz2}j0M8103kMt!)uRS`V-OaUDca?Tz#Vv713OEd+z$f?82i?h zLY<#iao*N!phvr^W{h_L+%Sq_r9YOlM00XCB+|D@nO!9qM%V4(0$%|)Q_ zHY1FMeLG1~?gkg#K}``E@#``L{uFP$&K^9jw<9jTII06Ek7BPJwhpC=ep>=bfzT*- z*s3{j{UI5T|yBklWZ9M>2kYjPIK}N@wvI4<+-Xd(>ID(7Wj-(`N z#-Jm6YQKcF5F{H<2CtP!{nRUcj^z5T4_AB}Xl^7@=$MVi4Da)?8%O1){l>K5?wZaH|UmpRax!w|G@D=cTt(Bo?bgAdGa zb$G-Q`?}gu?iq)2(f*qKAmdwOBHY9^4yBG5Hx)VM4K9%st_48*rxJP6n$5)gRdj%j`wlyeRpz~OS{fq)Stqes{D=VE*MI9GqXyGk;Umj%-7 z&zEms0UCcbzRV%_T?KxWFo0uvuap=GP_pHalKWfupFrXUaXOB!B2V9UD7NUx%J2Na z5T4bz|IjPn1cf^t+~d<-&t<3*=}}kCa}3i1>0`GB&znQ@ITn9RftzXiG*o&IWh}D!S^@p8&c}ii zmy}ZGl`KJ<5q9T1JjnFeoox270rhs`HEY3W>%#(pu;VKC1mTLxo=N2m34ywgOyeSH zFw93fC*BDXgfserpDKAvXZ}fHX|GH$5G-=~N1Cif^k~Bopwn2Wi z_Hi7X0OIU~D|bkxPAZaR4|;=9rvov+xrpkOb0VC2ju%)|nXLG;SUk#uQl4J+u!x9= z|3%jVC$y@l^)&i$e11dpIc<+g;~H4!4e@^)2NdhqLXvchqnJCVWU{#y@MrjWeAIPxs~a-u&;}CCq5}YxbdyN?ZFCg24F+)9 z0t0v%oYD3->Q~zmM6TSI5?lY6ekp&JFWDaQGdQH-;V@5I%9)B>S2g^_ODqiD#|p#7%H6Gd-Fta5v(>%LAMR~m*t{NICfTqoi}HV7_nw`l z^}QcI{O%wB{tx^&PsVSO3%fo^t9pH&CG~kx&ib#b{w$gQR^Qmz*!b+zPYwM4#>Phc z|BoJTeDc_AJbL`u#>0o7eYWwb*?9Qr<4-;_zuWk&I&jm!MO7!|?>2t*x8lM57y0-8 z-lZ+8w8-ymKI-19ibXjlHqn3YuUF;Rlf-(msHgq9y4P*oN%PuXlu4Zyc?D1W=-U|- zd2RC=np?kr|IfL(Z;owwY0F-fPOKScsm*J1UX-SuT62=*7nwD#-POvB%c81!Ra#qf ze0*Te%5-vJ`_K-yH7N3GF|(yf=JU#QF6vn?0p`{sNz<++VGU>$Tby{D`{G3c>HjT|E^CHcmp}a1Wac$Dd zOp5VhX7k!kOq%06qj_0O7UQ~7D?d+h!OLP%+iG1FrC(R0zBz$u>8V?<~Dg# znP(>_hvq;3?SGlPFvq*!nJb%KOzSTEtji>?QdkT#vGWRhz5aja{Li_4J1@%G09BRe z$=I4h?#T9{o(}Lk2XiDg^Zw7dG3)p5`$x=KQsEMHjqP9b&1hQJ^J;T_JzAPkA3tXB z`O`AnG*u1jA6n;c+tKc6nKfqa$o^%K!srIaM^8;%ys`Nfc5du8*QG7f^VC)* zh4rO-NtOW(yB<|_Qrl>#@PFpMd79=)W=cCrOFOR3>Ct~)-|R1{+MHQa0K zH`TbbHaGUt<|aLdU1}>}lCWozthCAG+9YEjUN;5vvY5^5C!1y!TUhm!S=!!tnq}Cy zoh4~z&QpJzP270&_gq`}nCfHuvO4xKC?@vER*S5rRZZsUclKJ35E#TJ-83c*Xp%n` zUIJAyn4%9j!Ys*sD|HG@22bc-jh0~ za0tYB>;%|Vnwz4W*b@E*)w2fGhWS(|vE6$W=?it?7$!5uRn0NEfsx{J)rfSA(G$=e zzUue;zfb0K{C{NF@YhIl$V3SWp`8}-vF3lsvz22NcPGV511DaX^pq)cX2zp`~_HJQ)d<3zJJ zJ&b)_?V51(GA-`9T9~yZO}R%@Yx=Ebn5TI%}=l!z;6a#tX|snr&xdv-A?>c3lJt$ZEw+IIY8+T?eYb+MU&6gGR%| zRwgNJ(2sm#?Av)-TJzr@n^~HJrfZV(8h3EDu)#K|uJf_6JfIGA-|dIBO>k95}hJDoN2)z^O}Uu2n< zJn&fYMzJDliiN)U%3Y=&n&1E4$y55+S6`Xc;_Nl-NVvCZQ40VL-tu@K@6t;&$W>|H zm?!EXeSOo6yz$~z!ki~*W+$fe&+lObr0INE8#NhfLq~B6P1neqUHo(#<19&S{EEht zROT=#>om#Qh&P7)d>Hsl*6)wj z`jIok`yv0oNhMk?Wb_+VO^;wm=>FrVfDtY~h>(4eW4v&?^1;s<~yHof6y zOVEAi8JKj5IkQP=OVhDwJ+-A~oQ|8Gm&FX5QQjE0N?llsL{yf_mX|45Qea1}fsNzU z(X`0Pw{d989xyOl)uwRf@MW5q5wn*OEID+sC~G^BVGLsr#^xQo6_ImlbO%4fv&Grp z$jz`rlRqvk3aKPRUkQKejc)dfGMrT3BEGuRy}&+&-`0`fhCdKe!>LIlbEUXUC$tqnwEHv4C!7p$L)wYDiDvqI(ztrdLL_yP6&Hs*E$sPYrsnP^7*2^ zX|9*_7&hpusV#qPpuH+H&l`=--XjGRIeg!Hu|RM|yoA~5l0ASHNiY|8BjLb3cr~@; zuwsD$jk1SBnifK) zlG#BrPtMXTt*_N8&XPPmw^beVjrq#nq%oZfR0)Bj0MmcmG^;2ufj!kWnc*K`L2fC& zNNPu2XZzk`b=Q9CNjSrMH}jqRvT>jO|LbL6d}foZo(`sV{3h6OX_3c{s9(a_i#IqW z6A;4_=HsB?I{wbR4|)0+dQ;s@pZLrT{4(^-JzJdpeSDDGJ~Tg@MLx0TX>KQfMVF-; zYv8eL^4ou5erdB}ZofQX)3jQQ$F{0A%~?@oHbE1f`NRnl7c`2pbgV_gmYXguLw~;8 zn*3^aX;TsU4gICDD)>v?rM{iJdrN6tcWl%12Wso?;n2jJtvDEc5CPa;!%A0UVn14 zMVE&e`r6y%G-&^;Id*BxJr?=|p(lOyR5V9NsMVVec-|&y1s3a?NwgJvb`1e_ulB&> z(%FAbn~c&V;U|TkY8$cZFWML1fV8hX=OtM$G7tJo-YsB%#T(BfNwj~sk%Ig*?hc<1 zPlhtDO<#bTvGsp`Z*yR@r$>8(Vm2>wo7cg>*FKE&+^gx`u@F`y-vHQ@)T>J36NRa4 zJ`t6(E>tj=)UQqoD$-`8_^L^c6t$Z-5qW((rH_eEhXfDEmzdT<=50IVLZ2<}wW)S5I-QfT2tFJ^!UvoYCAq;r@ z23p)SJX2b9Z!z;CYTL*vP6}Zsh zM>1QRc~VxuBHD}x@@zp2mK|I)ozR311(etF!*GMB>@rRA394FWHY=`t3yl@#kPrkdI^2QZ|DLR)saIb%l?Wi>gztR$*>U{hsWnlD8XE>Ule^%`V%{*y|VntmK zB39GI881K_tMaSZ3;;Rnv^`cuK|x-dCr^wb3B8W=z>R?>{~Z}^*mL5nc4G1(?*SD$ z1Gq7o+agWz-og&qxAl_f6O4f8l+=un<$TRfcZRulXBlyDT)>wa8+X$$OH%HLa zFvQsa5a3xdp9A9c-sU~X4&P39F>9XE^Ikx%(8VCkpnn9Z!-FB#0@|Tk0Q;`|(zcw+ zy0yLi46`60f$JNp5~34ohrVFdaQpci$%Tf;zoWz1xF>(Xp6tshS;OtYS@3-H*YLH} zJuB$MNSzSw&-I|f5x##$K=*n8K2$3y z2`l&?F3_oSqoJ(QOMfVu)I|s?v($t5nXt~r79EiKQlA)^P+ot!ydu-%j&=Ebs9rhX z#`G()97a@8*-kqT)+3av$DQ(oawBV4;>@Z8lc)zv@-pAXbNkXjhW zst!PK5ZmRh+7_<1&$;Tl6bF%?!9Z*EMW0u9pzKC{cYfX-ZhwPwzQDCDpQ=-lg|ew4m~VhffT;$s`#{9$?v^@X zJUq)w7=!Hi?Kav93oB0Ik8u@&@%G+?c6{{CK9x@64|FdEs6(hCuF*LS;VA*}#9+~e z)Sbo}%?^M5=fMsVdeCWZG@c^p1aaW?!C!NVke}>wjkUHu|78GY@U|SHU8;^R7b$W! z)&u%VgKqFYv;o4tKJwETiVD1$$Ryx@DPGjqy$w{N60L(LT&!kSn9_IwbQqWr^_ zJGqqw>jBrOc^RD4I?Q(OEadQ~78CU4cf0X|?T3H0-SEuKu(DRj;OBP{VpI{!4~UL$ zd&k_!i55JC6bbz7Ao{*P4f|AYRy z{O_aT_U`_$Kb!m&zHxK@_a~1Zej4Y0fAVnSANk*ZlYjqY{*@4`z1-GUMfqkOQ=@wf z7F}66B!n%*-S`?HpsKor@Bv_-C&esDbAx}cRef`C#bL%esot0@J-6fQab{OK5C_Nz zh-#Bbn%fG!eR*9LnaPS`-t}3Q0Lz})F2r%tI(-K@AvUu!TbI|}m2r|6c{)xqGdiLU z9WC;s;%a07eg^4K`Pd?MpsEdiuw3g>IDVScJ@8ts*!&{Rt?9hjMYo@KnmsTKC^mo7 zqIgs3_h!Y!W*(*RU?rKLhpouFm^}i|P3$;JN=z^}BXXxOm|-loDtOLTN;@wq zQ`N;B!c>4xrH~UryZh#Okzc?{r%sA#2AS`LhY7Zm`JT{E$h#eB^` zIt^eB%qU)A4v&rcD=VYX=q#zGEAxMRJ~PQ=ax*n7xoa%SC zTd%DAY=$}b{Rz1JGvFU{@YDY64R$b1>XSv8Lt{_NWM*MDdbwTHWs(V1_t0|KfxCU zexuDj4**Iq;FJ#bm+PjY_@95xuG$TATlOw(Sz~KRG|`q=ATq7$8qX!Vrd*tr=@|Y$ zH|K6WZB=BKIRAi7&PHb4j4qQb1?aTD3Ny5BMi-WTR?wXz$OkC>1}1>~15CB)jk5xx z4Sq}GI@mFw^2eBo0^j6O5_bEfOvZKx>rp`C`^nsMBO z@?D;`aXPWzxp5yk<_PVMFx4CfbCwl=cErEO$)!#Hzt+#nBWPr#bRRCA=#xz^*7dZ`^Use4l%P(HO|+~ zx`9-gaZ&<>LF@G7h?9S3D&1Cd{su*sA^eNz={#9vb+5FO1e0l7qyr|D0002npvIdS zbtBk<4H(@BQga+Cd$a}{AS^%fppnpHeqLD_URvO9gyMAA+R7w(UH}?pVn+X3;Z_|% zF30F!?c2F6Qz*KTWTU>^U7>fOCWpz3+He|FHBIJ#Ucii_1X6z!i*hS&#<+K9$@L1q z7`}osiot;=)%#@Qlh1k-X+ji3jc~`+BfN_;*WVAdGhKBxwM zSig?~h{sr&`|Cf3uhS-Pec8jvK@V8%B5r5X)a3#cpm9;!P4f_hdr|@1VRcgEb~Wx@ zGOqQ21Py>sedw-ySi@B)GTR66BI#Ez}3G=$$(nam*% zzzHo*$FiNZ5ehJpsIW|t#9Q%tp_hGKGbxjlbw_TebQ{^n`={2h6wT7yuFT7#F2+Sh zIvBt$`F+v^&AOQvRqbUS3RTXkz_0aJ8q#~y)X5v`%Y5{h`%i!Z79gQ=OFU+DM5r0q z*DE6|b@YF>W}QlJnQ+p6>81%=!42c`nJ^Q<=9sLoX`r!co{a6@#4FTry`I8&;Nk+) zFuJ&^d-=q^B`U$maeIXqVAD`yZHKNJUJn4CyzOg--l5#`u7h4VymXh}JBKnbC-!aa zT1=|S&dxGBp(j4@le}=-75q2J>wSmACpzy)7FT~)+Jx=&)z@Zn)=%@wmbC*GRBL@& zv`)X@=MNM)%pVBE%+_~n*S^JU!^5(e7gdr~9sdBg3AgRjY*9_!=plnH#6|2vouy<>v@H=zufRxt;{H9c?8Qs};_vJ-Y^$?$Cr#1`B_f6(H*18YO>a*IcV3QrcHKm9=K%>zS?d zj>cTpHOjs{U(}0|8ZK>SlZq>bIi(styW)!ujcB@ggCf6bu`klf)nGug6hXJy+?GDu znY0!tk_D|h0l?cAxMz`HpaPQCmDp6p)n#v3bNc46 z$kOq3@4U1Y30h^2!z5uTunLpU<@J9RDry7$M!;w9q9`UN$+F@K5ZDc|xMECf%XQrq zGSzkn8`(82=3Uo#+pfFvkrGumZoX>$CIF7t`1$u{MxCRYhUSC3<2%$@G3Eyp1@HMW zRAEs)SnUFs6Q0_1INKel1VN<1TAX^#%kR~$d(B_!&(l1&ll>*H+WZo~X5@d1Hoxqj z44xmH?*24*wte!`{=x3>IlcNL{71b#A^S=`yE@L2#l-e1V2gF{)81#OSG$SfWr2O| zbo!Q+>hSF0f(Ppg3c4eYP4h#`*M3Byh;Tpo7@;)n#0I6L21S0JUJx`+J&V{xmVi$I zq6&fz7}uuxVPxMXpaoRx*u{UI?9%nokDdPd^4n`j6`x3!Tx0cx1c0__2}-pSTx`Xld|(O7e0^QKC1#!7C1%|ei+WtltQ30G4v1CY zElGA=)hp=pLPuH!xw~a?X~CW}$pz@)%`I!xDDK9i>DTJYip%~ZbnAaLHSv{hkTqJU zpC<)7SAj^}a5u?LbBZPwSm>JSO$tiCV^bM$)C$*xMu|B9c9Pa;(?NTF&bUDq*E4iS zJP?S#d!FW%AD)28jRM#_;A)nYdNTJ4<+y%}njk68)Nc@xy=rd9;G{&i7Iyo34kQ2? z0Gd;gy#T(Qh7;4=U-y4@ofkOE+;!rEUunAeN;3G&720`I%d|X6$%&Yl@uI4WnJv5Y zNLsIekSqTbobpq;HNWnGjMbsyXCb5ZxUu@@mBDLuC^5LaunP1G>PQq!#tF z+cpOu$@dq)r=oU|>XgL<{I}{oTwP*0Vat0$r0D2%AL_CX<9L5=$6&eJJSo!xo*v25 zn*jW$=01!rU?-lD;0hyZY%kpFgABf7*|tmiG0)_Ko<7U?Pb z`Z!6%d;rip5p&=9O0dDT6#_2fk3QHO1QSPVXmyP#9bOx=b;a%u$dIO&=oh`_9-08a zT>sjn^l&>_b&h|DUxH9sa7>5UmC( z;PV7k{9i=2ONo_pSa90Ea&@BXLgpqUVSI4SrRMl~hXQ0apU?~R%*qEbpE1v&PTm%I zF}udl7GB|?RushggI7ZEDT=3v3JukSQBN^Rd9j#@D_kiu7`1TqHy{OW9maW{UU+Oj zzVV`{pB8_MeB$5P#saPr)KXDpofNi0pR#=m1c-03ul@w8FvIfuxA_K@ZeBNghXd68 z!(<7UCxJ~J6RX@$s~P0$sJ>$a%@-$eiUs(e)AMs%!g9Mv1GXfL`54@!z9n_z8A`{I z2sKWWs>ni_fc674MCQoPD=XVs#w8!jC_MULWIBJP?U8B%B_@H=8!ii_I<2jcld&wX zF~ood@Cfo%Xy=hPf$$X0oU0%;uC`^~!?{^;>4miCoF&N{iTKEIGpw<)G6M4NlLLHY z&a>jG@=np#I7rU6C9zC%|Naa3y;4~a;aoPkq+N9XzAHNg&V=+WgjT4E1J%+w^9JwS zQt*Fcl!VbZw=RnE}NT7hr(F4GF5PVBGp z!*N5MFX{@Hh{HW+*M?1$wPmd9h6vJcph3v&es%lzI=_y=A!iebRpbE|)2{qjtaa6E z6$#GHnrMlKOrK}KGLka26>u_l^=E=_M7)12m=4&FCjqph>m*9$yAQ|?9|R|`MOPd( zJ=z@JKw*nh`ANEg2z z5yDPeuyL&|aM)>yKd!ZXEpg%W(1v`Vtw3k+xn*c=hs>cK7=093UgXj-MLb4uj)d) z95eOU&V$KYIkwjPFq#x&tj7TP!&QHOHbD(AzPRQ+q7;iIQtq?EowUW3l_S9_~HCi=R%u|}AT z83@#>jsQ8>3WzaTsh(f`sLiF1t+Aur5zPhG48x|qH0yaj7PZ>;1Z=V&Mm>LzV7UDi z^{nd+2pW)X*p15a6LC{=gWfwn9(wB*XyL3Zu0Xaj!odv+%N)>)J?4_lPPRQnU%3fy zfuAVmHc!%TB*?cy#&OAAD~T=<``K5(10zY zZ?rwZBS`0TMuk%ZDrbJd)8K!h?0#!sKtRPet<-e}y7E6B6 z&n(UP#u-$^N5$js4RZa*zoq|z^S_Xe`;&fM{iffz5&!*gW8>4upGAN8??<10_V6G0 z@4v-AwxI4qzd!C`Gnk# ze1RY4r_c@4HAf4N(0Eo9Z@T8GwG$9m&yzC~}XXArE?fAKppIu)&5aSxM}WAQ<^QvIdPt5u(L~=BdpR>tx)xFQ`zjir^7xN z4)W0wD8NP)k&1I4;_9y$?8=G8d4P6VQL%F90)o3SJjxyjF6DnQ3dv#w?R0S5vN~&q zJ4_fZ(bB(yzC-d*ez%xFU)xYiaTO0afDlNu}U)-)CgT()$w&cMi9^l z0!H(S?7?X|vCzgWt;n&q;NpB9SwbF^WPx+33Go;lU1CbLo}uAn{BVhLtj_PftO?86Q>THFFy8&jpc-biPGEqX0L<@L>f2>UnWg6adVlB^*`PRc#&UsB1=4sl97PT)ns&VY|C~N5jF% z!O`CESf2!G$vtf1!AcxEW!F$gI{+Fy#`5Xl$5=p3 zPTYTx;;2T9feU;;Q1Nj89TzW;Kjew2ho>h1C2pCUDABj5BKCKln(w#wpRXfO*e?NZ zMc=I3@6A>n`EHs?Tl!%9m$ zv;!l0Zr085%je*9TsONr(k?xJW0S4pQ9^$MNC^I1R9~-uIVr}-w*oRMlbbtTDS8TM zhxRvCo17)4^QQvRN^pf}&&ww?Cs5z|pmyXDqq3dUkv+N15(Rre`-koV6Jm1`=}?t4lyM#f5)9h~pU>fuK6jw%Vt@;PqYKcbvf@H*4-iP)K>IgdO|5@F ziv_Pf}pA* zxBe48_X1w+x%aRRB)!wi?bRwQ66*uJLda2D6+m~+7wu2CLcX`qMl~%K*@SxA@x5E~ z<7a7e#Kmn#P=0WPK+Vk+JVm$X+C+ajLE-@qF7T?A0=z(^oU|9a!YB7~h^tI4Ht$oz zX5>U#ALJIuS*hICH3vs+AI8HHM&oP&XZ4Lt2lT*2X%QZ3;P`bE))0TIyidIA`T%+# zIVMr32i>0H2rLh;ImpjCT!BHn%&x_=gBc6Ku5srB`n6RnIr1ky;6m^~uta|V3rW(U zA+`)(zqT&C%$g}ukoVSOZ;%`rNIISt1t_0aj@Sz`E%IVPWY1&>BSiN)h@0%XH%s#5 zg7janSY#<6jRK|4q@emOss##`M1XLYX^~L?6EtXCe2#cf#5{3=IUE~~Yi*0NDn&j) zBlsm~=xSO383V$cv7(RF!`ep%1QlO%*tv5T7v{Kk(6O$Rqptts(0bjm&DP$yxp9;X z+~XXuT44fl-wYv_oGyP>Q?Tw2UJTh4QhDR(8=G5*@3CYUog9+-H}ZCcvl&@0j4rU#Pe3knt@yq< ztq67@n!o!Z<|UStreN7T!=MNjekj={ZJ1E|ykuXx$v0p50?|ggCszeOd!G`??z_Bq zO4mA1ZRx-(cwGC)=X#9rympR3Cu|Kf@|l~04Uay81?IfWOM9NaC1fkVfbtHYq0ADm zrYYoGv%TrsfxUm=YEMl1X8-i~#30-!ASN8mQEn3GK)P9SOQ&W0af3KRRfWt?F`2ha~7=)-~Uz4*hycf(_4nt0fxUlHfXfI*Is zXM0i1$;)22JYfA7Fw-5ke6As6k7d*W!lw(o5VU_@KsA=UF$@9vZss^Bj+J^)Vgv2lswW(ut`H*Wob@j~8 z!Zd#=C!hjM8dQ(@@EFV)lMBp4d4>q%f(N!Dsv_hptVCLYNW=_)i1NjAM(NJ(REJR4 za})Y?a~3%UW&lT8#iDAFd%;8-pFaoUgH;P^B~Z9_hflXppP&46a$Q-vkY)W<_;S%>1az*8kEc3Bd68BH__V}8E-gl9 zTub6?2R|p~=raDeL%B4r)C3NGt--a?WFqs54@RoQJYikue36kgia-k_qFygAdaU4# z!sSXN_S#U$+k&&p*X#N7eG`PrVv^%nAowy6{+dBQ7p0#YT%MrP&3cgVMp2av^gcW<4i?snSc_`d1d~>v zD96Zbzt|;`C3HP&gR2$I3;`Nv){;}lv)UbCumTVJ8(=y~0=@U7O2{PMi_{VSMct$3mh>Su z(s(F?)MAQ1)RQaD&BWHWoTWJk)^QN20fX7KgRgrrp0L6-$Jz({##Y3klS^A-@Q*(2 zRrVPiho)fq9vaJRqb}1k4C02XMXfx4iD%(LE~65>2CQiRogNQ;eW4S{Js^j@(eRIfE zHn5k%k1`YErL060b8hZ2&)T~UO*aue%{bca3Bp{ zCbZ7PuP6E@LN^%Qn_I|$Y8;M6^v~1#;pc7tux^sVjDS$R#<6C&!~>U2?nor9BS zo97&#W;(hGMBbIOX_rH zI)J!eH?Qp3F_c7vDqm@T4&}hu5AD~4{(E1leIi6H$L&CN8`bg}7g?6fv20Gt>Jvw? z*|6!$6v4k%yA@0;1>^>;|1XR5GC>#`6$Q8e7c_i)Ef{FFxbWt!uOrhBCYHzpCYI{= z;1PPVk|X1*lx;sf86M%pQnnc(*7f(O%o>RXGCxJIL$(@kb!3-+v^_bcXA9S=WsM}( zmR{r_QXpK5kx4G1=A$+y&Bs(~0UmGIK5#Z3-SuUvAa19K>|viB9_VD{dRgggW+!P< z+w9tOV5X-fgc2(`+ZkG;fPn74Cs>o#j0<=`)#`Y@k+r0i)fDwf-yl)bqPqsTj7D@^ zj=ytOPoD_1wj{@;ZUrP%T%wv`=~G7^4?rDSE!yH#>m_-uA$l!mFXebon*mR zi8eG23=)5Sf<%wCErTVe#+1qE*odp(5HHyXs+ds-p!BFSPfYqn%D$?jsvv&753>W$uK`;{AMet z3C3UKz%#Ua0lhq5_}LmK`PgQCYPCu)a&VitQP#jqN^pn+BAD(*@&JOn9k1UA8y?qh z#r?#!15u}O*MJpMA>+`e*v8`9pH+&=b5O`QcLlb=^&HZ}U179KD%?FFl0(sl$-Jk3 z6mH~^CR|K^;a27@I>gfyfq=LV(P(SB2%SQ4ZE|3I12x=H8|Lz zi1`y0Grn#6Fm3DtL6IbZ0H7BGnAhdN4dCaY{B7W)Zp|>@#r`)>8$JiO$F2#qhA=Mc z!Y7D-Cjm6c81?Tnr~;6o7J8q>W13$$sf+|mz0h9RMlhLAIEI=+7!6j!W?iLMt{W`D zfO^n?4U3dXsrG^H<#Z*h^2wJFy|jQdW2|{ocGGZ>O)NuEP9MAQZd^T;~Z_)%h3x zaL(ly4o0^M4sj66s}RXtCI4yf0)sSv zcCxCxEd2SsNek5Ul^5p5iFwnp^I|+TGXUA1+)2QD$?l9;uQ5Dn_-Djk4J5tyYv50C zHNxFv{1pjm?WtBPP1e4~p^<}pQQ5vZxZO$qL;7PMQfjG~U^TkUocH6X3)q=wU|84A z0R)?yJ`eN6d9Qt;Mq~5yqJ+3P_G1TsU;YNFOv-nu#nWoiGKsFR%oK{SI^WXv+wHw$ zTVAH%NKeMzFr!3v1g8*W4JTlsT^5|a{6u?_0ABHA)2vdWGs2}PS$I{<>13XMXJeE! zHHd`?qdOm>r3~H4{B7?)4|?N0+5s-t&@s>KRX=fYfoR6uD#B>U*A9bmzJA|-HTXZ} zRsUOmUI1w049celC?6`1M6s`mizRW9FlM4sP%Xm2(cN|knUntXus8AKJZXOrecXJP zTWq*zT$5``eH^%kO`;(a5%lbYP>Q4GzzH4-xpNAG;m|kWh~Uhy2+QnYYE+N*8u*su z%j;fshBQHF9O{Z)0q-T~7a=QuLlqaf6DZyu`KBNrU!Ejy#Wj??RkHHE>G%8Q1Il4| zalgP~iWwF)B25OubGVByxYI4Ma6;UpsfvB`xVU+8O>uJETNJc+2kE%^1>Fp7gOOQp z8JQ(l6`3>0EQmL$ES5CKI#XlI`DISKHt3gc*C2xVxhqhuASE7T zLi4lCprhkmh*h6>_&AdT<^og{)|bHPsB$?0mnm9GBYZAS80e@>(XKc_sGb$4jrwc? zXvWn5_t}6Uf{V)5u*pS#ITo%Ol|uo(LHjSsF@#kY1}D-aV5OX=cG81951LXHr*Xlg z2d-62LE|!44@90rq%_ShAkTH2&dGxb=|pbsRS5fn_mB!a_gVwbyoFW>(m#W1pKmwz&z`fxKz{tL(*!6$n24TPBCLJbsD~7D+XAs8R2|R} z5(`^EEaR4Sfj&)t26&LZ;86?I7~V)cfY|4Kc*Van%I#GT>`Bnoy?)b&5dUcH&V%rN zX+j^ij{)`;ZY;Kw0lJTwVLeV3Q>CWw`eA+(1-_$&7d|ZQ=uEl0g77=A&KdIY=Wf|u2?^Rk)-nwR{}PFX(4fxPM<(z zMLMywc~Mhozva=y6UB_EwlrloOvv8DyegMtgFp>nPn5G(mr1Hj9PAR)TAK}cNI71- z1Gc~ckS!+Su^@g?)pqX55(Wit$w%6=VRZ5x6O*eAzYyl znFPo4QD#Vg8A8YPp#DA!P7jn8rBr$7uIcrp%+=5Qu7r2Ofu_jw!r;!ll zFm*YI=>r@lZ5i}u`K(cHrTMKvQkK`f^P;>0U%KCa1&aKIfG(WR3sY=K`x9Ule@moc z(+CYeRsqe!5hpOTd6^a^R0&JM)NFocGpbnf!>YTp`eSG!4yaOBg9EU>bTF^0FiWT% zA$=>X-79ve(SkYLH{YNVk~CG2kjNkCIF#qnM28Ad(x&D-@C5fjRyB?a`LeZ5gTQTn z$kZ`^iGWu*jK3ucn`?}LUvoH=jN}KpO*+q#^v!~zK~tN|F#!@NmPAR{93w7*{Io{! z!bwiKyM!7jEurSRSeIcgsS$kXO2V8;A54P5l+M)2y+1Yw&&*zGJ+B{mnqoDkYbxv1#*aq zQ zWYWbH65*R)byNGwEn>u5LY=fr2Y|%+2Odd1I2vxB3?)gt6DHu2F4{Gsse1UHWaIh8 zE*DOMfu4+x&MrYsX@-IV9at8C^LEUCI+tvrhB}lg%!$H5WQP=O4DYn$9Cs;`+w&q- zM536(mRaO!eH{aeV1h`XJS|Fe`Zl~*p}O?LDvWLQ$0$}DF)0>6yg6dowspHccB`&6 zXp=h}9Od%Tluu828+<5$Nl-Y6Vxgi`wYN;=PtTx zQ2>acS@>wZ%<)BEOX@e#zHZX2TGE2YH|r#wVh+KrJ7_Lzn=LM&FWb$Xn;lqjg*4|8 zz#}EYQZN*SACauFPs?KF%w9ao^`=@yc%-wgDKT+LBOUa6LAgqX8(=g3oTn_!u_QLg z1}8((iI2aN=}5jVI&BRzkCSG99h>!$so^9oDU-$tV9?AWpA{1%PnDCxXRfd&$~gg* z%U$=ObU5n<%ZYVFQ1yj7wmA7mtmP0ZI=e>y3zj{?Qfl@}&W4~-K2NbUhI3DC@=T%o zjx_Qs3L~~J7)-8T7d&fiL@-TIB+ts?jm>eMAwoS)%kd&hO0M)w4&TXtVxD1XH?B67 zLdpbLunf?|Dif%|^~Dz=UIez<@f1+%FfDj##lQ#8&Q((8NKaeJ+DL224rP!C#_0<@ zVgS@a)4_(gk_TDkRdG)CG~$mdR?R3|05T)69Qj(qR}k#FcvmQE0a!yDbx}>PF!44N zshk*sM@akJD&eobvC!Oq@S%_$DCA1>!sKCIS(lgIR%Ohw9R%#|?s2Jxuuqi#F|p%f zG0&X)BQgGXq^M1F=Rzh4AF|lU3vtD=xi}j9e_G0Bp>kH_%$?xr4E(68LPNnvUrMco z*K}Oo_nOa%b~^os??n z0jzNisYA4U@KHz9a!r4#xj8#2{C5q|s~j6x^R}R{Yv2ja2L5pr=lRF?@Hal#?NaaIW?Y^Jr6 z4X&9pWc0;3GoUSDmzZo9FkY_5LyVU>F14b$E*&?UI}8Rm=+g6=ijD%aWyuE97-`b* z);9FeC58oad_x*5Coz0|yIvL3zM}g!1*?tK!?C(^%|zONx6xYj33XT1j6EP&+PJ4}?v3bB^q27!LbSok1i z99r_kk0DZD1;SOb*n{L<+>kv45CO9Qt$uDxj=nvAgj{#TZ&%*;f!2-`Wbkg_*NY0u z9pz~?CCoBMjXvp{Z2}3$2br+M0yk!llBcRT?_I4Y{)-pYR9QN9@u^1ujaKC&Xqj;A zR16k<_(0P=TJZkss>ofC@YBB8J2xX(Bv_+^{K#IEbYRv98=>aDG#gzG!BinJH`K-E zmR9Gjt7X!s6c;Noix7%hL|tT1dot& z6Hp;#?*gF17#76crAi_kI+*{m+%LaDTCit-xJXSM1xMYy+|qF+#l`)mxSA58jua*f zOB>ZuWYDCGdCL){m7BWCHNLREt{f6=Uu z+v%-EAv!{O_CbM`xQ1gjWZ`>Z$xO3W5b;3=jW?v$*bm zkxU6tyvV~$UR?F>wfN!vNPO1KqO!sWdBZ+%Ild1(Fd*^yIsxl07x@;YBWb`*qvX;w zxkMCqei7FcDJ_Db;j@x5v)3$G!E|Kt0X=t3%2QJxBIZ&x4@T6$;8f>N-qkuOqW2Q&&`BvQL7QO~elSM|}iPNnhrk-T7| z=IA+Mc5;e!Vli*TNZO>tIxw&@SPD@9g6*~Muuy9PGhnLgyq;R9`rxd8ojw6VgL$1{ z$uY~-=8}w#7RAaZWkoLR#L#h8P?ZEum%^zmu5y>1d>>^(ZnMgC4ok?~*vlau4vXP_ zGd$dup>%;a;K7w$pEUvf_9q5N>a4bVK%&r+cg=r)xB>0}LCC}_$JDU4$!!=z$Bp8j zl(qzCY58+NTWAwb7WYYi(o~Uh)2#Yf>Z&jAT-4bdPXD&~1gi&c!co8d=i4v7d3t(0 z{ORCi`245gi`~P6y%#54R2-f`Ms3;l-1h0oGdzl(ftapt9iEP}3BHnd?=pK7T<%QH z*8Cgie6UXV!c%=!JrAdE-`A7E8A;|Vbj$19qYlhhzgB1b`1vV+a&_F#ruhM%3@-kt zzXwV&=8w5_D0ig0%(y;#-Jj#$z2kad9i2_Hn#||=Xm|bn^>uXH`qY1<890--OHv_9 zU3LXh`~7}f|7||O-lGNHW$%FcRC7L{d@+O^7IiU8YSIBV{qtyFlX_z-sa@E0B}(bQ zmw*P;yPv?g$Zdgtah=K>oIF45n!|(R6MXY|l3&2NG3uR1eJpV=-n|ZbxjP1eGvJ*QijURCV|Xb?7b2D6k@9=_ zKEA7tmZw7Oqkx_;4;qOQpoyYZra1~z%zcNury%i0Yu*~UZ?1S4RLcv}S@9@dV{=T_ zhn(eXQB6sI=L14QIx-kdFKrKwrC*^+LjVpzK%ow(WyJ-gKb|dWCLdq7zYq$7b!|9F zS~zlOc{(G8akylbEidJReIHensn!Pw$cV+hw-DWqf}<{-O3*?%WpNdk2o7w+wo1Vt zQzBTgwXrkZCKYSxt(u1Yo$2NqSu z5Q#T`)W}$)m$&`9Pa2hf7@hJn+pgwkXF-M$CS6->fu$Nu>*Dmoaspg1!RO(lb>-K}CctSl4*_OeR5jSPYf{mepdDaN^I=Zi#FimP=9pOH!E-Yx{4}9| zM&h2;2!{kq8qW5DpEqd`!Y;cxG4HDQpJUTNt10r0+ww+X1df)he)q=yDBF`?3UV3^K_PE z%3oAEKq+?Bsli1q-BUoT-5rb!QUtYfb$VSA5wkd2f-Py4ip0`ZyqZd4sjE8RJCj{I zL%q7gq`2hhW|f5{GCi#RoqDh_f=C|h=;h6D!d!o0$~9SQoAWlmP0dGO{h?% z>R{4zOaS+YdIfS47NrXX(&r%)EM$J-8`xA+(_r0j9uo}!)Y8C>AigToV;qwu*21IKKX@NO}`$pyy;)to#(>{Dq3ZSiz}dEQZ!MAdSm zF+@itx22b$3!KbfuXa#L)J~|O0KBX$3E1BCiBG;*AsbySHVqc_F`iRU@R+bJ6`s(9 zOxm|2FH*fssk?<0Y0fdxJjfK-l32UUEsY^PM*=G#&h`Q{6^4}uDoS`xTQgfpLIA3B z>BMxrj|T)lplE@m<~fFg`@-!&NH?IP>L7B2eO`BsASf-4 zjA^CJWgQ&_S1I#I-6EtOch>N%>K9bR#jsG_S< zA1k^+2QkYfIa!RZ>6%CGT39@Xuihc+pzXVqv?+}{cEb==jkiaK9y&*}(oMkl#-_syq`9n5(yCtPcv=tQ zY5hsRu6~6Y2!by@w_ zC>vJ=coak;2IjcH6e!Z@RAkuMe2yhv$8@R}c(<~GU|$5#886Bzy|lenWI*3Xdmr}A z^CFoD`pd!cOeagK8uT&La#8C=7@DeT%NahlTJuPM-lIjXG3S6t#6>4h3>Ieaxn1N6 zBsc|z9uLW`^4Vah3`&n!`No+qJtlV-azWJ_L0+gE7CrsAZ=SirWO1wN+#GkP+yD5V zDkr{!C#5mU*nxeZ(3vZ+G^0{Fa&Qhk{n-uQ)XNfm6$%=xQxVv8|5=@AOyIQ+VOM+T z$5Q8iwa_b{y9)nO2A@;%H@WEhbX4hx|> zVRxfA6OAG2OZaknFhw0QgZdQwaR@tZ` zWlKFhC0iWo;5NsSr4^~}j&L{xQ~^bQqKN{kktiO<*2doM{l3EP{l3CJ$~?)&f65aQ z#iAtJGaIc7TS5VO&OiVB58vl}W9$c_N(Ru_q3&cx$WF*YbWJGsG}9Mktl68-7?-%`F`U>BF>ijSH z(fKA&mn&11Q7%{Jk*i@s>Oh;QI@)BqU^iZ~1!{bILwt$ws%-!sgJ_Kt@7Ze(RM3eS zfaaO=v;T6UPO>_mH`m8CKrk(e!4<0MJx9I$7mYzWtW*e&G-Yf<9t+~Pw7n&Ot_(n4 zU}#uGoGKwev@J|z=S1R*h0KC~i??VLh~Uf2oy6l)@~h%?XC!LBJnk+HoL4A!P(f&4 zUMw;IpTPz*ceGkmC3s=|@?Nfs`snzH2`2Z7q#q}`yQ$dS!^U__!Z$Gnp<0F!7Z5)@ z6HZc4yvXGX(Jl!?ZRw&CuYhQ!iv}S6DWQ>K9T}CMqU+7vP<)^9nqXgl$h|<+7Q4`A+*BELhc|Fopm7~Jh1`9GNZJraDiU}a*&N!e)+PH@YK=#K4FNHz5s_!Zbx$X1 zv$n|r(7|To!g}Ft_yNS%0iu*9LjfG2HAHY`ln~u4m$Xq4;s(Ps?&(DR-~aQ)ga4xb ziq0&^CP47ltW#ee=&3p>I)GWbKH`#R>K5XKaI+H^nab0>d3=z^=vYg4!x@f@V zO-0xW?j0N)Sh!p&0$Z zd;$OY68>|4-$@umlPeS*p-b?1C*80*h^wMecPQ5^OA8O&@zPqBC=5_O0x!N41~?wf z??+LmY1Hg2o^`DJP)_ty}jVOjc!oz3EJ>KuR5_aH^ChZoX-2Y`X#( z)(V%F&~Acc>Kb+6NTV=L03`-X8gKaxdZP!l%3D>WosMf_TJg_oCRypmdWs*5qZCosoSUp-C}z+-(tX7jQ<7gvzgNTb)=$24dUif710| z!$0?Fg{S6rK)O9MN2YzXBE_OiTnLb`wuN3dwHBq{Z3acx{+3b#kAQOREe zDdPtY@UvP{(mIn>5+uNu)oS zAjc(CG~!CEJ3r2&8t5&Z1AAjJ&_2s^>DuVgvOB=NGG8bpAi#WAB_IT0g& z=V)RqYw@agi9n~7v+M;Ge1wMnb;cx5V9vRzvsJm6Q26a-sR6;wPA{0b zkN&96%TAB^RC9t7^?9?Ya^!!RZwP)7lfyY;Y+hgvlB@<38O;4M+cY2CT%n#L>*6K% z*eW@HGJPiROP1#)%RuR(bZ;RykFxH6vWtSS%!R}qdb=oz_tu7*QlVU{+~+)tj1O8u zUK?(LkfHF#8eQV)mp2OxgFvL0%^E3qT%Z6p>BQ?a79VF}3riV<0B+7!0--N^A1*hR zVGbKOYO}U*qP0Xy*gAuy9J4OD1f!5uv25CvySaELNiu~Ko9z(V-D&rn1oGd1ApE*m zAO%4o1-7o#XNSf`Zy>4&Hw5()1eVE=IK(bKZP%I-Sv zy0xC|ixqH6#=$9=nDqYTJ$YV#=>6YS-NQ2f-C6fA?YDqS?P(gGES?uxTeKFq+*u-O zB(^!QgN^$Yc*$Ts0bM^i4D;8v(#pNI%Y!Y~E=Z0MVI1`AoUt(p?aB{$(ci=mBf=|` z9S+y7*_440>?1w9x{b5NB4CIpujFJlC;?8h%ckWsSkLU32gkGE7rLu|6+?KTCwxX= zKryDVT6sgS&cd}Hk!fW845Sy{m#=`eR}K_>Z7RUVCGt1s&KV6qOPP^~REIW_xvhbj zl0d^r1pwPe!e(KF;{|2|fJX3ifjAFkuTymMKrD5X8n%FV?v%rbhsUO`*~(fRI7&xr zG5_;I8WW@+rU7F#`J3l|&m~PK50zMP1!#Np8){;swu;RdHCG~T%H#U0pW0yf!|0U7 zat1V3EZ14zknKlUV{7g3>-d{^)3!V zTX+q?rNK=SDSeSDp#FP)|<9}c2YnIM!)P=&Ds9ii5)VFkZ4Ec zEL{?QNakx0L)^ny1ay#_G=lr?EbxT!to20dI26=Axu(3m1Ft5TBaH!|ZxP-~o4Mud zfFbj;s7oT+nIlGjbf(fks-^<$jJLy{Vl+iwNrG;n-IG<&iyJ};gZnk<3z{(@Q<8#H zGGC0wLB7(P*UvF;QTJ)JO0IKHJEms@Nk7{Vi2lH=@*X)@jv(U9kkV!@_ z_!z3fIC5N>bWiLlSSZN)UWk$7IQQZ7NR{jOz6UsOXdD=Sne@WMflI;D?wA3gJHY4` z+kgRyD~aDnH>8X5`GkfTtL3fuKqk;GVK78`U&|~kEf!f$i6>wL=#VpEPctkAgC}X6 z%_7U89&6%f(=Y9E5bA~n5WI|dG{fS8Q6iw=b3h}>XNH?WtFyLPuPNcjR*!kR3{;`g zvu;J{qcggHW=yjAV)kWmkR}lC;@7^DLV9?2&@Y*>e&|Y z9y@vZ9dU(1LJ}$+P+gP5892W&7`E8dI%hS*z?Gmv>o2P`W)d@*jaLRKxRjfZ8KA#Rj5Tp` z{4^D;iV@P%8eAbjrlCEhr=3oRK-)-KJ+osj9!-On9!&t2-ReI!JJ&G)kL@caYM6ZU z=2}XB_$zI;Dq!V^i44tYbk-skh&z5bJ1?&|b?*D8z}ZMAfU9>Q))sW3YZgsKa6U39 zQ%Dv33~$5$WV5(gfG*iAvN>=;+}L;nIjQ#vl3W(SKIM&0Fsl?*F31lBUWayS^7=BB zzUFPuks(B2=Rki6BS51#26tU$9oiJq*SvCn4RXGjCAxSa6m|@@8A2qAww|dJF0x*T z$0tnB8rez@2bVdj*ukpf!_Mi+o>qTt=CorOI+d?4MSTVn!3-;W%<6TKU9iELz*cdV z%-xbd0vKjm8&nzYSmOnS%u|5wmZgOMPRCX%aYIto`-7xphF^kJWY&AF1DFb-C2%Bv z=2zBoQ{__0f`>+4po53+5;D=0@{a{JH8fVzXAe`qRo*%qnV9ax+UXMY@BjUOkDcPk zi{I}h7FV2d5fh5V1TrLchy=(>Yht*+ zP^W?QAqog96m)e14giBKC7I8-Wzn6LqGeH~9P^O6D;pL&921zl&;a>~1jOUX28tIj z+##3m=SWzh0gn^oIZ+M~qOXof!_jm)9j7i<2cV-MfGNn16wGuGOFtkdFaQF7Tgp<= zlYu^9BnNDf_T=x&h=0Nj^q|85?r{DAu#-(w9rJ8}4wboXth6g?#E&pwOxAv15}}-V zfEIi}dRWu-oepr3+GrBuPgvqlf_Dd8Fs5~)Si0`lg7iN?Pxd=28?x)IxS&T99%3END#D|XL{t^>tsSbmyWSc*zpl> zr+=>PQPd_vT}%-^y08s&#}ONnSfXqI&{)kYME;XgWWr$AW1b9)aUVN?eN|N<7d36( zS#LYri>?{1;Ii8X`%%4r%zfu%CT!Q};k;;(;CfYb1)6Z+ur|;hF6f57LYl=QSDy>y zU{3*9fdWKl6B3KjPBx{X@?>N;PC3$HQ;!qP{GuW?dNmZv$>(#~6_nO;M&ciBIE^1{R;T$SxcJ8{VCNW*AA^l8DJb%8h3z zd(_Xjk4IFGV*3RNCvswoX65YroWi)qKt{4GDESSpjpt-+5U{PBz@z9WAbDT`^+e;T zx}tUH79z^usB`(BC0-dA(H1%KMKO*3vFMu z_Nh#rb@Kxkt2EK!?NKgTeIBMA&yl zOY5d+&!7k80fgU zUXbe8f-o;@A!9k)pmI7&31=IU*|GVfJ;G@tCpaB2L?gR5PREq%kNH093jUjNpV{bh zgIdwLZO+e<{l3_;siad- zZoFv$MpuvIhDixJ9%R53FtlN=xk-=YOGTLGFotXl!V+_5nmB|JBN^i9Zn&4hvV_!E z;u;|vA+LM}QK#^rHAE=4^@QN1S=ghU5EJ`KlS%#$F76Xq* zz@)&bD`ZzsA$m78?uAc~B!a=N<>NhV8F?`y(poy$uM8MU1&2{I-i%SMeNEn$xd)~=Kw$Tu*!sokO zFP!twUn@tE1y6xg;5A6p+3QzN5|i+F{OZY=gWu(Vtid!y_azE!10OU1bpSvc2ov=F z+EnXQjR+GN@hC}c$||+5HZ`oPOA07|Kv_30us5cK0crCJ@$FG_;|5mQ4F>3< zp7oiUw}4tGo=lS|;A8;B)&~#zsSM6t7RY?-$_Tvm{RJ@(=ewuc?Ysb#L52lC~g;vo54ev#9bA@>9pOc<&R$R-OqQ?&*0E3!% zjLLE<#C?ouFKu!TM->0k567N=o}oIP8(y$i7_qZx*jV#F0qf=Y^A{YlONSBn4N3E^ z&I?XmWA;E01?T12xkEpZmwIha9&VhIJn(}0%`rb&*Fok19(JjDyFW;QNmfQx3bVJI z=a?#9DKn;{MW%t4R0Gf+qS}zh5b3_)^YK)Deh<-R>BQXB69g}L$3}~#-5Jv+tjOZ z_Ff5>qmT>U|pCMB1I6CE@wSqIvK%Sfl@9r0d_1c z(EWDP^7k+LTfnRZyQDD`LSK|?HhT()7~P9Zm@Km(8VSit5C)XWCwmSNC6u^bPQ8T3 zvYp+E@M2*SJl0@;+g}h}H-k5~mLg`QzJ|O2welJSW_Y(T%d(=J+R^qslQ{bo&0GfL z-ey*9I35f3JurxwG>svy#=?;l-CX1F`F491ShXe;{*R_|iud_kEwV1FnlojM4fb|=jtZB> zt7D(ZXTG0bm?rC@$jMQDl>yv=Ws}6HaUhCC@%X%4cG}pAK$NvAg-jT1olYrCpQ5VZ zVlzQ2bPn==*R_Z^w2y1Z{>qV*iQND#fO2am zKxso5f<#0hVZ0KG#~d0?ov7bK38R#&hrxaUy-CN?PW}w_Yj@Mn=UbS9DkDXz}TiYyA}nIR~DkSTV?b(K|Mh%WL;nUl@U2BtY< zPRWd%B%naw!431e$RJmvZg9`T1$SE1mk>dsH>7}42Zcf6OE>@UN*+f6R?A1t1x*cFZ7}&eh@p-BEx%AYR48&jDx%H3<*j!Tee$e-zJ$|4yS<)E7G9~9iO&MlN^qs2MF1sG z(@&4S|Ksy#Z(qNBbNcM{+c&SD2krNNe|&WEEO^Nx5Kv~z(`Vlty?K5bexbfrpGpcK zHQCDm{FHEJ7{olkamtu8iVR_2Twl}Wn}epk^ddY}?d_L5 zd=SE3s}Vp`+(@S8ajxB9YTJWQ1;48OqOnmviQ6KGbSCUIE3S= z!nk>gzu+@Yi=rh$Ov38(4LSA(M+KCWC!82kxaE=MaU7Dc+jyvk}01B_@%MOO34H{!ezHZ8fq0Z!(BkMEbo zPoJ1ZIjO&tM?(B6{>=>*BI(#{P6i{J2?m6Jz$s?+eM}eM0`?KWOnEFNB%Mr4xq-J9 z>AGlHPGrtnfz!@vBQpn$$j%RXU@&8Y436~#=Yb2jD#8C%RN&}XW>tk$c`k+ms4fzd zEfrzrM~i@s!x{=|N~D^Mgx?i^;h|0VCHxD78)^vMVmc-r#`a=jTU#|}<-+JGb`Htb zh1h?jvkq*7P&*r$0pjoqe*wXMkl~p1r*9m2_V5#;^^kvCTvPS6OgFd$iX3k1aKjUV zGcQT%o%*@|_A;w9zmASK=XE2#xISDUICGY%W??S?5K#J>z3$HbOsraeh;F2u^F%?~ zWpk81DE1M3x^n?G997m#4+hMT5Tx4`iqWb1CMzq=LVRYG7!k9Jtj16&PU|gDF;yE# zsxFr$UuZCSMRVi;7+f<8`33qa#&V%YOwF^LSGi&SGEgjz?XPOo+2JV0NKV z+`*3e&T9H}T!1?i>IVU3L-BCQc4qB33X*%Z8V~I7WL(1VFWu=AwEHwL}NZg=mD+=I^&&8X}(*3jCe`v^8(%3Yf6#< zbzqh0zio;ZfPc|l4xuif*ullk*m_o3dr{;)v(n79DLG++beKc`!#*LS!MM^@-IznMO$Rr ziKXx1MZAj-nrECx=@XDoQnbaaH_)=9VNgMg$KM#JwP&e+6@haCOI`4H-35<=476Gz z6DP}`MG{C+2PWd!(u5P!K&Rlr06EK+30;`&LJ7``1{T+)1p+aKH$p-(($H~&A(XU( zZD745=rbotZ=md&<&tO~d^w5Y9J~C)>FZX*uC(QNiRU;3Sr$N{XqM0qW&Y#z7DF;& z<492y0_GimW-_a`SEvTi-sWP+?SJ0Xg*GDuFgh4HhR9KwcV&iCi0I$^L4YnUy6>A4 zDL14)b$b0hC(iZP0?o9EImw8E5w3j*;N!^Frj^nRy*NTU*}l@{5Bhph0!6P~Arq9} zAo(V2H4ex(p%q6IWXgDmFBaP^_0kw|&{ai!)}14N`Vr?|lMv>N$Eipf4r=2_PfM9y zl@|rVA&JZ0_UjJ^2cVB3L@YXXp`9(o)`cXD5c*dGI0OI|iTro-|M-j{()+PL3}ESY z8vO+7JPeY0B=i$vTik40a*$bA*?L;c{h-rcE0af87R)((TfZr{QY3kherGB zAR?itnf$eCz%@=Mv5l;St2=TJfx{A5)iKtQBN3s+q!1JOz6m@>^b3(koJR5HSnY#* zvn>}OM8K;;+XkIypVFv(3M7ZM-Vyws42As}e;yIL(rgP?NqA9nhxgD&6&R-T7+RZ{ z`*+o-u31m2;d4 ze-zxdD^==j!32}B#EOb5%Sp$=G%pLMphjS9Yn}L5rpCarC<>J#AqWCh7+fvdni6yC zjuRcm8QibW&Ttu~5i*o?$K~qI(O0J!gqNt(Bj%mpzXpZ@;)P=x70 z#vg%n-VIu8;Pf=`-G0pKOuVord7EM<@)83r=f!zeEjgVYH+<11Muy`tp&4fRyt#r0 zaJkB-MlOQXXVef)7D0hKCv$_5jS$iH*>kfMy+GN|J|lN>yQ$&4O4a0%je~`Re@KqI z{#k$qi=5(1P}2haVnIIPG!_z@5_~RE%P0iKDOT$SBVXL}O$y|T+(@vQ{E%FX5XX3b zqCwY<5G<&D@^5U`Xim^lUfw`ZSKe&qRWVrt43Zmv0*RQ8bnD=vjm+WH3akkS%k-D& z)@fM3fMI>o=#J?kc_=BgHLa=@e}dw1<`j07>23JJtSR@UoI@zCRFQVgMN#YH8oVR) z_m`WFf|GUkM&~IliTP%=)>sRKy29=M&oDZ8p)XBo%yP`gUa4@g^E_dgbK{NiRG69p zKgP6)+C7C9z&8jxiRsR8b;|UG$}@dFZy>wKjLw%ZI-HY^ksN&F%7U}ae{f!6t|5aY zp$(#oazgCk1i`i5Fpf*+7WcZjN;%!VY}M0y3<7ip%DUmX{O(VZ{0{ns;DCVtrG$w= z{Pwad{|)Tl%@rMd578+sju9zoo!y{WZ+-*k|B^1Nbk>!f1mt?~-3<|3cq43^&w^=c zDnj9-2hK(2<92r*7(&z@fA43s2%7DT|Dp{|KggEe>j`Bqv6f56weA4TZdRL$OiCDx zplG>@LQDI=7>B|QVN-m0!S^E!oeC(B;e%>;2`P#FAVy5yHk*qB0(LBzzv>rj_u{S_ z4tT@chb zH{n{W@6WC=r~j*PaVB)C(4+J!OsB_(2T4*TSvK6~tyRQWzA% z;yPI51{MG$(cQtwe{Y+^*UeSmnQyWy;k2}v-cw5k2Zl*OD=e(IrRcp9W)d7gkr;Ni zr;Cqz5Tq^)kpk7J_mp_(f`@P-^@?2-b=H#$q1DYtzLPQqF@#g#IQLQ`f9U{DTRL>5JX#EbG>#{& z8a=(2n3FhB-~DOq zzO(TJUetm0r+H#V;8R>pJ3>BIqX~34=~o)|-HBXR`*g-l&BWrjN9#I_5WZZg23D*eZwq*K!}e>eNbpW3X>o0Za=<+8kf=jPhPy5*LRELC3NPqVI=(#bsq z>RoZG<^|m-ASZPZ`0?xJ3LN-oi@(#6`z>7|dJ?*R%kx&YMa=T&%n!jbFqj>s#qGqKj?TxmfT>Iq@Y0 ze=0ylfBl{^R*|El)Y%$MoRXKt3=0c;HJz%HY>CG--k||~s=h%5)zgRoVkdTZIdg$2 z$)&My`j_ScA}ga8e4gxw47*`m*HNPZS!lQ1%txLH!^ZL8Z66hvuFWnX($gDSqi$P# zl9(df8(@q5CPND%Mx3y40uqd|xBJOw^tsC0e;YnJ&28j$b2YmCcpnERMT|ht!qGbo z1LRjeMJ0BL<>^$t;Mf*+Do$K ze_wGB<`76ucnNf^lm2<`O+88cA>-o-*(uny2oVk()nV)hl4S)3Nyf>>oG*bkt?-t_ zu)eOnf}>N}T49ihsQgnYo|~K^3!TN}U>Addctbx4+HN;Ut4&^sw{QtHVH~Z)NUl>% zMwg^08UI9dGZV8X!CKj}h?_`yr)b#pf4(|=On7vv&93k)Bm0J)s+Y?pr7uzC0u|xB zqevx%vCV9Vj@WmF&Xf&w4FzPnfC32J2u2Pz)JCd?f2m{W zGD?agkE0IT@Qe#Qjt0+|dNH3dtMwJ^Ry4ohuEa#Hwpdo+s14f_R`o^~?GY8RttzHv zWfLI9;AbWr8qwC4%;nYs>3LVIdqWXc&5oE?T;{gwaoL&OGqgBO7VL`I_ikWDV4-pF zxU9F{fWNR}dCM$gvT>J{)xMc=e~x(`Y!XL9G;BvG7Uzvg+rVwqpZctNij{Z+vPEM) z5&Ny<9kqKRo-{U`A;#M!|Hej!OK=mUhMYC+jpy4oHT|$BzYE!Vw)oqoBn|2X##qKb z3tp4236+U>MB*RXH{=^cj{>*w&?n{vLkQ0}4;We+|HgSmzCwKi7r^%%e^u{c%#eoi z#JuGHENcginDL)z-}|F4DFl>XI@m$)EhMqO+sg6$>H;zyF*BLCkw{iQI0IKJ^x=`S zbU38qhrQm*c4{Q#clcv@N4}w;*??IWuCZl(_M$|DHD|c=Z2RVsp|6|zF&Fa3Y`yDO z?>Wz_UNgNss>p=Kh<1=%MALeFIYMi_hOpfCor zrr-sDU6aQ7So$Y|fA8kR*LX&}cujO9-KnP6_(`NIPnsH(tQ!4&DO{u8qG@l63?K&S z(I77;SQYs>tm=*tu)*agf}4K3k=9wYMs9?gIKFT0wj*Qazf9>BhQ3Oq;$&H6mzbBM zc?Wq+cV?3aysyQD!nYlRV1G02a_G|1{f{C`M>`!ATN-ur@K0tTpb%6d#av_jD~f4V z5qKU&r%DbJf9DO_x3UVrimuRvw<d?FSQ=$kZ8bylt~LRJ-xpV)Nln1|2Xf5dqvC*od#a{4&{|yPltWsO zG%K}9f3(RNXg5=Yc`%xTg9%eI$Wzf7fT5I>6tEObX;{l88m=X9O^|#IHu-JFY8vGeM3U{H{^2kLs!-2$ zjUJaUA#LkvUDS~B6x2MzKF8AX<4yE&Zng({e;ad2P~}IsimTs!g!Z}nB5dP_+(>t6 zTzvDrtc%H2cBAHPnV*TUjJU(mJ8Kd@07l{zcuLq;?&Q|AU@X!FvzSVlwg=j^sR8JM zxD62a!Gh_FU;Q&;UiO^ewJ4IPDPBgR+su1Bz3D z?|YHRrDgWS@qp7HNL$7>^)Fq$)&ta?fAG1v(J?E-lE+G~u>W-oO-Bn^$`g)^sSUar z2&IAJ00>x_*0>M{Rki`eX7)3-l!AF|QT{!XYvw?Kj48AQmEK{)PSL$GFga8`WWsf0e)= z1PcJ*?hDLjFA+->R#5u9TAi%3dP+xFN*vf39QNxBM;z-kix1m24l~7V_qIl~*w^}+ z65sJHdpwfTp*GCfI>hU3ZwZ>CXK_q|n=O7kl1uisd>^k_#>U(i$&Z=L9s?p^E@(+l z72R)p;xvkZhz**QIXSy&ryyyMf4v$Rol5RUymi5+hu{&Qj)RobE`BGw@#SR@9b^n) z*|_!EB^XXFv{NkXT#|c~Q)sWw8xG-N{seX2;K1@(XJo@C4!uov zqj-l?+6HBOB7XyYr5lg}ESoqC<3;>X9wq-zYaKX!Kq6=2(Fe?^x2CrqpM zlb6lLWVT5^#f=|@nWahJI-Eq(_>x$9UgF2>C81$2AzCc1=`|=d{35^V{zD49>OGFu9*s}#z496Wn`0r5y*FjA`BN4Co2M=&e+Jw$0|OKo zqKAJJ8R7mOUT*l;TOFZW-ZNfac_(^VN}@bU@`+Eg!A~7&+<#(G8{TQ6C+{+RVDL|MS4ozSSftudqZSe43zaZzP7m9};#K zm;(lS8MP8579BPT=m3+!dw$YGjZC6Yx`~$(o=YRO6|1gEzJ{b4+g>iG5Pdokb>Nq$! z`2GF+3jTj^a1j3g-h=!1@2i6^AN>B{-o4-d{@}hkxOe}Hd-v2o9DKYc4FB1{O#Z{c z2iFyL?mx*NR_xgVOpMS~>BfjNEWE6ZJsAw# z+6l0K%3l$AO8$Y;3qZ>F-@wz2k;`~Vg2>}$MrqaergAl_)qb|=&KuZp2>XpU=qjrj zY8pdE(FO0}hZ{&KOt>eQWdy~8STeCUI5!Sqb5n>7@a&r4_Ng*toy5F`8VN-&iW~L0 zseDfxqGH!Je`h#t1ySF!WdfvMv)I5HiSYu6mc%GQ+HB4JY+df_c0uUqdf(0kptf+d zHqRhjMbZ1VhG1|7M0^5xdGho-=3Gh`7+v_Na)@XRu=!tcv283=+G5 zMhcvJFcmgKYwn|+I;pkmHDy5o3mGV4Ed^4zD!}MuTJ>Q*2Ya_ z>vhCd7iHK%2wf!A2~$4ccrgAkhd~BDfc(kB8LA$@paGA3{Kz9&sBhY;KW@fA`Q+sF zH8iH13RoXBhc+X=puv04%0eD$G zMAB?seplT1-2<`UQbB~l%BHgZ;|Tf1J9au&e_8Do3b=>87vBZ$Rd+x8M2B&LQcmmh zV_$`w5=po{O2e(hh<;&-|NVG-TS@i|xp-NQ40w2P9S{6w_Hn=c#_BUD1INAXX0yxa zWa8rpA-HMnleN4wWS0%;lW?5MX7P^;`B2kI#m-BJG@<>u=WbjnA$7JIV~ncw5YDZy zf2Y&w@3Qq8|Btc*{+0%4*pA$?;}DT%<3Yh=PE7Aqg@T#1IWGX_s1@&lm3kJ-TV$Qk z)X12OcRhTmm?y~Lj2>X8C3OxM6aXXi-yVP&mpC*bhmL@d{Shg+J0llM*-2#Ui-Sk5 z)BR3LmbgLixH#X;iS!6qL)>5XKLd^$e_!V^dJ~veAwZ~EDyEbA#ANb=yXhs&C6HP* zb8G4xyL6CIRMHxWMipuQrhCq}O;ygVfX4hrZp+e}6@F z=rN7B%`_8&#nuctGNd>|X$!?~x2Qp^`+O9INBaK6uB&)@j*usX3K}oSruTyZg6B6F z^$6l=1RAL=E}IK}PFoxQ?v`@aW}suAfZMX4n2;^dTXX0i)g(PZ*Z5{%Lz>H?i6*oEQ38fNI`&gMYh7fWb5^wl{ujt<-0)Fc_?DrTJDX<9Dlq`2-TOp zH|YNXx9zt@R(0o301n;Xe|cVv;)fmp%>|B24(D|4;NZzIz>N<@KcTeWwn~{+IFOw2 zd&#$(`G;PiTy1g-2Fr)uw1%qL{SIdIo%g$f(Ki1Gcd$_JvR5$mT<|j-9nMhljAXrO ztMww3yulNp7e|gxZ#Sp;xOZ5|(P=0chfn@Tk*0!jEf@~I=I?ABe}^%s)SXqWwAe~k4x3QFdU7*&u|#bJPO0*$@-{?>Wdop49kdNY06mS=#Hm{G2{ zTNjc@Or221p|b5LYhV`JRAB1twAeT z(l;swpM+|8{AUrR;jd=fB>ht$csD#9lHOS@0R`S34j;0L^lLa_f5ShX{}&T2`?8gl@^ z8ivHGL(JsGIBB>WKm($bD$?d<(%~Y7Y~c0Sf2F6Ta-dfwp9&it)(j90m~c4Zt{WBDpem_<{~!M=TB-$J0_G46q4nuh9o0A8xl@p^ z?IB?gy+xi#Moi>*3SLaWMoG-S0D{>S0Z$+f#No&+-Ef}lh(_-bTxMtpQyv|s|J%F3 zx^&xA_BW){So4-K!O_wTP>Yy3l)7YFf6p@%nxvF#$_sz!(V5ylA?BJD(LLeC`vaIz zGWNQ$l>VH=^XHy;F7lFdV(eh`Pk^sZpXFsYG)N!18J+r|8lD>XD{jLrKRVb&1degconm1?OCI=wk7=80?_%hx%gBm45=&hi(kh9Zs6` z>+Dw<5t4@yb`QVHWpCevou(!@?>YNV5vrg3@^L#|_bQFmxxLr#e25#~&(E7?{Su&I z9YiMPq$_g?5d#HY&C!BF`lExbe-qD&<&h)qVsVvIRI+ zNh)j>>gvzSi^A4Rw-(q0S6Q3WNTwz{u-()|0odKFf%Lq(QRz>VUq}kP3n7ms&3ZSY zSezZHd4kbxGzP+apl;R06^`oXcm=SOv5x5q?C6L<0PoF03s3VIji zLtm6@DZqv6cLN$5N!jDs3)4H7?j>gC-)UfYJ;$?)T-}xg5dmxE3a(35=QT~Lw*X&CXajY3H2W`aL;aV znt0KYWfDf#3?^P3(nWVY^+(Bxesms7p%4rSSTL}+h*G|+@;2#DZ$ z5HP-O8^8t9Q{kN9f4p{JbUlaQCq6o$d9%KNgXEwM8FtMn!71NXbQ35>3t4mshm}Y< zY*%r_U4m1eILg7kcVrs%M15{#4$fS&=@!jOkdyGjB*6G7w0&Zjec+aLm6dRMX+s;M z;hF^_qt)o-g^ zIM~b*-r%i!4)-_b41qSooPfdMbESb}3}{qrmUX#QH*np@=>$5hx-F^_)X0Fpf-HiE z4pODn7<>ds^>ZK8e@NF5;?e%iEl~rScSOVRq-#{TW}+l8_i13Xlo+7pa^{;a-7+lG zxtmoBH)iL`f9rkWEICiY$X9rqF<(S)xM2l%yV?$1ihjF`3SP&`?)jg1`PFcco1%V{gIvUc(@;S!zGFiB#ybXZ=Y;W z_fCV`dtWi<;cl}0sH*xS?sr1{&KazBQ@zt#N;5od}l&M8F;m_c< ztI?mfbFwEa$0$=zN6e5kP2<4X(=1i3%*DW_q zpO-7f6p|D`3(=V3%~yAj1_ifyOg6hwe}!}~f5K{EwF|)``S3J2rM=X^V0aBKDy~UG z?mZ|T%-|jVXW#8U*?8bhb=i#=U{T=81n|`=Mk%E=KsTp?_BvH>ju8Y1N+6vQxHBfC zaPn1%gfuN<*Y*Iqs>Pc%+9ie5h}vq5%&vASSvgS7o+f=BU7jOMX2@z~R9#wcU3O7u zfAz(|!Ab)l72xua$>X}?+rS*xBfY^`Rn$y!J2aba0;rG3*d(~o?N~g$-_Y9By}QC? z(ay83T+!I^vQ2%`H(cHZ*$%j-!7JEZH7=4RMq~_&itMF*{_(pHe4NNQS!~nj0;_%e}bJjwZhAXJ)ec~0Fjo6pq>~$+u=qnZs`^1 z-ze2KTZ@Sfr9Di5YsgpcAj&cNgK2ZXX@bE~`IeTNtH6W}9$T6#`gsJReycLNL*=aF z4%f}Twn){YiGI^SP^GVjh7~&3m>v2H%J@g?t6TN0z2z4^u($p-f4DY??LZzMf4BV> z>k9?QPkj-RX?2uDRJTm4Z<1KJMyAzq(vJtpwE8ZIS?)8fUWY$$7+a=QmP8NYOsjeC z7ylg3v;x?$z+;qYl_ycp8T?cv{YxA?UnT=ST=?-U39rBK^!)$#AiUr3Pb~g#b^e$9 z=zQ}l#s7VI@BZ&Y{Fev!{^{@We}BKq9~1xA<5ozb)LV(X_;`~e!qPB&62ExT6>IYk zi$Di?TmO}5{1Wvv>#`$DkoKy|YV(Z~Uxz2n z2PVysSswi8L=JefL5RRQ+CZEY2B}fA}#`+uIP&ER5XNP0k@Ik%(ef1d>A^jsO-#&thWbfA*$`)%cOg9sI%2 z?qN2KGq9Wo;$(34i*|FFip#Ru=wTRq`|idPmJa7#%FD21@Y_hik3z%kg`}vT)q>P_V-`BzBzG* z?6v^5f7Zq&`B%GLnqc|do%#>AHQ;cdzaEdfSN-93u|>>zzhxsJN#!ksVtr`!O;Z7K zWzdACsURDp2S%8+7$N6HukGGmx_|w;Ev7o(tIbul`WQpJ0ydVgwSW?JTn7dWdi-+J z1n!9y){C1LI(QEBs_10UJ=oEmx(z<3e`U7#(0P(#!r+$(7X1o1qi)vW<4q6Y zAGFmz&+)F{p*!_nVy+?*{C!42iVH`fE4wOK!lVyVs-49n&ej+NlQTH+UkSDN4jmGS zjR8|TO6-BoL`8NqYXN-HmlVqy81?RQ$n0{7_+^fmU*Zb0YvoJG8T+t_x_i5d>kr;l zf7>o=UJ{qn+yRe&bm9zlEAe&UhVYGOQNx)W0;}Q_#A_%~1pGP>J%W9G_c7Vx8jEoC z@c^6^-H4|&ii{bInq%xl7x&<=EQB_u^l-g|X%OblsZ4?eW$*Qym#A(0y~CwKN)H9G zM+%Mf*%nK_q1f}=J(4a->TRB27HPc=e}PXyLWfyMZ4(M}=d_9^uiPFvvSW|f_w(_* z+uJ0nM%Ufp9lgOJ8|_!8Gz})dqc=fc_n(R17jn>hf}!n9EX66KF)nCaW=JvgRg^tf zZclrU{G1~LU@ogOMCKpdnf=}93ZTQGUC8Mw@7ee}w}yh!1_vxsT`xf;!j3Ive}j8+ zu%G_nGbksv3(+`R><6u?!F^+nqHQbL-P4CWX)>&+Ozb@hcA@B!y$CFC6XFrkJLB=G z_g?hYv+CjH#CI=g;0P#V1j0$+Ufpp%%uM_S;X535IeU))lsit2O=#a{e%`H4%`bouM|FI9nc&%(ie5HLEHL)YHxSDR%wG;$?|;~%()WH0KV2Z|kRRc; zXjZGD&WrqrV;+W9!w-ACm!$^u&X@JsAG392qUxr8y!8cHVUhHrV08_>f8nf@$SVu$ zG>rTjiL8N((xxqtbv~H5SODQ7j2+yvc7~@-Kqr7pI}cALx!WNlV=p3NkzE#<$ywaG zdVZ-Tpds|W>~6MwEbn}TW%I6=4c60UC>?Cec$+-0&{0TvUQvZc61^f93`+Eh9QTsn z)jJe8#cNPw<2ohO+Pz;61;k6E~a=Tp`?MQWfHw|XIlL=`~@CfB%ba7kE&!S tP(IUY6=}=~rb25~I}LIMu=#r`+TZ{D{m^x?dj>45~d7>RF_LbzF-@T64Y;}WTN6qfuen{3R zyyt$1`w8zS`JItRiVyscZ;rnC`j7t+{89~`+>ziVf3<&B*e6d zy+xBnb&_YIlvSQy%9G~o7a7&B!+JTa==WIYrpKa`MSh&;^$0r> zU@a3qe;T9k3{bIMAC@v5i5XU#A{mCdBhaN3Q6Xe3L5Ja4HPUc}M&<^9BT(qz6uuNy zWE$4VrAjVkI#z=-6?NIju}HF7X7yAIWSrC!%uBNKfml=H>wEMc^cf3>bk1sYEqQ>s;CE3GbM--0adXle2goJv7G~&H-2Of1J_K=-f)o&PE2u2m8;D4mQ$8Rb*@dDTIVe z7$Qj~uO?997RK}N$-)@9v&-}?2g^NX=#;`YZBf&~+jR~vMiB6Pfe~~P+ zt`#P4>Y}M9RkW0=um>e7bAm-S;M7^(Y3xd#RWdB2B@}_(Aa>U6qD=B~Qa0J7YF4X| zjg!|V6_3)Us%6<-Xw7#YLl2t4svvOg%lt}~&%#O$M?oozG>qhM=k0%nlfUdv{&n*1 z;SMYxlL5>8&&k7IClBvq#$Xgwe`ylQ;qF*G-rXIEUw;*YCaaS=mGQvY058K7_rDWG zq~T_%E2*jmC9bzjD2S6%SBD(z_JA*V1Dg4`mR0(rkY$x;q4_rZ1nkmW5C>WBU3sv8fBi}2yuFau z@2tG{MPb)$B56=G)pB^FD@+0USQIGg3=Fu&Z z79gJ$ZNaQy!sr*h(&+7|7Mn2D!e*O>e2o7=#E&H6+aL%!X2Mvobk5=Lwx)D}XQRtJ ziQ$0cQtEG+MruJNXwI@%f66SDnnZ_~Kcr&*x(A3VbUM$8gMnQ~(j=2a2Q0q|iy`7O zOh#hw2Qe37BJSTLf$GqDieJznF%$EF{#Xy@Vk!pTXL&6uS^qFtiy`Duhu5R|Jt6oB zidNkDJ{;IJvGl?Yon3@UDq|6zhY1~HsegO-Z}iXgbWW>q5Cqr>f3+s-wfRNzLDWkr zR$-PbWK~avyp-j&P#T5Uw7P15lkz01MOce46XwLJAjL^3qdG6I#gOk1Vn^WSAa;b_ z7sQS*Tf#`>B`y*g%#b#LQ6OF^&NjR&hHTjvJAzhyu_MY5J{_S!gIk5y;!KKsmDIJ2 zpI~3pJTJsmlBS}Re;}=hMUnx?O4MO_E}>Jpf(BzyD8qUwOHnUFpgv$4V)+ocH^3b( zz?m`lIU9_CHC?q!EEh>8C#12fE6cb|maK|Yu4Gor7)SiLf%yfTA@;;TC)LSH24Ms{ zwg5i5mbJLTp@M~@UM7_p%z-0)&=i!HGK2HD@CadAdTS#vdZe@NYfM42( z4F{~rshB769QazVK7%oDM9gL&0qefFDCq^yK};upe=hViybVYi^gOY;C61dqRxWSh zZFHLMKJ~+v*qdWY|CZQpQlI5npT^GSjUQe&>%0zAvB$vSDjvkiN@i7(XH`(jxQXO& z7)H@p#JFvSQ6wIUIJlC@`LZ61UDMwUa4ZePmtTq@wfw!<4Zek;r_Ue6qu|@oU1&7t ztbhN8e|3I6nhL@~7%T)~T#2e#i8>eeZ?N5<&R--SWIX(Ov=%=%k9Qw^BdTTIq%rJ4 z|NBv}JMW;bOHZ{V>HPKvlB|j{zr=lF62@Us%hFNdS4DW0IU4+EnWXXiqRb;%Im-M} zhB3;Gz54!pLKIKOvoMOo5|y!=xFyTEML+1Iu<95=_J%!|B|FUowS ze<)iGwGcA$^r4{Mw;Ll%2ljO{Qdx`YA}I<9TgR?WW(SG|oyq)`jP8}jSL}6-`fw4b zd~>vNbe6cR!sQBwOC?NA@dZ0>@KQqcN1Y6u9HB8jjHhA|sE_w{UDAK55h$d!{9XyQ z*G@sABZT5yk{pFSHlj4hiK?NAE_z;uf1o59I55|Y7(%RtOe^X2Ob^M!J@LpX%znE^ zFE^L#h2dHP32l^7E*<;Yx(j@`#3<|P&q=);vdK3XeR@-Bw?Hvl>}|Xx#HnWYM*MX4 zMT@+K*U5kVe6g}E0QD+I?r=Dg!;5rufuD$DY( z$~Lw)AR)>t(Amhc6tMn>StRp?I@`?vvp`i8Q*lz4Np_C-uRng?Ve_CBe~Olyjwh;b zAFM|x_>{4!##4-cz<07zMPFtW0OX_Q6DfQbD+Kje$ zi+V$pAL~IIYf(*GW{jC6NEjAHdOgGnzTgL02}*Ll=yw&C8S1Ne79N{WpoJwIrC82G zx(+b}>*U}Tsd zZPQa+D8$K+`(l+;E13CDP&X{1Wtg4Aj=QL3sb;k}f$5cG=K)`X@3_O}U=f9L5I2Iu z>^0uG0-Bpe&M)$dO@IBqy{qR5&G2ib#Gh}Cxndxt^4!o$Yk}&YIvS>{TQJ(%mf7njO^@Y&HL|MtkZV@wdpvGRizJ2f_)wA>NHuOmOWE5aXQOb&rn{M@l?2jlw^3JfUL{*cbP>wNt9|mx~Df; z)m_30PN#KKk$;rr^w8%-xRI&GdN;N=m5MWjWd+`MMfg22SJM6j?u&Nr-#mxoPnKT| zN9zeqZ?K@81)i*A4KyA-9wCom(ZPCdl}gc4Mi+V0Lt&kZlsp@Z6-q!-mz;_`X!NS; zXzF~?Y4WNBi#YBX+8C7CL4bFa)yDS1INqiyG)(bniGPB&B_By}Kq<9qY8~%dFyVoQ zan7lDmdDq|EM>mm)cFc5d<1VxO*18?l=3o>S0V~axoFZ9t_Bx*DaA^b=Q5bPccb$Y z99W|+V>hXUrMU$bLak)#9$5N3vBvFONtjr`ej26m0YkN!f3fm5 zECnlSHv*7@%{TD>yiCrMY`fV5HMeTJbXulSIA1E_YxBuLQJO=i5;5)fWCHD*Z+Dj( zOeRP+lTAdlxj;vg2_HN!tZYTQwy_dR8OF8*=6~YHJ4*BDGj6ZQxUE>Km78g%KCxH~ zYZD!Foi$s@myTLKDzA$=7kk#RVK|_)0pAWNDIfy>7{WeF&<3NRDd4VcSXtRNh?8^J z6b4KAq3d4W=sEeNO83pepL9`vuP_#|EUJzR_q*u&&rln&hoFLu6+Kf(E1XfSFs-Hp zEq^cxRuobPQ(Z!zQ>_St=T)6eUs?j`J;R!I{7zk!Alzizo@3eJr+|}B2V6THDeF{z zAZ;?-bG}xI`_-Szq?S+9Jh~VbEy<48?9>}?I>_kiVi|&75xYn&RXY)>G@cNZ;cz(g zc7wh9H|MfGaTABdXg&IypQzD#E-G3o7;TRdlwUnuld95kGvgKmraqv)MNi+$9 zpbS)sTgo9F6;nxDDFQcNE%-l$Duettq~rb$-s!&ww{9B@?9glXT<_YV<3 zIn?$6Y^+@99RuVLo6Q%zpNBbPA-7I1kcHN1knJ2?K5jfbQvCLVV9q{@5e2m{sLgLw z8_eSbZtAE{Oi4ttAJD1iwjq%|7CWHBw{V?bqYKFA8y7wdR z73=_&Y^a^Ytk({Tz9^G7b(F6p2r!;4-b#j5Q_5FVU`PjQQgm?v%xa$1k=v|3vq0y~ zW{#%-JE>Lsesz}F3m{BWTMrDLjcOwZf)42K%t_Q#?7Tnm1SHsusMjh%5r1pS7Cexh z!B+NJzAEx6spS+1mzmF@Q6xbrrjkPS{K|l`22P*b(Co> z+|--}=EJ#z!ISP3r@g|6XCvypxf(^gD059*2^OC5(=fU~U(2a@`_5Uj*RHLYL9fyV zHC^3f-C*ZWQUE)blBGOuvVUj!GB4|)1?|^)daWob_RtdSC@hAUL2Z`U=G&6dPL!4w z??w!s9i2`1JO0Cfv4cf$F6$xhB1{KHd}nRH=^Nh8LDfSUj`gsGV!}(P@nAh_F+5oE zZQxyxw*A#Gn-xcdG%;+oNgD&G1DDhuZ<&(**8^#~%5| z9hEY_s_c#kU)@5+F4w(6DaVAfWG0*?=fB+%nB-fi?G36Z^SD9GCUqG`a#AJP1vg^6 z2Y%o1^SH_Ot5jWstE?Sm>X@0IJH~gH< zz%)8+()wk7ZrOPuL7ToRQe74kp!9i~pM~iuSY=}x(})`6)k@Uv)sS1UXSlk7X8_JG z+R%ql{ei7AFpR{>O?E7!yo?c!72@6Vht}~!L9>?90K!^I5Pyai(Xoi?59&IsD02{& z4r3;v^Qq(@0DHL1;vqF z!v~MUH}kjvyH z$^b;$Ab&bSuY(N=le`hrcN2jIef#Yg^+KZM_GNe`QwQQ$btQ$Iwp9x+>fcrOwpE3f zk{x2eF3o9@tF&ol>|ZIXNM>P~K^ z4@aY*&JRyssY#$$m$k3?ZO2!1UstB{nD~@Tyo7Mv89JaFjO6&=1 zOn<;Yg4sRr#Ss85n`{=CBw*q!zp{lvqv;e?39O)VEa_g9y{W{f5w-8kBCqmgSSDds zo5JQ>0n@loEmyNHdi1Stoy#WGdd;=`PN~Nmv*DQb}-Ag<)JGq!f*)SoR zQa(%DFnAUf&hlFNr7(@M>+rl%)PbL>{eJ>ZV+|}h$T8}i)H=1 znZ0(Hts?#nEEHf4eDLY2I>+}q-5@F1_d$WwPH$|;@clT!e|Bf*pOwY*QHWQ5Z-0(o z?&+9}pcMKI;{O#+{6}W-@)^TGZjS%>=nr3i^Vp03_|3Q9KK@7i$KT^msY-4O^~g=w zAkLA)Dco%|P8JJoW+1jwt9biu*<||!{AHdOAE{BP>N2e5`L!;J0h%;gCpr_1>Izv- zz-5PB(MU2)T7?(#01YZjbpp%|z<<5JYp3nmiM5XI7XS z>N6$$nqK6koCR=X1lhtVJEB_=F;!Yl6xwtNjRneTZX082{k<_&GuUX&9!Vfd1F%YJ zC3wS;eJV^76WatA8nkLh@9b{dN|3MvrS>)t1yASfj4aTsNz(W=Pdo(ibAPCzj16xX z!|d~|yjO{#jx74(Y7kF^mi^%@mRR7Dx=I4F#fEF(j8?dkrcYj4tEvb*>_#EZ?hAesPvX_Y9SL4Fu<>Do`bumyQ;Zd<{VcNgCnz z5sVqh;S|ZkSb4RX7Y)WY^?#e2Kk(xp2cmU@jJJA#sv|>-cMbRIf)ltINh%w*rI&$W zP>@GAcH^Esv8>f~1gw%`&zxHDn%3H&2_!Us;(uGlASd-@=9jK>@juh=DRVl5L-tz zI^2r81EHiP?`xFYiUz5k$PRZwt8XI&v1p=~`&)LwxFM72qiV{82r;qJh3~*{vk6=W z%Q^iSn>AzXj@Q_yRg+E`A%9PL;z4b<4?E5%Msn19zGpg9%5$LPNnOftMHV!=xq-R< zbvjOrxkxgMT4R+^J#nX;2AnO6I2a_A3Zybus#!C?c+PQ}+tm0D#vMFCA*mo78&jI0 zKKC+!>eNjT=`FjCOpoNHx`ej6H8t8;f?Zvi*f=WSkt@c-)q)bUEPsJfB_pnqdI>Hp z4hn>|c37fj73*Nb-i^5{$dyfY7utFjetiR=Fql zoC^dl{q%X}r1mP=5tiVzu%D)MMgi7M!-_AaJeGZa1uPp{duDaZACL%u*jqCDQ6Cjx zTizQo$ag5eQE=x{yMG>MJ0kY9XIz%SIuC1hnRjFnXe z_s1wV~|>tb=0q;V9M@xV7A6N3kXscq00H6coTVbZ~VRmtupuxDfK+OxP}9u?L+ z;&0{r085SpwCZS&hFd)fPz1CQUpmSgd9dHlz>!@xxf*o28GrbP#lDT&x@#yTZ`rIh zcIs^UTcWInszDbP3K*rm;b(Tp;G~al8Zgt4Whtv=p2nN%Ju{ADr>6L9CU%2=`PQsA z*!Hi#iZArQqtU#3BI^>8nGXzEINdHND{3#)J;jhS1D z!958t9n#BpknvD-&CIJZg0-<_q&#!*#T(>SA&3WH7hD~qy^O5%U{K?6NxI@K<2qYL z%BrH_46aUaX;lMg1hagtY+_?`mdk7r8?0%x7}1# z!L2t{PJi=F8)}=Ye(#O6jp<*16P0!ob)xL>4YcLa-?f3Z3iWNEz(~K*ZYV!~H{@&6 z=Kau(7W4bb8!Gt*BCbBE z-&(@8VEB!UCjJ}!Irbkd6M)at#%BAE-QDlL+kf@#KfeCMqkq_c{2qT0UE9RR5PQNm zBpnVva5rFG5FK72@+=rj#RH5HM>$hn^mUnkxb7$jn=$4MKhVZ-F2RB|91G=;c!#FK z(c80k_tvmMei#k);sq$*_^SO*U`8RR^3{e05pl;NEYBNs6~%>%G~A@!{_6R47Os-W zfPc*wV(^uCC_adX;;VsvQln}LSifZa6|82XC-?Y*#$H?PS<70p@wZsbocT}B4gS9G z|32RR?(sL@wcr2!hyB;@^0%G+mpV~#LoYODJh%)~vf19s)QkD$dDX+pOL+`J%d@bk zn^GQTwLFIaR>m+CPg>{Z^WU5JT$-<+@_+h8-ej>!IEE+iY~}wj4R^U%h#A_*@(G>qe=2 z%5-fIMi8t{u|IR4$?_{oqj+C#n#QzsM5^;9TIyaZVz`03;JQ#{j6K-cMGv4!W-zw* z?9p#cX-8mp7__S}@e5mkst%z~>r3dwrk~W>SPB#u3gThx-FfmApbFfQT7S&|SG^7m zg;%Yz)!->1jlMO-K|4UQ95LXP`Gc#9VXko4gnIhR>w$?YB1{GSI+!*~5l;O|x(#c8 zIb*_ zMn~|TRIi3;@Ez7uP>GCaXfeQk5o6K-ne9sB+79!#3YCe6hO!C^oRG7CCN-nbYzD=j z<*SvEQJ7I$nlng0+IDJS;@Sq zhf$cOvOG!7voM8-vY2|rw3#MJeBTlCNmN*KDmWZ;kc7A3&hir9pTH0*R4@848Jrlz{5Xu)IGP{|65RkGGSLp%w-g%pGUgFUS|SBn)wh#{7Yihy+g z#`Ju>s^;1v+pWtC2qLJ(*sp)Zvzb)XPWhN-|4T5c&ZKU5Fm?febzzP#fzX=o zKgOqoZOu%!zlvip+v6IuRHrg5^{69l?P~1NX_a5fZGX^Z)|IXtTBTAJ!xcO|!suwX zQ~iePaHz~B$La-KFXBO^P%`AU`x`z4NBP@~teuVgx=PIocSg8mtG1&Gr-yOn9GWdv ztn^tig$tDJiA>jrN+`xOI4yo={A+!BM@PQBOq)#?vfr+#=18$i% zR1w^gyMG~x`ozFGO7n(0*p;wZy~b_8M6_9jANEZU1PuZU&H|PP0KZk1>fY|vs@B@x zW;5|fem&Oh_1EN?Q0r^?4b`HZrd3|;Wq8;B_**U8)#ddGifOXlf|JW%eRO334GLH9 zQ@tv8KywRVPan%~{7?v<@J(X(`#$NgSAIqa8-E1hj~7K7dbRzng2E?9Yu3(2m)l&l0o-3m%3TWwx zH}3N{6TAJ8$eJi_7$;c{n=EVRDU=WRC!A*-t2THqtbwt2cL zlV!r7lz(CUFLlrP+w!S)gR@{tE%)N1ZyiRw=`)T3f)eDvXw(U@|AUo*`Vb;8IWsdgd-pH%6PZ%Z6n$&Ad2tw{?sp z2Dc9+UdUM7zhTV(>#uM>k1?qSV1GKqnN6eV7U$7~eVkDd^FZz5#|n3)gw3|bM!&5Z(E(T(JKrfeE^TSk_zj~FX;#F^YArLRl3UP%Qq+n;#XCN ztD+N_&h!))nA>>grbSfPWpCZrfvv zmr1eLD;*B3Td4G`M=LdInj)&e=?QsND9B@zYTiSd7i!p1y#!r(i=!q?la(>=jl%& z#L8|F8Up3^5?WQG$M)cBz#)R-3~Ym2Fq3!_M5<;ZZIocQjP>9r#(ytDAaEdrFI5#X zZ>9YPl9So+Hbhq?|9g|2I;jY+Y}qyG^QLxb?>_8sN9u=Voh-tr_GZ3LMt6i(Uu{!)nT;158sU zeGnkagiz5coE89;(|l~ z3g~Pvd6A?P(|}I_UAB7)*xb8L0alYu0S>QY3Yhq9bl`3^r_RkiO{&~Yv#CY6PoGt` zOLj2pv};OWdhhaRGTQ)6kT3f-?f}ec@U18CHe%^vfnGFmD}RHoJIGX9rGFw9sy)hQ zvMfxu6tJ-DX|F8$IpnXu0a4WBbKkFZ>vJ%>0a9<{Itm`+%t(uxy_Yk}lNj%eAR9v; zV*$m2cz?Cv&&p|Bc=;dp+@}=v1KLe? zVPTmkw^>c5zB4@O6z8ox6pvePT=WtV8}BnE>ZYTWLAWA%?NBL`R~Y8Ai$X4abV;BM zNEcuDWKac06hFNBb5bvd0~AdL9rjgjt1Te6(NSabP#OPhMMJ3ZIZ24FjlhgR4`*g@ z0zt|NUVol`3~4$DL5tj9rN?YFT$5!fvn;!XWqDlQB*V8;utZQ5X;Q;lHTyZ+@swIG zRY|O2o{K~l0b2@u0IiFtE!CI-#$sYB`InN>%i9BttoH4Q58;SCQ7)#c2l|a^wbfyh zzy2z$!2S05tDg?ub!hW<2P&amM(hdqflyU) zBxV}Q+ue7b%eeb)8Wo$74vrVFCY`#7O5ugNge(9w>pcP~ApHeYpS``L(;R_aI`j%& zn?$eMg)dk9>KDKSe<;@A*R5lw4!ZN!y%g(F(R^;Hk`9$y%HiB5jExowM~+dLI+v0d z=6^g8`{kFE>dsLrIAx-$HfDz_q6xrsoXu9UJg1oaugiRqq_RJ@xHURQubDSLogN+? zynnra`Xj8f5WE~ZO@3SzeD08l*NQ~`0DR7$EyMb>DYGu?D8N#o!_1!YOIfDjHB3f& zIpow9(@@~d3g2VD7OX#B8N2MYjAK++1Ao=0H)XoMf5UrB&jIOhGjuUW7Z410qbrzc zC#9Lak%-c;iRA=@P-eh&w!x-G!I*lS%$jHCS!jRCku&iahYBKmbA_I^e1AD95+bt#8#go6}XM~Lp5KO1-cPz zD74p%i8|UwNS{9-8vj`;cjN~dHTZhjGR)#sI?lDRER#!lpo<*F`oT|jB%14oInCR6 z>#0sMKD{;x>Kwwl0Dm%j8y!3r$?P1ac3mNF%gkT-4>Ggz8&s(j=tH!2h~cO}1oZE4 ze*eZ3>8Z;4-rRVX8>T>99Eb9q2M>VU4RG=p^lNbD{4T!~;<;r65d^*0O>?A&`g?TT+Rlfp^OT@qZJ%y{;T>D)ImR&;Qj2 z4uYc|+^a-I&#eKNIR-VEi-%&a{mRQG3+NroxpI3Cu>KAOAj5+o8)^4VD)mSwJxVp? zK7Wp)=SD!NzB;L|$6}f17Zq-tSeX}7M=Tgxmp;aI0T=(NdCA*&lQ|wq{7tLJ_I+Ca z;RRDvd!)(izkeFpz_W=S`?RdUu@C;M2z~p6F!s7mHOHsk-w)dM=|aCU^OjAEclzB3 z1l{?dF#Ub%limtDAKQCfzt{a+rVUQ`c<2$0R6m?4)l(pzj{+G9_&qd>%hYh1U2uG- zgj#0m=}l8sC$);|6^fVDY5lbDP3XxIAZ{XDx`+)_f`98`fSxH))U28mF3>$K`6e%M z4L{)Gvi-AMYr(e}OkbwpNzpH^ZAKY>9zQrfvjoL??ZNFC6zJzCms#3#W!+2RQ$pVm z#N2}qJBFnUuU@EkKb-o%{>rg!*E2v3p=z-HUgYKKg~^&Z^S)hRs9Oh7RsZH~wU-hO zP`!AxUw^^>!>m@ioFQLrEuJn@ZdzzMmYJTWdRLs!vEHnE$D1#~us`Xyjqkn^^V(a{ z?W2}x-pb#|B7MiT*nieZ|Ebk@^ZMJnHujU!znQr!O`J8gJtXv~j(NOWQ{HGeo%#-; zG;y%TU~C9rs&1-$8@-au??bi7U1Y3&g#sg6SOL1hivLXhTPe{E$J;1yP8l&PDCpe`p71`5NM zl$zC<1cin3Kvo#*?r4oE*GH^C5u(x^ZGR|sc-8Y@RT)C$w^gtr$Y>XDzA#b(dr<3( zGQ8@1Y0K>+lyc8$sEGw}>qcnWV5EAXi9uhQ2-A*{UAHa0GT2(dCkL?7a~JWn1CND9 zZA}_sWJU@?{{%tcr()bMe0vJvcp2|JV7fCdFq7&7b2AJuG$+9Vh9`h8)qz&GWq(E@K@_>bS0k?MFjlRMIhjx6b z^OunFEYu5;GBINp0}3*`ESSyI#fK_8Q+?6@Pge=-HtYb@P+MYysghuANNP&+hl&-eS-_RpcEo z{3~3dkc|>qnbn{&vkq z|H00AI}CgoU`PqOXe2RDVyl9lR@_=p*$}I`<{eBgfEp<9A;Yd9=9?zeuKES$<3> zJ3CrbL=z-*JcFp&{%fMgSn4T-vKh+klI>19%&W{UgVX(={`m4hw+BtlW}=;85aD5i z+$?@~xpVpG=bf3pz-$N7>39I^T-HZGlxkRV)Wu$Jdu*qrO^Vrlz`cS4XT5Xzh^Sr} z4}TkP#oEHOY-Nb&9DkD!3`F3+K0Bx8gl&gREwtgqobAm@(Qa%%0%*yJKJ1E>=+!`W z05f4e#1x~&dhrJ-OnFnvSdo*Ef#9%p86L)N6xEDkqiAu0%90T){>CFD{sM0()&+zH zBA9|ob--f^!s?|m%&>X5riw=+V{)qsBr%iha1TaI%%+XdaDR++4Rd-=JpVrX9m^^z zlY(O83@jb!P6#`J8wlK!0*fbQp5co@=ENVC(K4xJ1Oec=l6#fMVVcy}%2sOf1N@)q z3Gm4JUX@DF&xajIy-1gF6ui%7?Xbz(=iyK-$aLD&u8koXFHzuwrhPM$o(a>jV!K*j2#tN{%RcK`OwdJ1=UcP+) z;??od!SnY=ubv;ge1G^HLR$al+x^M^3MYTro&0O?et+lRrjvIM@9#Kg_dm-teUXmqo%lO!M=@xO$P7GzN+kcA-e(zG&RA zY_N3!9%*(7ZdMPygu;wY;tJvm4pp+bLAXrldbtC_d!0=ok=q4KzHZ3C`|?Y`3znHA zwu0zj2Y=iqG$!-=H@f_K=l)F+s}-At$xh!noTOP3eyF`11m-a zYdv=zI}N3SDqqRooeIUtZP3Bse6$Kq4P8x(aCA}A%C&4$iDvV+2VS8?% zCx4K_%P>hP4tk3n?lP2CT31?`3B0-iahbp*$P?I4euPE$+5T&crF~C`!C6=>QAQZ( ze~asSsjROD8CbXDuc#?0{KVi&p3#r9GQUDAaH=iSba3xoTk-q)_|?(t)A!GQ+&_j^ z|7CZV?IO`qMi(d7RV{VQa;vpUz-G8~B!59;GpjXFlrRe_2Eq)IDvb}`!!IQWn61cb z!mgr9)Ul#f1x7R^TB$o7lLb~Fa6^L#OsJtro#&HkiD76>!6K}e z?=f*MTq&B4;wquSmeO@3)4f6k)o`A%7%C zdKF$*0*^zGC(A0BJL2CvPpI%Dd>qwNIP?8Sbrcmt?$qIoXHr!S9*XEe38kF?R(ci| zm9IfW>H9?GhGz*$sQj>J`I~LnxTwk)j6oqrX4KM5;^}n1Rm#^@Vll>h^zCUrR|sAg zSrHg2z*UBDDp0ezOJj(jU2UNz3V$v;7y*Q7v5<`P2*;h67JyD7P0Z^_SWTQbGn#AX zh){6<1{&ru@+`l8r82()z6)s(g#|sPMGuJ>h@<>ciaH02fmNa&TM04p9R#B$|0QbD z@Ew0*q4V|g)-ZEQwbiTEBa7V5JBoat?Cx5G`_%v$A zGD<_7?wR7yvj!dv5>Z}~a|6Vv0KR;y>bw)&=Kd%uPGos0%Rn5EN$M56+ZSfy=-F#g zVb&O*XrUDYO?P!$?MX9C79y#?0v5o{Xaz6B%2+&mc__}34BW=YA}_ZX>9xDW5aHX) zj8<=*EW<*&it+x1hTaL*mw$2WNRAOZI?`KP$2Z1ezKbJz`9V)S)x?A~(XnYLb};t` z0ZRNFFT)&`H8zw+F%;%ob`E1u=efWJ!3tGv8n8%#j0Z*D{D0!12%9*8BXyXhYKUy~ zewU1PyY-S<#Cx>+d8pUB+x8HS%5Ool!tA$&giRI2Z-P&-*(PZBUk z0^OMk#Y5m&DqsG$UEqnNVy1Z$yx`!O?T+15v#CaM0-RDfwmaR7+PUO=Wz;sSK#QSa zC!)gMSNCs}PWP)(AV|?pr{8l_NM*vSfyDS~(WL2D;?=QwJA-QEFc-2qH-F86rwC}E zlRCZrh=n_>Dp~3c=$SpPxPoF!8)11D+X;eT!|?$nJGl8_xhM(ym&yEmqTC`h`s&1{xuPkS11v$h9}SGW#Lc4^QPL$rY9&1iqLOrxKrLuhOd zpy;$yjVOJxOkzM~U4LNS=O%iFaufq=cO7OYtS;W8lhuG?`71GRtICE6 zx*(wWmYKBrs(<0ENz&NvyYza1c{ocK{}>cx77z4u1h!osqU68ysEEDIqck-2H|<_r z1?jOEboUmn8yX>ZtH#Fd(%zm2qNa0=DHo|Su~Q}_yrgpKhiL z(?5LHX|~>W)+&z(hq`^Uwu)pHmPvldpV(uGvzef`!(w*DOz=7D`>VJbk3_{cV$YAM z&8Lacxqlxs=wnkv9=*BZ9%&!zHG88)n7)l~yc70{lBmRGSSHFtRhKYHYQCMJ`gSUY zH)5GUW7lJWZ&9gt#?~nIn&-LVumGDqRSn#jT(Xb*plF2X2G)39)AKp~)tm<+O*j^0 zBiLRcEJ)Y3J7fqk>nA3zP5lGdu_KLiT`4Gw@slb(3xA&1nxR5<-+XE^m;^?S7EbzR z%^?YSCjg$x9YMwJqI2t145RvkLsoJoFywnmhYu)5V4R4Y?z88XA2PrGEO6Y^Tsm}8 zqP20brHO4HCTor6GfpGQ6Ryq+$M+2+SnsSLw23iWHhZLU!H*CZ9Y+XtT!Tw`2Nu2+ zWzp56wtsd<&nG2~)3DCXY6A|wt!W0M=sgt~)l)GHNj4UGlzs4FRGTtewV~FYC0J3M z)Oj&&fwXyIX4$L1)OLOI!k(YYn#BHRc~!ru;x-z72|(sX=)S6VDfDuKoNZQ}o>k6= z*a1#GUDXHF?L(fO)Uud@dRi9P;?{edEC#kQS$~jmjxhj! zHMMf9<|?&EADc3>K+~28HBx2;)^=D36;NO4py(Y1Ce&CHWC-yX1Ob&9bIE6IUbG_B zTYvlI>SnbH%j7T8oD`3nY)G8$*lXIc0m*_2sK-~8SP|!|Fv;9JN^>;`DxVLo)gxe* z%Ok!%L4nzzlSLr*Q0O=1=_TCK+iNIV^*VJGt}>#d-fenL%M6CU`oaD#6koB62DPMy=zt>Vwwc1BQK57MSlbETeYe$@2GhbDZqwV!O>e39~&jMd6 zTBliTSM#*i&6*?_3O`q|M;vd}18u1C+iHLQp|a^y*(gT&EQ%Ygqv?7|!)}}u5v=nK zjlX{##m>ia>VGSBj=he9Z53Y{-Rs|BTLnP@}riM%nIIO%^h z_tCc2W5?dXRbdwvg?{g07HB*Jv~yt?^3{SRGlx|D$aGk4>druw(~EgGrb=qpG5Ni5 z3*cSuyLN`-$2!B4HIwtmaDH6rR`|X$ygJaEu8$`XVd!wSyXmyH8t?6kUUrAx`#}oy z<2#!k{?_RFZ-4W61?p+)t|yg8j5vP}_w*yyEDYe5qv1vaY`y%$;~|*9WhYugGO)4C zo^Y;#2O{z&TIxBE=W$I?vr7B$+^dwHDeH0hK^AxNc#F$%$LSb%Z5%o z6v^Bkh~BBO^W)cF)yDfV1BhUpt}b+F$=}YW?@sf`U6N)2-j_%aN(SmD?oBEp-nP>W zg^W5c5aT2MiW3h7=?eOGxy=hpMiW{|E9*7c6nMLO`t2d}U{V(iRaTph8g6?D)6-6c zbKhp&a7^nzcI9Z<(zkj#gMfeE+6VKAhKA)-bVU%i{c7vEFrgN?3Du=6$!YS79_ILz zr3~8dT+AS&(?b1j9Qx@ioz<`VVJQFBS2*8b&l}s{ZDnhF#&_Gj5Y?^Lq^&VAkpRZR z3Gxl=3p=+v@y9w&=Z;g7uUr6e%WS1}S!1Aj1{Ek)d!`BO;!Qwx z+kVqSMjX7_Xxocyz=ZQQ1H=(<9gTX_$ZZO#i1_=H&_hFi_51<4S9C7j+swAlH{I@l z&Q??IH<@#{B6+8I_nS{Vj;`6nZo!%pw+*~=&QR3hyU(_%Sdy`YZaWW{GtjcAmdRHMQ`x2k@it|dVxua4=p z8xKDcPP>pE>RPF~^oXXTM%Z_IYvlG z@Y_6Zr=#wzd0ecPwH;a7InN70ii+_AzmJt223QT5&md6`jTsHzgnTkEMieVY7a=2W zG3Yb#F+*eT$aC;YeX2b2@Z4g9tC@4m6R#Qx@+?r*h6|N}4Ul7QFnoB>3tP%n59w5M zj%rO|l#;3h-r#0ypE`dpB-zC*FwcW9Ds5w|)@>ZqeREKw`72mgGSg2SY)T~~GE7y5 zWR&h$hHA@V$Z!mcYgwpgW~c@A=~^N{7B@fOW(KMvOVJYr_&dqYopOGvx;VBrt5nQ2 zV)cD(U}>HxpBr~yQR_CCAvrb;c*&>F)!_S56(MYUx4SbAJh)p^!K#Ccm!ACtH-Z5- zoH!|DGz*fCYE9PKB9+QM0h9mO0K1u_u$pMlF9`rfn5jZivx1kIZYWws(5RWb?(U z`&goLgT~8v&uF2y8qZe=t^r|->$nx6-qAZ))AyKQXn0-}u0bHPt959 z5f6~2e1ChlxZ@<@O@DSpsZxAbfF3MfHT9j)c-7SXcmOaDkylN9D<*0Y+7b^{x(f>p z$l#z7TnyHIQ_Y=O?n9FN{T*)Jqjw&TP5&5X{gnYe()%)mPpKjP7-+h+cKtlBE$FY4 z2uK|p7G@Aa4IhY0w1N$)z`x3C*u;DpKyrPi*er7v5; zb?d@Ita>-G1BxH+YV)Gkxr7YVx}h2op|fn9$px4QvehxEDD+c2=v@8GVM%$s_)A*1 z^JG&%z^7rG8a!l_b24`YAb7=Ei9GC(h%bDjE`%~#;o!OI6APoQJ2_F}%K*mqorijN z4n6$ze-zfsfU<`m$bTs*?(6SH>QVW}WASKr#81Fgm?d@c7x$5Do!Hi99b=e_4bg^r zoOCca27UH0X ze~c}h!F%Se)*cb)+GT1WV;mHf%{@p)Ho&`Yf9QJvfc-(sM$x*!bY;6S3%uAyn+t2l zK3e9CQgM3wNrxlKJrH0_BErBPC!rj}+0d#j7Q zsXv|hsc%$Gu7AamX5^NLcG2v2LbKCr#GC(b*tPCVwac%1VN%CoRFk9XuvK6STC{I% z`+3N}6V;?>T`>bi%o^cl-oiuvb?kP7^H~tOr4nX(8!6gtJSU=T}^50 z+nIeTdfMupx(;?~bhdu?Ek&_;YXi44QxnV>GFejvRbD6P653Hq7tr4=|A$Z6yza6u zY}~*;Yk$}OR=2;u?N0vPY@WXbH9yO4cel>{UX1dOjT|0Q(AOlNN^g~P;@WcGJ_>-t zPx$7czKx)7o)3yOP+Zs&lwzn?p0~-R_Wsx5H9SYfNLHTTnMS0Y{yV1WLYPRF#2oqp+>>-nN87UGRCJFJlBWzs{cZNwGTCa)Ys$n^LQ!BB zjJvocbF1<#IQB-}17vQQGz4SQxBUbu%Dl*{Ff~rmf4y21Ldjr-P6b3QS)>HZEZm35Hepp zOfw#&zKQ!iK09?LpP$RHqPP^NuzgQW91&f%w3&)VYApuqEM3SXQHiFA{Sqc=dphf? zjMS~9yHb05uHdiY5NglaGhcf?-V7cqhmn`~bxkGFwB!n|B~Kb87LI$+~CT3wSq`S78}yxWihWqa8Ibrc>1|yFKF_;nPgKsF$f05qup0 zyt2K1nsh3MF`qzx^0Rxc z8*PoxaoN}j%^PI>68Y;qO(KsnZhv0ygxR3x>0X(1Im59!q1B@eQ^o)x54}@kr5Kem zmRX&IDO~B9V){pm$w~6qvS9{LDXBZ>%t~3_vcfFA&EA$G9&g*uO}lz*Ai-H-@g~E^ zSwE&T9tc0IE>=)nYU-#~oJVl`FPdEy&#=Ioq)zsLtSx1$Tc!SU{HZde#ZNP5-OQNE(6 z6|xAN#P;SuJ}Ou;u?{L)FHanWxhBS%6=L)fdaZ{Ia7Bd-((}y$&$c z1}{UfTN7(5Jb9CSV0D_prMAi&~Z&s$#xh(-_<nb7JsQDOm!;nEdW{~+a8Rr;hbPbt}CT;AEpDEGqS4yuVANspdPa1 zF<(^;8kY-sjRW%bLSB!ZGt@h;)s4CcV=3zEqx@ioUFKYV5JY2M+JE=$#vSYe%44>T z9A%h2BU2siOMYu(>IF{UhN2tX*v4tnS|^Ia9r9K;0N5b;@_rZTGYQah%$%sLRVjVI#lA(W>kHHB@N z3eA!1X%;G{Vq$D*6WMPs?eu~fsNA_PED;y+>ghEeaCmH$MN(F^iKy^|$Fw{#jw2?C zu0x*mM!x)P)PK9xJZEG~+FSj5Yq{$kx=L;=0CM$6Jak>nHn8ulF)h)a-6boA#r`ZS z7xxzM+R(UIja`?~|7Lx@*~U%uOg`zKxMhxYAV|Ihbh?Srj(*+tB($yCL7HlYaIfJz z271aOp*Re_9`_Jw+||o*SGT-G7;fD&Z{8aJfXa0ZkbfCG9)1FDk>!cZYT{z@`0Sg& zDOfN$v5l;M_lR<7p3D;!YOhR+pX3$h@UrW9>pea-@i-7kbFvQ()nK5M3y+Qb3{e(u zbKY@EkD!EG|A#&M{4Pfha{)Q@)mWstUHB9%g_(87FX#XV5h)2%az4YEgbp5XOjH`) ze?NKkQ-46I$>Mrw9C}!Ie82{y;}h7RD$=AL?)+TNe$IAAT{x4b`Th;0G3*q#&ncq{ ze#w(;IQTgmjMn$=%{|#myLEVsbKA4kH$p9t3C7B);f*#3aw1c?g0EMq-rA~^*^1rG ztk^#{UO3XYnd)*t1h2s8KLNldVX>KxJ4A=q1zjDFbzpN*os8})I@ zYMR(JMpm#jI{e;RTcx~IT4CGEp)qw+CwgZv%*{D=XUh7jb08?ylp*d!gJrkO=I4vN zRNXK}3$#Q!P*fVb{BDVeoTTblT!x7%Mm3EPM-P2$KJn$&9dqkiiAse7(O+@)VcAPBNM#(SbaV2P&Am zs7qZ|z&*2jkE-y3zIUltq5r8(|B*DWV)uLYqWh^cuWqY^hj7*Q5KyNNQ?8Sne7pWn z=JkZi>#y#>21c&-<>4u3ZCqK2{(TVcy9(4C70lBkyM7zIkwb1;w-@Pb(%qA5GfUgTf7H zu^ zvU-wa6O6;L3d;)&^^4(smePe5g;|~@5ymKjP#qw@Ld)WC=T#u~>pF~4U%1Vg za9|KyIWdS^9T~({&J1E(hlXCV8fS*v$cm9s->;hbPGYSA??@I~crSiG7sKY> zwoLzjn_jn?arXKxZ2pC{`)6kT{X##p=4$pErGEv_H1DO%^adri zK0s3N^}F0Tc6E%dHU1`grC)#i)Go0*C@!4%SEpW|xh5%=ixAD9T(ye)unZdvlZr@a zRR`8oc~AO*uGLE{R`Jo+5^)pfDn|$UJNYZ<`-d-*DmOYZ9cGt@#4W-S&fa^JiC;sg z!xT^c^#oNJiJ7iEb$=kQ@5v9Kvc@hLjSea?AB(W^l7JCZ?D(i#p4zlwklTL~$0ye+r=JWZ%i~Tn*Pv0NEdUJYk{Ql|w$-(F69Bffq!A{GCMCIAmd!tNFJ=(cc(;oRi0i-V7MbK*Qm7Pys2gRG_2&ClG51| z=KeQPL8WT;RiC~0G>~5rf%Yq1=`<`=ob+B(3ZB__)x^uRM+$S@px>CJP0vifREDq3 zg|tgzc1t@N6Y$fb>DrVsrct=JesXVT=buHDH)SM`!hfPjvhz2`FZa->HuwdN*#7As zf4|XR5k?o`x!j49s@^Hmu!i*}_@(-sZS3yue)r8c0{-3I-Sz){^z|PeeJyss{`R}w zM~}YyZuc7jN#8ye|Fru#k?8wtfY<+N_mkW5JNI|;NB$6E5bQ+DCcBtC|KppZZ@&KH ze+0i&gMTOH{!=r?PT15-xB$HjOYvGI?l<*PzsQLD5rnXmSzM`?9*u-kHfW+{tnsv< zcb3Q3`tGG_3TcX{yQO-WR7Tu%LTqCWR2O>)g{8s?2*jxtmIHlrY5`Hz^^;b&o}~%h zXi~ob#WPy;p;}lJbP2kUb+kNO73mYNtImyMbem-sKJ)Cv1IDbbk(6hlcS@-mj7KB8?*oSeK02*&AB3eC!#cgaFok{ zsp4%o@O(_#2L>}46e{c(FF-{TWOvkJ`-2bBGR)4U2;qUBliyhyfBdQ<2$6C!6ld3% zuP4BWo?y?!{_8_=A+N%N-6&Lw_=rKO$~*> z+eIW*)kwzv7y??V&I|!{hx;jE8)nxqrqutA)l0v{ZQsdr*PY?~{tf5nHD`Jaer_M3 zT*-QwgLdontCLfsf6!RUFvgf#H{@z1vw8w|83Rz`6-BDfbvwUQ5TwqSUBXt=vaq|s zzKmf(p7KRhy`8sqI;OTe>RAaUb5))VM*%*{tSox6CQbS70LAhP-)O|Xa*Z&*{8HNn zQWK!qAiTY@T;kd_}$aQm@2`y{!InD!H;H=M)*+n}AmQ^!!`dHykyo7N5 zu<g|vtM|fc0`0kb_6$BvAc?K(5gVbXme{4h27^>M#i0Y3vx)$=_9$O7m zp8Sc38Hnb4{1@gRqA6tO)f)Vb;% z9tohHfBXJUC+E<9-!T>-e(h)r8iLbeB1YY%4AXiUEoF33iD4R6wV)sXB=5GE=toxm zGjkC~rI(F(A>OakGC7Aw>OzVL!uy4Oqnd%5tg{DqqcKMUo>&}=!BV{?A0H(dy!N3~ zrIREBc?F&MqOb_hk`$u6P(Xh%l-Z@Y3~@23f1`DQ^b@!=VsLf{ zbc-uk~E>V;DuUsJjGY5^U_Q ze~0_OeQ9o_n||xc+Tg_22KJH1fcrPl?{!P^_auH-lJ^i5GhenRD!Sp$9C^MbFF`BM z<0GxA2w3-&!|Es5;w%((lO*X$D4R6%6uvd_GVj3@{^E|@B7CRwr*1IWY*7CeNG zzk;fXB6#)I1Z42f6-uO|;xi@x+v9=)f5Q$8`jjzq@<=qCI#;Ugqs!e}u|1(?G^}5L zHEvukQ14LVK<3AqWpkz;|MCwshVJcSgqC5u4>TlVt09=p1RF#)So)&2|FB|OF)ZSu zO)HCx!322%Oe6Ft`(4?5T5;n7Qf$Gl(+fVDO>{JlXnLyOr56L2_}QgQ^Fo@Fe-?V3 z&2Kqj`8X3^8_rw|adYruSK)1?#n?+Vm%S~~-AlxRy*Fg+y5^%V$@Kk?`WKZ6pZkts z^Zn26*WY~o$iM%2wEKsD-2eO@f55Nfh=Rm3x`2;oa07e9v~GuBmYr4KCq< zwLF8;yTPOHf?Y~xLGB>yST=afe=x7GrO84@*HKEAofOG(QWY`+0|*PivJAP`5II3r>qiqaD1T6dInlScb;eyyFpW?D&^rF zTMGcnJ>{A?IBU);+mM2KM~FM9HLWaAjqfUtn^g4+NC#;F-P!7E@`0&Pf3` zOff`*fwg(Mtu~gS3&TDHu!u6I>MBEvsUm$~)Kr_0Fss}xc(B!Q6DquzhfSD;>2+04 z(mXFVVZ_VbhLuxxGiH8KA3NZbjcA>t+s`fdV4+(O(PhlmN8AX;L(t$`LJ7+1S#*4` z|NQ76SjAB19hNR2U`?g$e?2V2g(y})Ygiz%%pS`E;#_4>A{m2ruSpQL2Vnf?Zt(Tj z!6Odj@_9F$vm{^1U#cDa7wbL_zESH}M?329rxv>4u`LH=-^ub=0r@ug&O)iHroiNX z33gd3RKfKT^S%xKVDm1MT2_?)HP$g*k!a<`x}e+qVec_325&NEe-&mN9n#rS8%8>y;l9A9tu3e+ngztHTgO(O_0k6I81_+ z3so5jC<7Ym5CW1_y!3RDeDG^bCRb%r%MCSQOt37WABo8%%MVs+Nx!%LYj4cI)1PDi zqbL4nHo4j;|2=;6f8Flmw*Ai^{vrSUE`K*xqWgM0pd&^#n7$nZ!46y`AxrF3O}Pjo z&`nf>v6~gkMUu%0u}(WXS%J0-BZ*G*JGt#r<}IoXWpWkmv7J+tdEB6?bwYs}swBIB za_>x!o{`oFI=1uR!Os~ObD-JciR#%L^~kY|(h&4#m*B8ee}FXz=j0C&i! zATO^`O;#Z+C{)RGF=5>mu#cneP{#0BaHf@m`2ud;iC|D0A%JO0)tpsz3Dz4;B`Z`{ zAr^J6?M}fA92-V`!2Cs$#YuL4(x7Hzj(~>vM!MZ4N8~M4$}kmqHi?t!0uc|yPdPl7 zJh_x*jhb?9Tz*KZx;nYeqG1pOqq(Rqk^&G8 zYq3mXi9l9K6)9U!x;H_@fKquO%dpN%+j~6L$udK@Tyz(?Hh z8X4iFc@vXaPKcUx751RK7WrZUyI~oKeWAD+@%^eoG>5bdLroI_WpEyt5h7?=E{^yA zD5AV9a(II|sYI2miu780z&*i^!H6S_QZ8r+b5Vk+u#=u*7=Pl6l-cTo(<2lu$m+2=ZD7!&rV+*A0C|8 zaRuWHxEv=)-&b_E^Fy$T0l@bF>W3X824k9nI!WRmg6nXV&V{U_fB=*Yj5-v{o4RP~ zlV~YdA+>*U_|qR>9*7r*FC7G68Nn;Al}Iv#f_Js7e}85fodemSB8{3!7iFH;sO5wi zpI(c3;FXwi<1=u`F~e8;sBG1_^lXzn&30nz>QDDCT`T= z`16+AQRIK_AHCc`O|~JsnvWW$p7cIQ&AC$%cL8TiEXpPY#xB6!JBt%C55Ox+9i0mU zoFuX`)MwRa{VmhfP|nb}jt-$OFU5{H`5!OgWV0ilKQ%z9_y%~LJz-lKFbw=`svmZ~ zkAL$B*DTI7Ww;65igAy_DKw)+g;QD#|5qN19lVqhG|LTqKJ{5cYuIc4fA-#exosrL z8{J2#_Sff-v`I+5^-NQ?wG>6WwQaRzNgl7)_Sb|6kVG2<*Z`HTMU5N%%HddYh~sF0w(Nrm(jt{^d6jMU?3lI*&Ih!@Zy(0@ju z7|ajO7LWtutyG9tEqNA8{Zg<6C3{k=rT(m1RHT>1=PjNi(Fb^HP=+=tsp6(et5W7h zcNi-v=lLp)sK;&H!;U6^-eiv0?_&&Cqhk#H@8b)ldQvZL540;ci+>- zyY!C6*>^RMt`;hJt`&vu`+uy{ugS+hqw4^PNxq^Fvj`7a;2oiVKzGXLdBLr>xmR7? zg$~MvscM_@f)4~YxSKix8QObKB56Y%7WrvxHz_e1U9~LYXXRw4aew^%-piMMIdSWm z=EX$xj5NFiKs>JR4J?`ro>SkunEyvdn3yqYY9K8c`^2n2TrM_>UlFIErWzu4XgYNU zlT(;UbNp);aoWKoMF5PCsH(y%pqiu|DvY?t<}@FkmK|NOh2-OIjNvJ&H~w~3#A#P= zN>#CnyKA?QD{2A%?|;g0rQ}1hd9r-B`QfYjCj)=H@&X2}>#2=ge?^OjhDV)o$7r?F zrT6s#%QyUi3>|%9_*s)^?eeH`-!w>~>lWdciwBehVU%a-<)A3S%O@lwp3#T}|B&lO zWJ|+Tpq{t1oC`pXxBAWR$|_7_d8w)T4k@oUOe@>``0G#7W`CKkW>=_ZerT?Hl7`8` z)V;BxhVZ0RDLAtx8Ei#y(W~-hGEozHWprKixx+>0o`8c}_q9cJwy4+X3SGLS_tjPy?AO1O@At4)t6+{L!i4>HevOsb`@T1r130FH)hph16xSW;ttw7tr{BTaG1jF z{$*|M~<+*$p~K0s(? z>D`Cx8!f__X_8Lq&fuba(kLOwDzL5x^!C=BpwAmw3KH2|q34({qKvI$T`#OpZrv5D zOk;m0d4D#Ei>yg9lbNAM_K0P^L}kFVC?oH_RFY<=9r;ham+o7z-T z#Aiu-UR$!xDEVmX^xZk+@&_snPvexV_xhlV2Y+0Bs%>tlrYq?Qmt#*y{vbKYrQ1}S z-dTbwf0+YFYgoo@y#reK@BcIG{j%Bnw)f#4fWWtUt%i^OXYbyhd-uM^53KhkxM8$6 zyXwK_W(Qn}Tj0`FrE%1PZ#tftgwcTHoP*aTY=DTgiNZbpur||lv7wLnnF>MC!zgxE z1b+?g&>lFK(LHCpSXP&G!Vl4gPC;Vsv6_#nfqyvzZn-6y4lr&Deb!Wg*K2XqRb@^$ zG4>5$9H~W^CDRyHcu2h!Ruqe~TB9n3zx1vTvCsL`Ri4p2?Aho9j;zIZkAG+g%krwH^-cW*R%OdIUTWTz+f8Yz{iR2I zTd{8*=QI1nPt(+2Ly~tN5I){~9wo)G?v&eD1HGO>X<7>{TNi5aZi`Azseg&(T|dx* zh@M#e08)N#M_P3G(6Xw@*;Kvz`NMK{v98`#yR^MGwDJ5)x@_+uP^owPhxmL*+J9oY z@16gbpzHH1NGSIK#5~KyKN~ZpIrV?Fw3JF>4np<+r+pq$zv;dTBqppJ6 zY?@diq+I=QzO;-IU#+)F+}zpab$vBPhdO<2Zu=X7{{jSl7X(4WTExsUP3z~wubqV@ z8YYdI-lOAxassjrY9*^ttNeFT7dUR^#_~pLw!MEUIz5+aL*G`b4Wrz5;Q9`g4XLM1TVE$=^1rZe zt~cb0c>}I?)rNBc+tKwc!b{8jV(Zqv7hFKsW0cCZo=@m=!TUd@0c*W~pS4JHzSA{+ zdyY1W*I<`R&^wLam{v6b+J6QEPAtDZJo=DBi0z&hcrJJhqodvMxW+11=Ky#k7h}eF z53W%#h$=2Jbx+?aHM<4MO+uJmk|&Svh-Z}h-58F5YL??g`vlH}Rj%%DZf=$!QFSQ} zqG8`FzS^WCMakDxrDldMJ7{zzLZ^#pKGJE|aWBc#KR@2&Y;IVU?|(Wt-ZKLn&@ppd zcQ72j+=GW$gYREx#%n}V-<#`V1y{Yk0zFm`!#pqG?y!W*zAWN%1D;btMUWkM2BGN8 z1RO9MmZ=5N!uNx8-Hw-*IqJ}Om8L_emBVFXPGW9QE9K>R_L`mLS-`C8|M=H``Hz477efg8{a@mL_W2!HVhVSvw9aHWff_-g0(uA& zTdIQ}M(jphT8sKcoW(^lDaBi1d~!(geXL#F#FLQ)zbfb9GJjTWz|aOvw<2F?CjsM^ zpdA@If!ZbWEqbAjR~bT1i~-r8ay>+JHPTW9=XR7L?J+cWs4GR+8NO@{v&(9pWV0J{km_ zHcjFJ$FzbjgUyZek?loekf_)ra1>%kO%=J#p)nFD5aPKdxI11GM>*EuJsMHRF;nfU6D}PnF`WVMcE={Ot_*Q3SM1>ee zNtQZx6pDZN;Nr; zIyO0cHGkSC%02}aNKk=RjcyJi2-I;Lb1hBb3$mm2uW@x2$789+__El3(1GRLcZ1um zROC4CGCla+$W@EQFZ>2a|3K~2Zp^h1cMvLowmzZ|7FU09@-OY_SYuoBXXS`|TC0XQ zjGSbPib1v)t1M8v+TT_E`@e}FELZgJ|E7KdBrf@~u2C+QXtYFEAuJO|jL!}po$QqY zCRl*@FF{~x0d};?j^asPL=LC1lP#lwlgDu^0>wd-^>HBveom0-h^&(wayT`~1K1

pl}2$vw)k}`hcS~rB0&ua8z?HSXX_}*i9 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%0Ae z;vk9^?ce~hGCW<)Kw{2+dk+#dK>NUf6(SD{dDzvroVCYIIKphuY2O1U>RS~P_2U6$ z&lymw3F!2jgP#%vC@m^e>@D`8ZS~#Ig+GG2xdDd)P>2u5ZT%=8fQab5k@M)rZT<8w z+q4|~_>;L6TSp%y(#CTt-4qlOCf?2^e~w_cFBqL7Ap~s zMXyiEf~0wZ{id&fixyL_Bna`uiHU*~;<#wNP)FlGsC7ZLYZPzd?=u-DdY&Q^pr1_o zz*v7RWF+JjVGaT9vCbJR%4XKW(H$v_Az%r0d#wX?13e~#m`MD5R7ry;L4_r+Pt_rW zH=AM(LIk2Jv~4hW_BoB(=W3m&mEMy>j~s>F86FTx^JE8qZ%KR$#N6964bg3bkf)TUXM|y=mLP;e<^g2Q(7-ayt1b~|QQjcQ|N$Ni~ zXHcnaW2mLG9E2kzR9SdIlX?vJB2iYFsiH$KB$yirXCzc-8cHFc9LfTI1kQnF%77aU z7Bk$oE7jD0*@6jH4~iERSC&&pi1lF>2T2XU+15DuvCOcEW0B`-ioBxZR!@fpo}0F& z$q5u`rwvto)KdIW_vjob}JHbO?*7thUB^b%#i_=3X8T<$qlqlY#R7Akl- z@A_AN;VZ})5DYCXurKBm6Q0IWVyDEyLkzVFu}SmwrpC+{SHDSve2^O$*jFzl7c;~$ z-@mFy^#+t7^E3iD92}*#8+0dVX)miGsf&ORMhhTYbMsGzV|C(4w-yfCR4$0Uz&GJw zneT;fGD!Yk!mvKBb*oVNHawJ++Bz-EHDc?3Pn`;GX36CPv!>dYVg{+W28yb!FY`*D zRuCPbzrWnJl$@;F*BbDwG1RkNZ#2FIQeENn|6gEq@IYUf*4BBd(RoRl03pllJYkx1 z?Ts;@8Im94sz^8J(|SbY3|K1*~#q!P#zq zI5#n6v8@nhW>7h&P>MZh5M1ju!%wAdb7%Fnz=0NxdUBU>$(CTOA4+52|A~<%(7q6? zB8b0W#Uawb6>aff!T()flj3`TL177uNK2da2HkqI8_@p?I;@heD_Tj&ZCw_yns_q3 z5w_iD!8A56LgAx(^dd$?{GA7m5Ou(R`x!lgW;^4*XhYKvvZW6?LfNY_=GJkoJA5@% zqfLSbWH2pPF=%NY80S#9AuMJOFZgbTq4NStWca8zyo932ZW1HH44BQu2?2XH%ujWj zwR>^jjfWX^)2hD4`uGLK-uyO9T_h0@8H)2{nhqUvTfs(blI?f0*jFl zHtE&{L7j7}&&B)x^euu>d=oCt#IDVUmN2_)**b_sOH3KJNpga5H(B8iV7P3R`N{!U zfO>Bf&BE5>&D`-sy#mbHwEV_j;}1LDb8;R^Spvq@re3lr?J4Wj#s^{~K$f*ljv%&z zkn?6x_7Jgcv}!F*F)3EI+9gN-exJUAu=6*sryma_K>m`Ml# zMRM5LpDr=#UO3w@MQRx$l#_mky42tzXrxZF^Snx%qVBXH%Qal4w=d1u6vMKb1QOF> zlFze~Hhh4$_emQx^~olI)D2*fqJZ(sS=}LRF7=*A4 zHLV&vxtkbGoT%^rG<52F(CB4EPpc}WIpNJTDHD)xp66;%FIo<9!#mO?uUJso;2jh= zBCK~dkpQe9w#3(*%<5K$X%}D({YjlPD(hWGoNNS_MnWQH;Rp~-wi|Hk4H~FJ^Y&hd z@S%P+@DY2=tsi0fM4JkK_RskypF5{F_~$Jf$&=KZuHEO0X?L`(MQ_9fKXW|UxsD^O zXB3IK#%w7G>wl}J|MZJ|)VRprP5$YZCato1t@L)WDBiwvYi(km<+hGw*IwgK0L+*) z_gKBQVbv_B69v?y&Vx99RbN8@AARxfrE>fDISN8p!^i>FhNq2xwNrW-8_f^BODC*5 zKF#9ESl+!mS5Q!j+9!Hl)E-7LGIZiG+BdS~`GiI2M3jJo#)7~oQ-cWP1YWxqlIbzp zh3KP0rHeAJ0N%Po$GIssv;eMGwPClg@{TNi5u0ppO~(;iY`e+CgGa_m0Ifxj3hq(& zOBr0tu~DjYgDy^gsmo%b#<@M3j@5a(KsAkLXuwsgZqPvWEF!=giYYH=F0drIFdk0- z(40eNW%Pj0ll_2UH%#a{>NH?}k^YnUz;j{PJRUslqv6sv=@n#pdP8f>ZHtf+Gh{mh zY_T3?XhFnC5Ee8bB2c&9?x&d19hEgVtUAqUTpjkD-<3^*EDi%RYi z%hR!X!MQCQRzw&vGZ~&Apl>4m2`6-MIX~;%PKJ2^x+Kg%!zjw)euL2g!ityTNSU>R z5dvO0-k4NzB&7@@Eg3NkmQ_piv*eTyO|511DJi|gFLwPEoSn+v3WJG6 z?O#aq+~p~f*;`Bw4lx*PLX4B3?{>SvR0gy?-oPQ$q;YW9+2Mi3=u#9V=?GTQIVkfh&+PqAoc4D0A%{vAFaY5*f{}v_wUt-H)G;C(6~$4+QHOna$_<`I zlV{9-d$FD|t@JhQR&>AMuEavFCSQ~gs15rQzUsBkn-gkc*Q*GDQe1$PgP)po=xxbS zT;6C(xA~?s6yd8m5c7)L-1T}~c4qhVElyJf3rBWJaX5XVVWDyIxU9F%fWNR}dB-ec zv2mLg<)N8z&Ux_^DwtJ;)p;5O=iPJLQF!IyXgvcqCN7yqpj9kqKRUNqKU zA;#M!duB7kCAkSoLzZ=O~v7sgac5 z{)gord4`f^{hnf;O;Igh6o|0q440nl-aIn&Rb4&eM*f&?_I>ID=Xu@frdLN5na~)& zFLtkQ+}Z*_@`b zBX_*m89xn}Mws5@yY#@xi*Uu9a^nCi+1B-=8^X@L4V>l~77{x6geFXfMI-|J`i4l* zJwCBFgsvz^un9O<)5^wMu)6R3HuhNp8s|0+G`m-*F@{~IU3+6>DKbcp23b16S5ch9n(mkZ8ytQj zTmZK_X`NMTX;zHrw zjzh4!8TV1Tv~>S}k)?yZRKfh^f+nEbkHJ4sg*YZJUrboN>B=a0pKW5S^1g3 z>=TD8=Uv6JRgFK|aH9#!i_QHMc}YMmP`sn2vk5wT?e|$!vFCd|d#o3P+xiYvPZrf^ov-WWCZ0SSQY!0h zAx&DRbfWaU!3r-fyvR9)p0InUkX&xzxGHEQsw^rQa+RHR+ESWem{*5!UZ3zp4{aB{ z>_00;X}ydOF}e7bbYKGGJgr{fm_$az;T2c0pM_eNd=Fu;i?vvP=Vw|l^y4yb2c6iM zGvhpqonYB!Yn}^l_5u)y!<~^4+<6bFvfIN%P^C<6)t!B{Pt%dAZ(+hZ7@fluQlqdm zinQG6hUQ&u5(Iz9ufdWUf%gyOn88QI0fF#TMR%aLxX7r6G$(0RXj5pDG0<+R2=ibt zg8&njWKg7{H2_0@btP>MX~D0zWm}LzHw8x*Vnul-f!>r@)7#dF!;W`NrpzG-5fAd+ zl1*4EAafYguoeq+Tub7bAo&_>^4px%G|K0QB-=s#!(j+ap-$);BQ9Y<+D>hays~-l z<^g_ltSmp?MIYy8ccQm(mjti;1V?f8`%ln6cVC2k{FoDe={}u{Z@!mRKDthC)T}A8 zr3lMNI2@z1rtkw`Brbq|g=6JTZA}BtBAqjroTO=cpk3PvfG&vJ0D&JYn7*YaUZqz# z0{pSSJ0$mXx>yud(cWO-1`!!XfXIO4?4*YC28z-PDphl;-+Olke#j&I{{9MUdJc@E zQz9(VZMxonn3xjQ!Qgqp9Tvs@rA#amX|8W1$J~BFsRqL9+TrDha9oP;%0 zC1_#B>a5uGL1qICy5mj+c~_~y!dCj`Z__m~xmJp*ktG6(Q-Sw;naG7fL89>hZ4i_# zW1qT*u1@a(>P|Q(-RzjPVe(|7R~SlLhNh#1EbR$@N5Mi7kd7oM3*Gyt=vU2t~@Hq>yy8>UNo81X3G?n4{ zV}e@?^d#*uBOE2dAu=TGH^y-wcBM^I|IEAC&vBviHk!knD}g@<768EA=UB~NAeJhu zpy~5}a(%u@t1+ptX+l`)pzJp(jyS&4EI#bpFsu}}pSLrk#oiX>?{Yb6CPaIN)-YG= zpaaM`C1}o`#W4v^w)pTs4%xfveY|cNA9GhEQ+f!OiojgZk{&BM-*&`l6ax`EG%7L* zcGFHlsvvtbGB%Z*k9g}sOb@{$z#IoDr(OJiP7dSC%^*6+8N#x0=ds6G;Wk~ND}RUH z#hybjXfE_q%pJZV43BaN?e(hW6dvYJP^%gTmUW$x4WBvmHozcDJDkcksNxg(8|W!r zgA`!d#Az5eI;g>QD1{L}K0YRGZIJ!(bN4U-c8ykKxqrr_bH8}iY)rVFbW7a$Qdn7k znh?X6{~P6zBwfdG5~=V@VwHP|pU#(rK0kgfcj64}9g4UmaY75ezY7i){_rOUn%4Z{ z4Y$uL<@`H%k0HDZt_ee4aQTJ6B70X~qVplVSNL#)%QBwc#d9h4rK>Rr;^=RFV*wIP z_j1rw2nUe#;c6zkj?~GHB5r<@L7n1%e}faVsu=Q$K(pBs%RdK5Z*pCrGm}^iu7A<1 z>XhdL2DI+w&i!5Ql>b|pD;W7zcQ4A;3ob|B9?f*_+;Rrc&#~mm&YiL~{h18^%`+R5 zWIchNo&gG6+WkL@NO6A;k4gONts&JN_nCOH+!H-^B~eBudBi8|;H!>Y?_M!~&Gx~0 zBmpyC5}=qT9$sgHsjIclSpbaNl z9=JfDprk*9pWK>BIl?mG<*s{9iF}=o&r^$I%|eA>X-)x)v7T?iB*8=vgt=mPCvCRoVY(>} zbu%Yedwpo<0^oKy&7NnF*x|oWdIMSf3K$dxsr3BG_e_?RFrvK>iDi(A9FQu!;+LOn z+tp)iyUR5s_ZSF$LHiUO{$MI>Y2D0+sdZ|iHybKn1Nu0ydmGAsfN{yM545^CdwqWK z^vRo(SFg_b&c-IZq?=8|$r`0bLhNH?LkeqkKpUIDrbGP$4?yAT@dPbBpgw_1KJe#> zEY#Oc*&R3IS$=$e_KXk-9{A2!0%0bOiQ4)iV9DCF46yTiow3g_j|RAoz?H9FpPkx* zAIl;P*S(=edSx7cKjUny{m~z*mun)q7grQvpgWDg%Y3glcjI+aL~?eX)mZWXHE{J3 zV$1U;&&knEIYA~X%DSi_+u+I;tPxz}@kAY96At9H z+kfM(>y?4S&UUk9fuxyO9U*o)t$hk}6o#d=Ci5NCscaTsUC4u)G!;jkAw`Jx=YhL% zrG=@p)fgOqR!xsVZ+$x+kAI(THu!&(9q_kl0AudREjtboX*TQ?JVs-BS``{*w(Tqj ze5yvmK33{k5O$Gu!Y?FaGTc}BQZtW{d>cdePEV=~z(WB4>A&3v|2vVNLh&OZSowaD$c22H%h*le7l+KQdZCyH z>@x$w0CB>XFqgnaMm{+7WP``Rq+-DD6SwA%c@4m!KFiZ4Z`2?!40|dMz08L|e>&!( z3?P=XrFMBk22lZWAJpFv#Ic+8ffm>+CkIIoUa+Edy2=3|Ut_S<#G~aCo`!bTWDldt z9pSNmfmrI=L*H#@zo9wwh(_F&2nsW1V}=~5ZJeRBjpDOA%pkUXK8e92-~QOHt9V_N zuwI1*8ZXGY^MgU~=Q|j#3KF3N)2hj@>Px;(TO0rGwz6zeu(8h}-rTJyN#_`5JN8vI zNs|cO7GxBb>CF!j4Xu9PUtXa0L~bhkgF~<)_d;*gc3uznl^4F7^6eX1=49K@g4LJNGj;a8kVxX8wBlz4*(_VoOld> z4GrrO!c}zT7i4MXjjpQ{krar{-1NV`uJQ&da=k>9=xF=KIh!yj;5(lm9Hrdm9&I69 zW5^0_+&NsQRk6r*7c~c^O{{cHZ!z=Lv)-{hZ)@G{XZZj{w_~{%Xak`aP0k^=OveZtOBSnaa=Oc)0qQcC~AN^ zABuj0*S~F*G9PpxIpgP&@3ym#JwmzGGvZTB(FTM}^C0Cc209 zjRGE2b(P{~q})$?)DWcey z$j~UOjNj)U`)9uLEOITYAZideLl{@P4?_`%_7-{yOqv(vX?1-ql1deOanEmFZcx`n z!9O@FDTz3{_^w_rSkiSW29JbhdHiP)rQxq;+az5r5ZoJThoqycC4a-)!{J3Xk^atO z-d_9{E2~CN-n{s8v;LQVSD!2jygUB;<(FUHzZ=AV@83Q8NBs9U`9r8MkXKrz<&B06 z63P8E7YJ6$TzOWYx5d3fBnkhdpA?IQdPhYl2mQ=H2ez2y)pk}w20yGo;}s>o(otF0 z8C(oz}lm9PPNqBlS_D39!<@+Q~lsTFm9pp>$#v!-(hDc0M7x6;m|x@DX9^$S(Jjx zJHT;Z_Eda%p^4D9xGa#kAllC98nhbbxZT{|x(()CRCaBM$f=Sx2Gs5l*EkMoSkR+fpI5%RY0!HhnzV}PnyHZ)c?2VDC+HynC*OKgfB90nZR-)2uV2{F2Rbo~u@Fe^ zDz~JY@Wcs!K48X*Xe*j+u1?U+I%bVqpc{s0P_l%sAerSFhs_h zzw@z)ct1a{>&;8R^mU-Lm`t$9Bt;Aicr`p zE5+KQY*zaja7gpucqOT^NocD-FD`TYUOKhFCb&+Uj7D;5(gT}qMcj?;%?8-&%NsTQ zg$hH-fOoO~v874pL=?-jBQsCH)s4kKU>)>XRep`5dIygHmh0DXU4b7Rp(EhFS-yaO z4ly~7U}|f||Cxf_MTP8_#YP%%c^2G+##(AEdG^A(+=Y9HnfPZK7+x>%>_Uz=1z~%@ zTDgYflC^n_(F(qPaQ3mYjcV3uOii{=s=-Vz>c$%AuqC~b8qhe*!Gh(Df$ z`qxRxk)uahFHwl_8#Ob7Rk|+oaak{k4q^HuZy|J({6r8mPOzw60&>AxFKu9uP;Yi% z>ibBLZayDDZ2^=cq8>uR_@=3U0nka0g)E2x34n@s1<6mWI-q-#u7HDDtSvcqjh5hA zeJi>N&7^^%MZ_^nq#gFFIN~nBsZSi^;LuZ<#ynAXjLgBAtGDgEUJK+E9+-sDUKNWnHHQXfJK}el#4jU}Ur!oS#1>vnXNr4~hRFzX@_OHZWw{#>fhPY39Po?p7~e zpI@jSUS1G629ZdS$MTL?7@l;E3fEMO1SYKw zoR*RUv{+1h_ods0rKop*Yn>PQ$C!8hcNf>zwPc!C;=m|G`!TTL;dk#f^yMqcI z$BK5f%FFD{qHf-h@Qj=`=(H_=G}fL_Ay!jd6(Bpp zgxH87&ht|i%pT+&=KG5vWVo7bHSo53(rbnzriS+NH9aHf_!b9-`ZZL9J8yqzCp@0+ zho9jxdD#!-dQ*mpQ>@v zis3ZBzuU>E8h;mm5tvP6X-?H*+hEo@W*}a}kql-*z*r{&kwea2O3)}4ykEpdc}w0o z$0|WRywy~xMLyv#;IykTp0?mDAIZps2Z80ThMrs(I_Kg&2&s?}3BfR|-(%)wcoGaE zjiv~d?-N*1nITCrcTKN~HG@G(382N+oZ`t>w@?NJr+F-YHoH+jLpd0M?6BE|sGqDn z4Zv)tG%y%m189qD(y+4kiU%`z$N%1UyU!9IL{nW_Dn`Z>oH;@2wT97@${OG^#{$zk zRzEo0aB0Jf?H3O3dyrPPSoYMjijbt+Xk(9VG-dmclc z11x4pD`iZ7U0QE#dYNnW<H4^3S4t+0Dg zimuq5BL4^yHks%`P@6`O3uLAoUrP_99Gtr^6$^=NFeDe;n9HT#&J_lqpm>iYu=&3mhjM(9^ zqq}0CN4N!c8l!u>oNe6xcXO;Q@@la}pVg35>D!^<3!P)k9^VVb_$T{T_tm$4E#L5w zU+W)#^ZVZhxgAKrGtxl4N>6U8sEQzISq*|RO-F%Q#tM8MT=RVcy zEc}7f*ix<1B&vv0t!ABHd_A6O1)y$0#3;S%yr{;b$ju!kbM(1iIK7^5mg1jkCEZt&tA+#3Iz(a1!8u ze2c)!$p6-4YA_Z2OmJ_15%M@plPFj)aM?qfMBkd1h?wnmk57MyFptB8WrN^2y4mET zGQY}8wtBPz{OuAOfXvBFna*#ePW6C4d&+zX<+f|3KB>V?g)F;VKFCnQl}elWs%Z0h zyKT}^%>W)eg@|vPzGd{FL@nF31rJJp)U4d*o2DShx2z>TpLTBXeerD}Hoh>X?yLus zVEp$^BEv2&yBY2cC~AddzO?+t+cWP`{ksEW8VBChq;B!<3=k;HhCCj?3J*BfV>xue z7WPu-I%IWdb|Dq2Y-4>q5Jpu!@izF6KP75+AL5mTk<+@VIYc88K?3tYa_HlK0l=c@ zNldKV-WBm0KQy(2KN`M7tfp}Wmg_*A3eIlRZY@)ORn%ME&tG8g-CCyE{<=%K8A(B# zJad?EI~h+OJ>j=4M)+PvB>q@F!d--Sy(1hC(nN-rD}1SsypXOaiUO~b5nC0ICvY=y z71^G2Pre|S_&QTMk#A0`EPoq+99PoPE8@jlH<;3++t)e^OGrVcq1OO^4N{vhCU{;f ztF)Y`6Z?2)|*HjEF>~TF2|UE72#Sm`Egk0Y5XLAdGv6WIC=>gTMVz&*oLO>&d-$cjpymo8R8OTcpB{ruXRm zE#hV`qrV#xvZ83e&)Zv~?R?c9pZi{-^Oi$Qr!R9_%lm!uhbcY8`e;D-3yNWo9SpYElks9)oJ<~__8A0~Hy4zFp)f zz#9ftyj2FqiF)6E|9xQEu788~&4`j^zXh=KZQPQ7wA-f(aMFE)G=t9jk9T$8xaWKG zw{6esk9UhLqCEUtb^?l2-asnW$G*O(eWDirRoy8+gH<$#IF*xzhgj)O_A0!eR6Q=f**gYQ;71`0M1&B#sQ7&uX)O#N# zv(F*oucKmr{u)P^eP6zSnz4_YsQ2$z{`RAH)vm*uSH$IXwb-9F619tXxxZB?>7gX{NTZQ|Z??@+Y$$g9_K2j*ZhKoKm_=G= zL*P}A&|wu)Q-{Lb(N^)|mD@8%_WUA_{d~IU_AZI4F?4tQp3&ftM);cxng&zf(V3uc zyVoS{3pMB+!O*lOm*Rra80R!D6QmgWCQ5rOr>BF5e$9~qFqhRCBJ=m|%&6OdLE6cA?mkoeV5*6QYXf=y<&9-4{LetUGu)@!d-nI0DKTfp8v#SNEI` zGZTM<^c_ySoP0n6%6m?ZEok56e%|k$nrGO5rla0AReGLRX;ajlIp=_9=lWIJ6i6Y^ zIdy&ToOgNM|0Ywu)6Csl;oagxeJ?NK-VWD2+>IX}no(4K!tw9h0@d;9Wj<|ZDmv9H zvBcQ-K7m*QW`45Yd;jABwSMqZ_~;x_hx`n;dA(leRhDNbob%AP8h$+J+$?Xv=zLLs zE&rHqA`?~B)uWv^$O?;;7X_=U?+$0BL~fbepkd_KNM;Qjl(tR&7>e|RiHju=&cocn z9c!n5+5~h0sLQiZGs)Qw85uhn5%csaPfg9@&eikRS^^qU?~C?k*TZtpCs;P`I@Mqu zYlh0fc8s^F^9lonROb~nXr$09a=@TsLa&Irm-?=bQs5G=UW<+Eykg>pD!u&b04nh6 z=v*xEN