diff --git a/.changeset/fail-started-agent-runs.md b/.changeset/fail-started-agent-runs.md new file mode 100644 index 0000000000..d0a86f8ff7 --- /dev/null +++ b/.changeset/fail-started-agent-runs.md @@ -0,0 +1,5 @@ +--- +'@electric-ax/agents-runtime': patch +--- + +Mark newly-started agent runs as failed when a wake handler errors before ending them, preventing chat UIs from showing "Thinking" indefinitely after runtime failures. diff --git a/.changeset/fix-deferred-pull-wakes.md b/.changeset/fix-deferred-pull-wakes.md new file mode 100644 index 0000000000..961199ed32 --- /dev/null +++ b/.changeset/fix-deferred-pull-wakes.md @@ -0,0 +1,8 @@ +--- +'@electric-ax/agents-runtime': patch +'@electric-ax/agents-server': patch +--- + +Fix child wake delivery so same-stream wake trigger notifications are queued while an active claim is already running, ensuring the runner checks for all pending wake rows again after the active work drains. + +Refactor the server wake registry to use TanStack DB collections and optimistic actions over `wake_registrations`, removing the manual ShapeStream-backed registration cache and stale-cache reload fallback. diff --git a/.changeset/large-live-batches.md b/.changeset/large-live-batches.md new file mode 100644 index 0000000000..5a5128f468 --- /dev/null +++ b/.changeset/large-live-batches.md @@ -0,0 +1,5 @@ +--- +"@electric-ax/agents-runtime": patch +--- + +Avoid stack overflows when processing large live StreamDB batches. diff --git a/.changeset/reconnect-wake-stream-after-sleep.md b/.changeset/reconnect-wake-stream-after-sleep.md new file mode 100644 index 0000000000..cdf01622bb --- /dev/null +++ b/.changeset/reconnect-wake-stream-after-sleep.md @@ -0,0 +1,5 @@ +--- +'@electric-ax/agents-runtime': patch +--- + +Reconnect pull-wake streams after the runner resumes from sleep or a long event-loop stall so desktop agents keep receiving wake notifications. diff --git a/.changeset/reliable-repeated-wakes-and-desktop-restart.md b/.changeset/reliable-repeated-wakes-and-desktop-restart.md new file mode 100644 index 0000000000..defc06e0ce --- /dev/null +++ b/.changeset/reliable-repeated-wakes-and-desktop-restart.md @@ -0,0 +1,11 @@ +--- +'@electric-ax/agents': patch +'@electric-ax/agents-runtime': patch +'@electric-ax/agents-desktop': patch +--- + +Preserve every deferred same-stream pull-wake notification instead of coalescing later generations, so repeated child completions reliably wake their parent. + +Make desktop runtime startup failures observable and retryable even when they occur before the built-in runtime instance starts, allowing configured agents to recover after relaunch. + +Reconnect the pull-wake Durable Streams subscription when Electron reports system resume, preserving active agent work while replacing stale long-poll connections. diff --git a/.changeset/upgrade-pi-agent-core-openai.md b/.changeset/upgrade-pi-agent-core-openai.md new file mode 100644 index 0000000000..f5c4048a98 --- /dev/null +++ b/.changeset/upgrade-pi-agent-core-openai.md @@ -0,0 +1,12 @@ +--- +'@electric-ax/agents': patch +'@electric-ax/agents-runtime': patch +'@electric-ax/agents-server': patch +'@electric-ax/agents-server-conformance-tests': patch +'@electric-ax/agents-server-ui': patch +'@electric-ax/agents-desktop': patch +'@electric-ax/agents-mobile': patch +'electric-ax': patch +--- + +Update the agents runtime to the renamed Earendil Works pi packages and latest OpenAI/Codex model catalog. Preserve custom provider id compatibility, move built-in Codex defaults to GPT-5.6 models, and remove deprecated direct pi-agent-core dependencies from agents examples. diff --git a/docs/superpowers/plans/2026-06-19-wake-registry-tanstack-db.md b/docs/superpowers/plans/2026-06-19-wake-registry-tanstack-db.md new file mode 100644 index 0000000000..fc67c1a38f --- /dev/null +++ b/docs/superpowers/plans/2026-06-19-wake-registry-tanstack-db.md @@ -0,0 +1,1525 @@ +# Wake Registry TanStack DB Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace `WakeRegistry`'s manual Postgres row cache and ShapeStream sync with TanStack DB collections, optimistic actions, `queryOnce`, and collection effects. + +**Architecture:** `WakeRegistry` will own one TanStack DB collection of `wake_registrations` rows. Runtime uses `electricCollectionOptions` over the Postgres table; unit tests use a local-only collection. All mutations go through `createOptimisticAction`; each action persists to Postgres in its `mutationFn`, then awaits the Electric collection txid. Evaluation reads with `queryOnce`, and timeout wake timers are driven by `createEffect` over collection rows. + +**Tech Stack:** TypeScript, Vitest, Drizzle ORM, Postgres, Electric `@electric-sql/client`, TanStack DB `@tanstack/db`, TanStack Electric collection `@tanstack/electric-db-collection`. + +## Global Constraints + +- Runtime requires Electric; there is no no-Electric Postgres fallback. +- No `wake_registrations` schema migration. +- No custom TanStack DB adapter. +- No pull-wake runner changes. +- No changes to persisted wake row payloads. +- All public `WakeRegistry` mutations invoke optimistic actions. +- Bulk unregister operations are one action per domain intent. +- Wake evaluation uses `queryOnce` over the collection. +- Timeout wake timers are driven by `createEffect` over collection rows. +- `WakeRegistry` must not instantiate `Shape` or `ShapeStream` directly. +- Before running package typecheck/tests in this worktree, run `pnpm install` from the repository root if workspace links are missing. + +--- + +## File Structure + +- `packages/agents-server/package.json` + - Add runtime dependencies: `@tanstack/db` and `@tanstack/electric-db-collection`. +- `packages/agents-server/src/wake-registry.ts` + - Main refactor. Owns TanStack DB collection, actions, query-based evaluation, and timeout effects. +- `packages/agents-server/src/entity-manager.ts` + - Await async `wakeRegistry.evaluate(...)`, remove reload-on-miss fallback, and require `startSync(...)` for runtime startup. +- `packages/agents-server/src/host.ts` + - Require `electricUrl` for host startup; remove `loadRegistrations()` fallback. +- `packages/agents-server/src/standalone-runtime.ts` + - Keep passing Electric URL into `rebuildWakeRegistry(...)`; missing Electric URL should fail through `EntityManager.rebuildWakeRegistry(...)`. +- `packages/agents-server/test/wake-registry.test.ts` + - Convert unit tests to local-only collection startup and async evaluation. +- `packages/agents-server/test/wake-registry-sync.test.ts` + - Replace manual ShapeStream mock tests with Postgres + Electric collection integration tests or remove if covered elsewhere. +- `packages/agents-server/test/server-start.test.ts` + - Update mocked `WakeRegistry` API and assert missing Electric URL fails when applicable. +- `.changeset/fix-deferred-pull-wakes.md` + - Update if package dependencies/behavior summary changes. + +--- + +### Task 1: Add TanStack DB Dependencies and Local Collection Foundation + +**Files:** + +- Modify: `packages/agents-server/package.json:46-67` +- Modify: `packages/agents-server/src/wake-registry.ts:1-180` +- Test: `packages/agents-server/test/wake-registry.test.ts:32-61` + +**Interfaces:** + +- Consumes: existing `WakeRegistration`, `WakeEvalResult`, `DrizzleDB`, `DEFAULT_TENANT_ID`. +- Produces: + - `interface WakeRegistrationCollectionRow` + - `WakeRegistry.startLocalForTests(): Promise` + - `WakeRegistry.evaluate(...): Promise>` + - `WakeRegistry.requireCollection(): Collection` + +- [ ] **Step 1: Add dependencies** + +Add these dependencies to `packages/agents-server/package.json` under `dependencies`: + +```json +"@tanstack/db": "^0.6.7", +"@tanstack/electric-db-collection": "^0.3.5" +``` + +Run from repo root: + +```bash +pnpm install +``` + +Expected: lockfile updates cleanly and package workspace links are materialized. + +- [ ] **Step 2: Write failing local-collection evaluation test** + +In `packages/agents-server/test/wake-registry.test.ts`, add this test near the first simple `Wake Registry` tests. This test intentionally uses a local collection helper that does not exist yet: + +```ts +it(`evaluates registrations from local TanStack DB collection`, async () => { + const registry = new WakeRegistry(createMockDb()) + await registry.startLocalForTests() + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/c1`, + condition: `runFinished`, + oneShot: false, + }) + + const results = await registry.evaluate(`/child/c1`, { + type: `run`, + key: `run-1`, + value: { status: `completed` }, + headers: { operation: `update` }, + }) + + expect(results).toHaveLength(1) + expect(results[0]!.subscriberUrl).toBe(`/parent/p1`) + expect(results[0]!.registrationDbId).toBe(1) + expect(results[0]!.sourceEventKey).toBe(`update:run-1`) +}) +``` + +- [ ] **Step 3: Run the focused test to verify it fails** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/wake-registry.test.ts -t "evaluates registrations from local TanStack DB collection" --run +``` + +Expected: FAIL with TypeScript/runtime error that `startLocalForTests` does not exist or `evaluate` is not awaitable yet. + +- [ ] **Step 4: Add collection foundation alongside the legacy runtime cache** + +Task 1 is an incremental local-only foundation. Keep the existing `registrationCache`, `ShapeStream`, `startSync(...)`, and runtime cache code in place until Task 4 removes them. In `packages/agents-server/src/wake-registry.ts`, add TanStack DB imports at the top while keeping imports that are still needed by the legacy runtime path: + +```ts +import { + and as dbAnd, + createCollection, + createOptimisticAction, + eq as dbEq, + localOnlyCollectionOptions, + queryOnce, +} from '@tanstack/db' +import { and, eq } from 'drizzle-orm' +import { wakeRegistrations } from './db/schema.js' +import { DEFAULT_TENANT_ID } from './tenant.js' +import type { Collection } from '@tanstack/db' +import type { DrizzleDB } from './db/index.js' +``` + +Do not remove the existing `ShapeStream`, `serverLog`, `electricUrlWithPath`, `Row`, or `Value` imports in Task 1 if they are still used by the legacy runtime path. Task 4 removes manual ShapeStream runtime sync. + +Add this row type and local test state after `WakeDebounceCallback`: + +```ts +interface WakeRegistrationCollectionRow { + id: number + tenantId: string + subscriberUrl: string + sourceUrl: string + condition: WakeRegistration[`condition`] + debounceMs: number + timeoutMs: number + oneShot: boolean + timeoutConsumed: boolean + includeResponse: boolean + manifestKey: string | null + createdAt: Date +} + +type WakeRegistryMode = `unstarted` | `local-test` | `electric` +``` + +In `WakeRegistry`, add these fields next to the existing legacy cache/sync fields. Do not remove the old cache fields in Task 1 because the existing runtime `startSync(...)` path still uses them until Task 4. + +```ts +private registrationsCollection: Collection | null = null +private mode: WakeRegistryMode = `unstarted` +private nextLocalId = 1 +``` + +Add these helpers inside `WakeRegistry`: + +```ts +private requireCollection(): Collection { + if (!this.registrationsCollection) { + throw new Error(`WakeRegistry has not been started`) + } + return this.registrationsCollection +} + +async startLocalForTests(): Promise { + if (this.registrationsCollection) return + this.mode = `local-test` + this.registrationsCollection = createCollection( + localOnlyCollectionOptions({ + id: `wake-registrations-local:${this.tenantId ?? `all`}`, + getKey: (row) => row.id, + initialData: [], + }) + ) + await this.registrationsCollection.preload() +} + +private allocateLocalId(): number { + return this.nextLocalId++ +} + +private normalizeRegistration( + reg: WakeRegistration, + tenantId: string, + id: number +): WakeRegistrationCollectionRow { + return { + id, + tenantId, + subscriberUrl: reg.subscriberUrl, + sourceUrl: reg.sourceUrl, + condition: reg.condition, + debounceMs: reg.debounceMs ?? 0, + timeoutMs: reg.timeoutMs ?? 0, + oneShot: reg.oneShot, + timeoutConsumed: false, + includeResponse: reg.includeResponse !== false, + manifestKey: reg.manifestKey ?? null, + createdAt: new Date(), + } +} +``` + +- [ ] **Step 5: Implement local register action and async queryOnce evaluation** + +Add this action field and initializer inside `WakeRegistry`. The local action is enough for this task; runtime persistence is added later. + +```ts +private registerAction = createOptimisticAction({ + onMutate: (row) => { + this.requireCollection().insert(row) + }, + mutationFn: async () => { + if (this.mode === `local-test`) return + throw new Error(`WakeRegistry registerAction runtime persistence is not initialized`) + }, +}) +``` + +Replace `register(reg: WakeRegistration): Promise` with this minimal local implementation: + +```ts +async register(reg: WakeRegistration): Promise { + const tenantId = this.resolveTenantId(reg.tenantId) + const id = this.mode === `local-test` ? this.allocateLocalId() : this.allocateLocalId() + const tx = this.registerAction(this.normalizeRegistration(reg, tenantId, id)) + await tx.isPersisted.promise +} +``` + +Replace the start of `evaluate(...)` with an async collection query. Keep the existing matching/debounce/result code, but iterate over `regs` from `queryOnce` instead of the old `registrationCache` array: + +```ts +async evaluate( + sourceUrl: string, + event: Record, + tenantId?: string +): Promise> { + const resolvedTenantId = this.resolveTenantId(tenantId) + const regs = await queryOnce((q) => + q + .from({ reg: this.requireCollection() }) + .where(({ reg }) => + dbAnd(dbEq(reg.tenantId, resolvedTenantId), dbEq(reg.sourceUrl, sourceUrl)) + ) + ) + if (regs.length === 0) return [] + + const results: Array = [] + const oneShotRows: Array = [] + + for (const reg of regs) { + const match = this.matchCondition(reg, event) + if (!match) continue + + const timerKey = this.registrationKey(reg) + const timeoutTimer = this.timeoutTimers.get(timerKey) + if (timeoutTimer) { + clearTimeout(timeoutTimer) + this.timeoutTimers.delete(timerKey) + void this.markTimeoutConsumed(reg.id, reg.tenantId) + } + + if (reg.debounceMs > 0) { + const buffer = this.debounceBuffers.get(timerKey) ?? [] + buffer.push(match.change) + this.debounceBuffers.set(timerKey, buffer) + if (match.runFinishedStatus) { + this.debounceRunStatus.set(timerKey, match.runFinishedStatus) + } + const existing = this.debounceTimers.get(timerKey) + if (existing) clearTimeout(existing) + const timer = setTimeout(() => { + this.debounceTimers.delete(timerKey) + const flushed = this.debounceBuffers.get(timerKey) + if (flushed && flushed.length > 0) { + this.debounceBuffers.delete(timerKey) + const runStatus = this.debounceRunStatus.get(timerKey) + this.debounceRunStatus.delete(timerKey) + this.deliverDebounce({ + tenantId: reg.tenantId, + subscriberUrl: reg.subscriberUrl, + registrationDbId: reg.id, + sourceEventKey: flushed[flushed.length - 1]!.key, + wakeMessage: { + source: sourceUrl, + timeout: false, + changes: flushed, + }, + runFinishedStatus: runStatus, + includeResponse: reg.includeResponse, + }) + } + }, reg.debounceMs) + this.debounceTimers.set(timerKey, timer) + } else { + results.push({ + tenantId: reg.tenantId, + subscriberUrl: reg.subscriberUrl, + registrationDbId: reg.id, + sourceEventKey: wakeSourceEventId(event), + wakeMessage: { + source: sourceUrl, + timeout: false, + changes: [match.change], + }, + runFinishedStatus: match.runFinishedStatus, + includeResponse: reg.includeResponse, + }) + } + + if (reg.oneShot) oneShotRows.push(reg) + } + + for (const reg of oneShotRows) { + this.clearRegistrationState(reg) + this.timeoutDelivered.delete(reg.id) + this.requireCollection().delete(reg.id) + } + + return results +} +``` + +Update `registrationKey`, `clearRegistrationState`, `matchCondition`, and timeout helper parameter types from `CachedWakeRegistration` to `WakeRegistrationCollectionRow` where needed: + +```ts +private registrationKey(reg: WakeRegistrationCollectionRow): string +private clearRegistrationState(reg: WakeRegistrationCollectionRow): void +private matchCondition(reg: WakeRegistrationCollectionRow, event: Record): ... +``` + +- [ ] **Step 6: Run the focused test to verify it passes** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/wake-registry.test.ts -t "evaluates registrations from local TanStack DB collection" --run +``` + +Expected: PASS. + +- [ ] **Step 7: Commit** + +```bash +git add packages/agents-server/package.json pnpm-lock.yaml packages/agents-server/src/wake-registry.ts packages/agents-server/test/wake-registry.test.ts +git commit -m "Introduce TanStack DB wake registry collection" +``` + +--- + +### Task 2: Convert All Unit-Test Registry Mutations to Optimistic Actions + +**Files:** + +- Modify: `packages/agents-server/src/wake-registry.ts:298-530, 730-910` +- Modify: `packages/agents-server/test/wake-registry.test.ts` + +**Interfaces:** + +- Consumes: `WakeRegistrationCollectionRow`, `WakeRegistry.requireCollection()`, async `WakeRegistry.evaluate(...)`. +- Produces: + - `unregisterByManifestKeyAction` + - `unregisterBySubscriberAction` + - `unregisterBySourceAction` + - `unregisterBySubscriberAndSourceAction` + - `markTimeoutConsumedAction` + - `consumeMatchedRegistrationsAction` + +- [ ] **Step 1: Write failing tests for bulk unregister actions** + +Add these tests to `packages/agents-server/test/wake-registry.test.ts` near existing unregister tests: + +```ts +it(`unregisterBySource removes all matching local collection rows`, async () => { + const registry = new WakeRegistry(createMockDb()) + await registry.startLocalForTests() + + await registry.register({ + subscriberUrl: `/parent/a`, + sourceUrl: `/source/1`, + condition: { on: `change` }, + oneShot: false, + }) + await registry.register({ + subscriberUrl: `/parent/b`, + sourceUrl: `/source/1`, + condition: { on: `change` }, + oneShot: false, + }) + await registry.register({ + subscriberUrl: `/parent/c`, + sourceUrl: `/source/2`, + condition: { on: `change` }, + oneShot: false, + }) + + await registry.unregisterBySource(`/source/1`) + + expect( + await registry.evaluate(`/source/1`, { + type: `texts`, + key: `t1`, + value: {}, + headers: { operation: `insert` }, + }) + ).toHaveLength(0) + expect( + await registry.evaluate(`/source/2`, { + type: `texts`, + key: `t2`, + value: {}, + headers: { operation: `insert` }, + }) + ).toHaveLength(1) +}) + +it(`oneShot match removes row before a second immediate evaluation`, async () => { + const registry = new WakeRegistry(createMockDb()) + await registry.startLocalForTests() + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/c1`, + condition: `runFinished`, + oneShot: true, + }) + + const event = { + type: `run`, + key: `run-1`, + value: { status: `completed` }, + headers: { operation: `update` }, + } + + expect(await registry.evaluate(`/child/c1`, event)).toHaveLength(1) + expect(await registry.evaluate(`/child/c1`, event)).toHaveLength(0) +}) +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/wake-registry.test.ts -t "unregisterBySource removes|oneShot match removes" --run +``` + +Expected: FAIL if unregisters or one-shot cleanup still use old cache/DB paths. + +- [ ] **Step 3: Add query helpers for matching rows** + +Add these helpers to `WakeRegistry`: + +```ts +private async rowsByPredicate( + predicate: (row: WakeRegistrationCollectionRow) => boolean +): Promise> { + const rows = await queryOnce((q) => q.from({ reg: this.requireCollection() })) + return rows.filter(predicate) +} + +private async rowsForSource( + tenantId: string, + sourceUrl: string +): Promise> { + return await queryOnce((q) => + q + .from({ reg: this.requireCollection() }) + .where(({ reg }) => + dbAnd(dbEq(reg.tenantId, tenantId), dbEq(reg.sourceUrl, sourceUrl)) + ) + ) +} +``` + +This task may use `rowsByPredicate` for local actions because action `onMutate` must be synchronous. Capture the rows before invoking the action and pass their ids into the action input. + +- [ ] **Step 4: Implement unregister optimistic actions** + +Add action input types near `WakeRegistryMode`: + +```ts +type DeleteRowsInput = { + rows: Array + persist: + | { + kind: `manifestKey` + tenantId: string + subscriberUrl: string + manifestKey: string + } + | { + kind: `subscriber` + tenantId: string + subscriberUrl: string + } + | { + kind: `source` + tenantId: string + sourceUrl: string + } + | { + kind: `subscriberAndSource` + tenantId: string + subscriberUrl: string + sourceUrl: string + } + | { + kind: `oneShot` + } +} +``` + +Add one shared delete action: + +```ts +private deleteRowsAction = createOptimisticAction({ + onMutate: ({ rows }) => { + const collection = this.requireCollection() + for (const row of rows) { + this.clearRegistrationState(row) + this.timeoutDelivered.delete(row.id) + collection.delete(row.id) + } + }, + mutationFn: async ({ persist }) => { + if (this.mode === `local-test` || persist.kind === `oneShot`) return + throw new Error(`WakeRegistry deleteRowsAction runtime persistence is not initialized`) + }, +}) +``` + +Replace unregister methods with wrappers that query rows, invoke the action, and await persistence: + +```ts +async unregisterBySource(sourceUrl: string, tenantId?: string): Promise { + const resolvedTenantId = this.resolveTenantId(tenantId) + const rows = await this.rowsForSource(resolvedTenantId, sourceUrl) + const tx = this.deleteRowsAction({ + rows, + persist: { kind: `source`, tenantId: resolvedTenantId, sourceUrl }, + }) + await tx.isPersisted.promise +} +``` + +Implement the other three unregister methods with the same pattern: + +```ts +async unregisterBySubscriber(subscriberUrl: string, tenantId?: string): Promise { + const resolvedTenantId = this.resolveTenantId(tenantId) + const rows = await this.rowsByPredicate( + (row) => row.tenantId === resolvedTenantId && row.subscriberUrl === subscriberUrl + ) + const tx = this.deleteRowsAction({ + rows, + persist: { kind: `subscriber`, tenantId: resolvedTenantId, subscriberUrl }, + }) + await tx.isPersisted.promise +} + +async unregisterByManifestKey( + subscriberUrl: string, + manifestKey: string, + tenantId?: string +): Promise { + const resolvedTenantId = this.resolveTenantId(tenantId) + const rows = await this.rowsByPredicate( + (row) => + row.tenantId === resolvedTenantId && + row.subscriberUrl === subscriberUrl && + row.manifestKey === manifestKey + ) + const tx = this.deleteRowsAction({ + rows, + persist: { kind: `manifestKey`, tenantId: resolvedTenantId, subscriberUrl, manifestKey }, + }) + await tx.isPersisted.promise +} + +async unregisterBySubscriberAndSource( + subscriberUrl: string, + sourceUrl: string, + tenantId?: string +): Promise { + const resolvedTenantId = this.resolveTenantId(tenantId) + const rows = await this.rowsByPredicate( + (row) => + row.tenantId === resolvedTenantId && + row.subscriberUrl === subscriberUrl && + row.sourceUrl === sourceUrl + ) + const tx = this.deleteRowsAction({ + rows, + persist: { kind: `subscriberAndSource`, tenantId: resolvedTenantId, subscriberUrl, sourceUrl }, + }) + await tx.isPersisted.promise +} +``` + +- [ ] **Step 5: Implement mark-timeout-consumed action** + +Add this action: + +```ts +private markTimeoutConsumedAction = createOptimisticAction<{ + row: WakeRegistrationCollectionRow +}>({ + onMutate: ({ row }) => { + this.requireCollection().update(row.id, (draft) => { + draft.timeoutConsumed = true + }) + }, + mutationFn: async () => { + if (this.mode === `local-test`) return + throw new Error(`WakeRegistry markTimeoutConsumedAction runtime persistence is not initialized`) + }, +}) +``` + +Replace `markTimeoutConsumed(dbId, tenantId)` with: + +```ts +private async markTimeoutConsumed(dbId: number, tenantId: string): Promise { + const row = await queryOnce((q) => + q + .from({ reg: this.requireCollection() }) + .where(({ reg }) => dbAnd(dbEq(reg.tenantId, tenantId), dbEq(reg.id, dbId))) + .findOne() + ) + if (!row) return + const tx = this.markTimeoutConsumedAction({ row }) + await tx.isPersisted.promise +} +``` + +- [ ] **Step 6: Route one-shot cleanup through the shared action** + +In `evaluate(...)`, replace direct `collection.delete(...)` one-shot cleanup with: + +```ts +if (oneShotRows.length > 0) { + const tx = this.deleteRowsAction({ + rows: oneShotRows, + persist: { kind: `oneShot` }, + }) + void tx.isPersisted.promise.catch((error) => { + console.warn(`[wake-registry] failed to persist one-shot cleanup:`, error) + }) +} +``` + +- [ ] **Step 7: Run focused tests** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/wake-registry.test.ts -t "unregisterBySource removes|oneShot match removes|evaluates registrations from local" --run +``` + +Expected: PASS. + +- [ ] **Step 8: Commit** + +```bash +git add packages/agents-server/src/wake-registry.ts packages/agents-server/test/wake-registry.test.ts +git commit -m "Move wake registry mutations to optimistic actions" +``` + +--- + +### Task 3: Drive Timeout Wakes with TanStack DB Effects + +**Files:** + +- Modify: `packages/agents-server/src/wake-registry.ts:104-177, 562-728` +- Modify: `packages/agents-server/test/wake-registry.test.ts` + +**Interfaces:** + +- Consumes: `WakeRegistrationCollectionRow`, local collection startup, optimistic timeout action. +- Produces: + - `private registrationsEffect: { dispose(): Promise } | null` + - `private startRegistrationEffect(): void` + - timeout timers synchronized from collection rows via `createEffect` + +- [ ] **Step 1: Write failing timeout effect tests** + +Add this test near existing timeout tests: + +```ts +it(`timeout effect delivers timeout wake once and marks row consumed`, async () => { + const registry = new WakeRegistry(createMockDb()) + await registry.startLocalForTests() + const delivered: Array = [] + registry.setTimeoutCallback((result) => delivered.push(result)) + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/c1`, + condition: `runFinished`, + oneShot: false, + timeoutMs: 25, + }) + + await new Promise((resolve) => setTimeout(resolve, 80)) + + expect(delivered).toHaveLength(1) + expect(delivered[0]!.wakeMessage.timeout).toBe(true) + + await new Promise((resolve) => setTimeout(resolve, 80)) + expect(delivered).toHaveLength(1) +}) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/wake-registry.test.ts -t "timeout effect delivers" --run +``` + +Expected: FAIL until the collection effect starts timers from row enter/update events. + +- [ ] **Step 3: Import `createEffect` and add effect field** + +Update imports from `@tanstack/db`: + +```ts +import { + and as dbAnd, + createCollection, + createEffect, + createOptimisticAction, + eq as dbEq, + localOnlyCollectionOptions, + queryOnce, +} from '@tanstack/db' +``` + +Add field: + +```ts +private registrationsEffect: { dispose(): Promise } | null = null +``` + +- [ ] **Step 4: Start the effect after collection preload** + +Add this method: + +```ts +private startRegistrationEffect(): void { + if (this.registrationsEffect) return + const collection = this.requireCollection() + this.registrationsEffect = createEffect({ + query: (q) => q.from({ reg: collection }), + skipInitial: false, + onEnter: ({ value }) => { + this.syncTimeoutTimer(value) + }, + onUpdate: ({ value }) => { + this.syncTimeoutTimer(value) + }, + onExit: ({ value }) => { + this.clearRegistrationState(value) + this.timeoutDelivered.delete(value.id) + }, + }) +} +``` + +Call it at the end of `startLocalForTests()` after `await this.registrationsCollection.preload()`: + +```ts +this.startRegistrationEffect() +``` + +- [ ] **Step 5: Update timeout helpers to collection row type** + +Replace old timeout helper signatures with: + +```ts +private startTimeoutTimer(reg: WakeRegistrationCollectionRow): void +private startTimeoutTimerWithDuration(reg: WakeRegistrationCollectionRow, durationMs: number): void +private syncTimeoutTimer(reg: WakeRegistrationCollectionRow): void +private deliverTimeoutForRegistration(reg: WakeRegistrationCollectionRow): void +private timeoutWakeResult(reg: WakeRegistrationCollectionRow): WakeEvalResult +``` + +Use `reg.id` instead of `dbId` inside these helpers. The key bodies should be: + +```ts +private startTimeoutTimer(reg: WakeRegistrationCollectionRow): void { + if (reg.timeoutMs <= 0) return + this.startTimeoutTimerWithDuration(reg, reg.timeoutMs) +} + +private startTimeoutTimerWithDuration( + reg: WakeRegistrationCollectionRow, + durationMs: number +): void { + const timerKey = this.registrationKey(reg) + const timer = setTimeout(() => { + this.timeoutTimers.delete(timerKey) + this.deliverTimeoutForRegistration(reg) + }, durationMs) + this.timeoutTimers.set(timerKey, timer) +} + +private syncTimeoutTimer(reg: WakeRegistrationCollectionRow): void { + const timerKey = this.registrationKey(reg) + + if (reg.timeoutConsumed || reg.timeoutMs <= 0) { + this.clearTimeoutState(timerKey) + return + } + + if (this.timeoutTimers.has(timerKey)) return + + const remaining = reg.createdAt.getTime() + reg.timeoutMs - Date.now() + if (remaining > 0) { + this.startTimeoutTimerWithDuration(reg, remaining) + return + } + + if (this.timeoutDelivered.has(reg.id)) return + this.deliverTimeoutForRegistration(reg) +} + +private deliverTimeoutForRegistration(reg: WakeRegistrationCollectionRow): void { + if (this.deliverTimeout(this.timeoutWakeResult(reg))) { + this.timeoutDelivered.add(reg.id) + void this.markTimeoutConsumed(reg.id, reg.tenantId).catch((error) => { + console.warn(`[wake-registry] failed to mark timeout consumed:`, error) + }) + } +} + +private timeoutWakeResult(reg: WakeRegistrationCollectionRow): WakeEvalResult { + return { + tenantId: reg.tenantId, + subscriberUrl: reg.subscriberUrl, + registrationDbId: reg.id, + sourceEventKey: `timeout`, + wakeMessage: { + source: reg.sourceUrl, + timeout: true, + changes: [], + }, + } +} +``` + +- [ ] **Step 6: Stop effect and clear timers on shutdown** + +Replace `stopSync()` with a general cleanup that still keeps the public method name for current call sites: + +```ts +async stopSync(): Promise { + await this.registrationsEffect?.dispose() + this.registrationsEffect = null + this.registrationsCollection = null + this.mode = `unstarted` + this.resetRuntimeState() +} + +private resetRuntimeState(): void { + for (const timer of this.debounceTimers.values()) clearTimeout(timer) + this.debounceTimers.clear() + this.debounceBuffers.clear() + this.debounceRunStatus.clear() + for (const timer of this.timeoutTimers.values()) clearTimeout(timer) + this.timeoutTimers.clear() + this.timeoutDelivered.clear() +} +``` + +- [ ] **Step 7: Run timeout tests** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/wake-registry.test.ts -t "timeout" --run +``` + +Expected: timeout tests PASS or fail only where assertions still assume old synchronous cache behavior. + +- [ ] **Step 8: Commit** + +```bash +git add packages/agents-server/src/wake-registry.ts packages/agents-server/test/wake-registry.test.ts +git commit -m "Drive wake timeouts from registry collection effects" +``` + +--- + +### Task 4: Implement Runtime Electric Collection and Postgres Txid Mutation Handlers + +**Files:** + +- Modify: `packages/agents-server/src/wake-registry.ts:1-360` +- Test: `packages/agents-server/test/wake-registry-sync.test.ts` + +**Interfaces:** + +- Consumes: local collection/action architecture from Tasks 1-3. +- Produces: + - Runtime `WakeRegistry.startSync(electricUrl, electricSecret?)` + - Electric collection with `snakeCamelMapper()` + - runtime `registerAction`, `deleteRowsAction`, `markTimeoutConsumedAction` persistence + - no direct `Shape` or `ShapeStream` usage in `WakeRegistry` + +- [ ] **Step 1: Write failing integration test for Electric-backed registry sync** + +Replace `packages/agents-server/test/wake-registry-sync.test.ts` with this integration test skeleton. It uses the existing managed test backend. + +```ts +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { eq } from 'drizzle-orm' +import { createDb } from '../src/db' +import { wakeRegistrations } from '../src/db/schema' +import { WakeRegistry } from '../src/wake-registry' +import { + TEST_ELECTRIC_URL, + TEST_POSTGRES_URL, + resetElectricAgentsTestBackend, +} from './test-backend' + +const db = createDb(TEST_POSTGRES_URL) + +describe(`WakeRegistry Electric collection sync`, () => { + beforeAll(async () => { + await resetElectricAgentsTestBackend() + }) + + afterAll(async () => { + await db.end?.() + }) + + it(`syncs a registered wake through Postgres and Electric`, async () => { + const registry = new WakeRegistry(db as any) + await registry.startSync(TEST_ELECTRIC_URL) + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/c1`, + condition: `runFinished`, + oneShot: false, + }) + + const rows = await db + .select() + .from(wakeRegistrations) + .where(eq(wakeRegistrations.sourceUrl, `/child/c1`)) + + expect(rows).toHaveLength(1) + + const results = await registry.evaluate(`/child/c1`, { + type: `run`, + key: `run-1`, + value: { status: `completed` }, + headers: { operation: `update` }, + }) + + expect(results).toHaveLength(1) + expect(results[0]!.registrationDbId).toBe(rows[0]!.id) + + await registry.stopSync() + }) +}) +``` + +If `createDb(TEST_POSTGRES_URL)` does not expose `end`, remove the `afterAll` block and follow the existing DB helper pattern in nearby integration tests. + +- [ ] **Step 2: Run integration test to verify it fails** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/wake-registry-sync.test.ts --run +``` + +Expected: FAIL because `startSync` still uses manual ShapeStream or runtime actions do not persist and await Electric txids yet. + +- [ ] **Step 3: Import Electric collection and column mapper** + +Update imports in `wake-registry.ts`: + +```ts +import { snakeCamelMapper } from '@electric-sql/client' +import { electricCollectionOptions } from '@tanstack/electric-db-collection' +import { sql } from 'drizzle-orm' +import { electricUrlWithPath } from './utils/electric-url.js' +import { serverLog } from './utils/log.js' +``` + +Keep Drizzle `and`/`eq` imports for mutation predicates: + +```ts +import { and, eq, sql } from 'drizzle-orm' +``` + +- [ ] **Step 4: Implement sequence id allocation and txid helper** + +Add these helpers to `WakeRegistry`: + +```ts +private async allocateRuntimeId(): Promise { + const rows = await this.db.execute(sql<{ id: string }>`select nextval('wake_registrations_id_seq')::text as id`) + const value = Array.isArray(rows) ? rows[0]?.id : (rows as any)[0]?.id + const id = Number(value) + if (!Number.isInteger(id)) { + throw new Error(`Failed to allocate wake registration id`) + } + return id +} + +private async currentTxid(): Promise { + const rows = await this.db.execute(sql<{ txid: string }>`select pg_current_xact_id()::xid::text as txid`) + const value = Array.isArray(rows) ? rows[0]?.txid : (rows as any)[0]?.txid + const txid = Number(value) + if (!Number.isInteger(txid)) { + throw new Error(`Failed to read Postgres transaction id`) + } + return txid +} +``` + +If `DrizzleDB.execute(...)` returns `{ rows }` rather than an array in this codebase, adapt the extraction to: + +```ts +const result = await this.db.execute(...) +const value = result.rows[0]?.txid +``` + +Use the actual shape observed by TypeScript/tests; keep the method returning `number`. + +- [ ] **Step 5: Implement Electric `startSync`** + +Replace old `startSync(...)` with an Electric collection that syncs rows but does not persist writes itself. Persistence belongs to the optimistic action `mutationFn` methods in later steps. + +```ts +async startSync(electricUrl: string, electricSecret?: string): Promise { + if (this.registrationsCollection) { + await this.registrationsCollection.preload() + return + } + + this.mode = `electric` + this.registrationsCollection = createCollection( + electricCollectionOptions({ + id: `wake-registrations:${this.tenantId ?? `all`}`, + getKey: (row) => row.id, + shapeOptions: { + url: electricUrlWithPath(electricUrl, `/v1/shape`).toString(), + params: { + table: `wake_registrations`, + ...(this.tenantId + ? { where: `tenant_id = ${sqlStringLiteral(this.tenantId)}` } + : {}), + ...(electricSecret ? { secret: electricSecret } : {}), + columns: [ + `id`, + `tenant_id`, + `subscriber_url`, + `source_url`, + `condition`, + `debounce_ms`, + `timeout_ms`, + `one_shot`, + `timeout_consumed`, + `include_response`, + `manifest_key`, + `created_at`, + ], + replica: `full`, + }, + parser: { + timestamptz: (value: string) => new Date(value), + }, + columnMapper: snakeCamelMapper(), + }, + }) + ) + + await this.registrationsCollection.preload() + this.startRegistrationEffect() +} +``` + +- [ ] **Step 6: Implement runtime persistence helpers** + +Add these helpers: + +```ts +private async persistInsert(row: WakeRegistrationCollectionRow): Promise { + const result = await this.db.transaction(async (tx) => { + await tx + .insert(wakeRegistrations) + .values({ + id: row.id, + tenantId: row.tenantId, + subscriberUrl: row.subscriberUrl, + sourceUrl: row.sourceUrl, + condition: row.condition, + debounceMs: row.debounceMs, + timeoutMs: row.timeoutMs, + oneShot: row.oneShot, + timeoutConsumed: row.timeoutConsumed, + includeResponse: row.includeResponse, + manifestKey: row.manifestKey, + createdAt: row.createdAt, + }) + .onConflictDoNothing() + const rows = await tx.execute(sql<{ txid: string }>`select pg_current_xact_id()::xid::text as txid`) + return Number(Array.isArray(rows) ? rows[0]!.txid : (rows as any)[0]!.txid) + }) + return result +} + +private async persistTimeoutConsumed(row: WakeRegistrationCollectionRow): Promise { + return await this.db.transaction(async (tx) => { + await tx + .update(wakeRegistrations) + .set({ timeoutConsumed: row.timeoutConsumed }) + .where(and(eq(wakeRegistrations.tenantId, row.tenantId), eq(wakeRegistrations.id, row.id))) + const rows = await tx.execute(sql<{ txid: string }>`select pg_current_xact_id()::xid::text as txid`) + return Number(Array.isArray(rows) ? rows[0]!.txid : (rows as any)[0]!.txid) + }) +} + +private async persistDeleteRows(rows: Array): Promise { + return await this.db.transaction(async (tx) => { + for (const row of rows) { + await tx + .delete(wakeRegistrations) + .where(and(eq(wakeRegistrations.tenantId, row.tenantId), eq(wakeRegistrations.id, row.id))) + } + const txRows = await tx.execute(sql<{ txid: string }>`select pg_current_xact_id()::xid::text as txid`) + return Number(Array.isArray(txRows) ? txRows[0]!.txid : (txRows as any)[0]!.txid) + }) +} +``` + +If Drizzle's transaction type does not accept `.execute` in this form, follow the pattern already used in `packages/agents-server/src/entity-registry.ts` for `pg_current_xact_id()::xid::text`. + +- [ ] **Step 7: Wire runtime actions to persistence** + +Update `register(...)` to allocate runtime ids from Postgres: + +```ts +async register(reg: WakeRegistration): Promise { + const tenantId = this.resolveTenantId(reg.tenantId) + const id = this.mode === `electric` ? await this.allocateRuntimeId() : this.allocateLocalId() + const tx = this.registerAction(this.normalizeRegistration(reg, tenantId, id)) + await tx.isPersisted.promise +} +``` + +Update `registerAction.mutationFn` so the action persists once, then waits for Electric to sync that txid into the collection: + +```ts +mutationFn: async (row) => { + if (this.mode === `local-test`) return + if (this.mode === `electric`) { + const txid = await this.persistInsert(row) + await this.requireCollection().utils.awaitTxId(txid, 10_000) + return { txid } + } + throw new Error(`WakeRegistry registerAction called before startup`) +} +``` + +Update `deleteRowsAction.mutationFn`: + +```ts +mutationFn: async ({ rows }) => { + if (this.mode === `local-test`) return + if (this.mode === `electric`) { + const txid = await this.persistDeleteRows(rows) + await this.requireCollection().utils.awaitTxId(txid, 10_000) + return { txid } + } + throw new Error(`WakeRegistry deleteRowsAction called before startup`) +} +``` + +Update `markTimeoutConsumedAction.mutationFn`: + +```ts +mutationFn: async ({ row }) => { + if (this.mode === `local-test`) return + if (this.mode === `electric`) { + const txid = await this.persistTimeoutConsumed({ + ...row, + timeoutConsumed: true, + }) + await this.requireCollection().utils.awaitTxId(txid, 10_000) + return { txid } + } + throw new Error( + `WakeRegistry markTimeoutConsumedAction called before startup` + ) +} +``` + +- [ ] **Step 8: Remove manual Shape code** + +Delete these obsolete items from `wake-registry.ts`: + +```ts +interface WakeRegistrationShapeRow ... +normalizeShapeRow(...) +shapeRowMatchesRegistration(...) +waitForRegistrationInShape(...) +recoverSync(...) +loadRegistrations(...) +replaceCachedRegistrations(...) +upsertCachedRegistration(...) +removeCachedRegistrationByDbId(...) +findCachedRegistration(...) +resetCachedRegistrations(...) +``` + +Also remove all direct imports/usages of `Shape`, `ShapeStream`, `Row`, and `Value`. + +- [ ] **Step 9: Run sync integration test** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/wake-registry-sync.test.ts --run +``` + +Expected: PASS. + +- [ ] **Step 10: Commit** + +```bash +git add packages/agents-server/src/wake-registry.ts packages/agents-server/test/wake-registry-sync.test.ts +git commit -m "Use Electric collection for wake registrations" +``` + +--- + +### Task 5: Update EntityManager, Host Startup, and Async Evaluation Call Sites + +**Files:** + +- Modify: `packages/agents-server/src/entity-manager.ts:430-440, 3291-3310` +- Modify: `packages/agents-server/src/host.ts:111-120` +- Modify: `packages/agents-server/src/standalone-runtime.ts:151-157` +- Modify: `packages/agents-server/test/server-start.test.ts` +- Modify: `packages/agents-server/test/wake-registry.test.ts` + +**Interfaces:** + +- Consumes: async `WakeRegistry.evaluate(...)`, `WakeRegistry.startSync(...)`, removed `loadRegistrations()`. +- Produces: runtime startup fails without Electric URL, no reload-on-miss fallback, all tests await evaluation. + +- [ ] **Step 1: Write failing startup test for missing Electric URL** + +In `packages/agents-server/test/server-start.test.ts`, update or add a test with this assertion: + +```ts +it(`fails host startup without Electric URL for wake registry sync`, async () => { + const host = createTestHost({ electricUrl: undefined }) + + await expect(host.start()).rejects.toThrow( + `WakeRegistry runtime requires an Electric URL` + ) +}) +``` + +If `createTestHost` does not exist, follow the existing host construction helper in this file and assert the same error around `host.start()`. + +- [ ] **Step 2: Run startup test to verify it fails** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/server-start.test.ts -t "fails host startup without Electric URL" --run +``` + +Expected: FAIL because host still calls `loadRegistrations()` or the test helper needs updating. + +- [ ] **Step 3: Update `EntityManager.rebuildWakeRegistry`** + +Replace `rebuildWakeRegistry(...)` in `packages/agents-server/src/entity-manager.ts` with: + +```ts +async rebuildWakeRegistry( + electricUrl?: string, + electricSecret?: string +): Promise { + if (!electricUrl) { + throw new Error(`WakeRegistry runtime requires an Electric URL`) + } + await this.wakeRegistry.startSync(electricUrl, electricSecret) +} +``` + +- [ ] **Step 4: Await async registry evaluation and remove reload-on-miss workaround** + +In `evaluateWakes(...)`, replace: + +```ts +const results = this.wakeRegistry.evaluate(sourceUrl, event, this.tenantId) +``` + +with: + +```ts +const results = await this.wakeRegistry.evaluate( + sourceUrl, + event, + this.tenantId +) +``` + +Ensure no code remains that calls `wakeRegistry.loadRegistrations()` or retries `evaluate(...)` after a miss. + +- [ ] **Step 5: Update `AgentsHost.start`** + +Replace the wake registry startup block in `packages/agents-server/src/host.ts` with: + +```ts +if (!this.electricUrl) { + throw new Error(`WakeRegistry runtime requires an Electric URL`) +} +await this.wakeRegistry.startSync(this.electricUrl, this.electricSecret) +``` + +- [ ] **Step 6: Update test mocks** + +In `packages/agents-server/test/server-start.test.ts`, remove mocked `loadRegistrations()` from `MockWakeRegistry`. Keep: + +```ts +startSync(): Promise { + return Promise.resolve() +} +``` + +If tests need a local-only registry, use `startLocalForTests()` directly in wake registry unit tests, not host startup tests. + +- [ ] **Step 7: Convert direct unit-test evaluations to await** + +In `packages/agents-server/test/wake-registry.test.ts`, replace patterns like: + +```ts +const results = registry.evaluate(`/child/c1`, event) +``` + +with: + +```ts +const results = await registry.evaluate(`/child/c1`, event) +``` + +For inline assertions, replace: + +```ts +expect(registry.evaluate(`/child/c1`, event)).toHaveLength(1) +``` + +with: + +```ts +await expect(registry.evaluate(`/child/c1`, event)).resolves.toHaveLength(1) +``` + +- [ ] **Step 8: Run affected tests** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/server-start.test.ts test/wake-registry.test.ts --run +``` + +Expected: PASS or only failures from tests still asserting deleted manual Shape/cache behavior. + +- [ ] **Step 9: Commit** + +```bash +git add packages/agents-server/src/entity-manager.ts packages/agents-server/src/host.ts packages/agents-server/src/standalone-runtime.ts packages/agents-server/test/server-start.test.ts packages/agents-server/test/wake-registry.test.ts +git commit -m "Require Electric for wake registry runtime" +``` + +--- + +### Task 6: Remove Obsolete Manual Cache Tests and Verify Full Wake Registry Behavior + +**Files:** + +- Modify: `packages/agents-server/test/wake-registry.test.ts` +- Modify: `packages/agents-server/test/wake-registry-sync.test.ts` +- Modify: `.changeset/fix-deferred-pull-wakes.md` + +**Interfaces:** + +- Consumes: completed TanStack DB-backed registry and async call sites. +- Produces: passing targeted test suite and updated changeset summary. + +- [ ] **Step 1: Delete obsolete manual Shape/cache tests** + +Remove tests whose only subject is deleted internals: + +```ts +it(`removes cached registrations from shape delete old_value ids`, ...) +it(`ignores malformed shape messages without headers while waiting for up-to-date`, ...) +it(`hydrates and updates the cache from shape changes`, ...) +it(`reloads wake registrations ... cache miss`, ...) +``` + +Do not delete behavior tests for tenant scoping, wake matching, debounce, timeout, one-shot, unregister, or end-to-end wake delivery. + +- [ ] **Step 2: Ensure every unit test starts local registry explicitly** + +For every unit test that creates `new WakeRegistry(createMockDb())`, add: + +```ts +await registry.startLocalForTests() +``` + +before `register(...)` or `evaluate(...)`. + +Do not add `startLocalForTests()` to integration tests that use `ElectricAgentsServer`, `EntityManager`, or real `TEST_ELECTRIC_URL`; those should exercise runtime `startSync(...)`. + +- [ ] **Step 3: Run all wake registry tests** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/wake-registry.test.ts test/wake-registry-sync.test.ts --run +``` + +Expected: PASS. + +- [ ] **Step 4: Run broader affected agent-server tests** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server test test/server-start.test.ts test/horton-pull-wake-e2e.test.ts test/pg-sync-wake-delivery.test.ts --run +``` + +Expected: PASS. + +- [ ] **Step 5: Run typecheck** + +Run: + +```bash +pnpm --filter @electric-ax/agents-server typecheck +``` + +Expected: PASS with no TypeScript errors. + +- [ ] **Step 6: Update changeset** + +Edit `.changeset/fix-deferred-pull-wakes.md` so the `@electric-ax/agents-server` entry mentions the TanStack DB registry refactor. Keep the existing runtime changeset text for `@electric-ax/agents-runtime` intact. + +Use wording like: + +```md +Refactor the server wake registry to use TanStack DB collections and optimistic actions over `wake_registrations`, removing the manual ShapeStream-backed registration cache and stale-cache reload fallback. +``` + +- [ ] **Step 7: Validate changeset coverage** + +Run: + +```bash +GITHUB_BASE_REF=main node scripts/check-changeset.mjs +``` + +Expected: success message that changesets cover affected packages. + +- [ ] **Step 8: Commit** + +```bash +git add packages/agents-server/test/wake-registry.test.ts packages/agents-server/test/wake-registry-sync.test.ts .changeset/fix-deferred-pull-wakes.md +git commit -m "Verify TanStack DB wake registry behavior" +``` + +--- + +## Self-Review + +**Spec coverage:** + +- Collection as only in-memory state: Tasks 1, 4, 6 remove `registrationCache`, `Shape`, and Shape tests. +- Runtime Electric collection: Task 4. +- Unit-test local-only collection: Tasks 1, 2, 6. +- Optimistic actions for all mutations: Task 2 and runtime persistence in Task 4. +- `queryOnce` evaluation: Task 1, call sites in Task 5. +- `createEffect` timeout side effects: Task 3. +- Remove reload-on-miss and `loadRegistrations()`: Tasks 4 and 5. +- Tests and verification: Tasks 1-6 include red/green commands and final typecheck. + +**Placeholder scan:** No `TBD`, `TODO`, `implement later`, or `similar to Task N` placeholders remain. The one conditional instruction about Drizzle execute shape gives exact fallback code paths and a required return type. + +**Type consistency:** The plan consistently uses `WakeRegistrationCollectionRow.id: number` as the TanStack DB key, `WakeRegistry.startLocalForTests()`, async `WakeRegistry.evaluate(...)`, and shared optimistic actions. Later tasks consume names introduced in earlier tasks. diff --git a/docs/superpowers/specs/2026-03-23-wake-registry-tanstack-db-design.md b/docs/superpowers/specs/2026-03-23-wake-registry-tanstack-db-design.md new file mode 100644 index 0000000000..ffd8119e8e --- /dev/null +++ b/docs/superpowers/specs/2026-03-23-wake-registry-tanstack-db-design.md @@ -0,0 +1,370 @@ +# Wake Registry TanStack DB Design + +## Context + +The current child wake delivery fix compensates for stale or incomplete wake registry state by reloading `wake_registrations` from Postgres when `runFinished` evaluation misses the in-memory cache. That fixes one symptom, but the underlying architecture still has two flawed state paths: + +1. Tests imperatively manage an in-memory cache. +2. Runtime manually syncs a Postgres table into that cache with `Shape` / `ShapeStream`. + +The desired replacement is to use TanStack DB as the wake registry state engine. + +## Goals + +- Make TanStack DB collection state the only in-memory source of wake registration rows. +- Use an Electric-backed collection in runtime so Postgres changes sync through Electric. +- Use a local-only collection only in unit tests. +- Use optimistic actions for every wake registry mutation. +- Use `queryOnce` for one-shot evaluation reads. +- Use `createEffect` for wake timeout timer side effects. +- Remove manual Shape subscription/cache code and the reload-on-miss workaround. + +## Non-goals + +- No `wake_registrations` schema migration. +- No custom TanStack DB adapter. +- No pull-wake runner changes. +- No changes to persisted wake row payloads. +- No broad `EntityManager` refactor beyond async wake evaluation call sites. + +## Architecture + +`WakeRegistry` owns one TanStack DB collection and one effect handle: + +```ts +private registrationsCollection: Collection | null +private registrationsEffect: { dispose(): Promise } | null +``` + +It no longer owns an authoritative `registrationCache`, `Shape`, `ShapeStream`, shape unsubscribe callback, or shape recovery promise. + +The existing runtime delivery state remains in `WakeRegistry` because it is not registration row state: + +- debounce timers +- debounce buffers +- latest debounced run status +- timeout timers +- timeout-delivered tracking +- timeout/debounce callbacks + +### Collection row shape + +Use camelCase fields internally: + +```ts +type WakeRegistrationCollectionRow = { + id: number + tenantId: string + subscriberUrl: string + sourceUrl: string + condition: WakeRegistration['condition'] + debounceMs: number + timeoutMs: number + oneShot: boolean + timeoutConsumed: boolean + includeResponse: boolean + manifestKey: string | null + createdAt: Date +} +``` + +`id` is both the TanStack DB collection key and the Postgres `wake_registrations.id`. Persisted rows already have a unique serial id, so the collection should use it directly rather than inventing a separate deterministic key. + +For newly registered rows, the action input must include an id before `onMutate` runs, because optimistic actions apply collection changes synchronously. Runtime can preallocate the id from the Postgres sequence and then insert with that explicit id in the action `mutationFn`. Local-only tests can allocate ids from an in-memory counter. + +## Runtime collection + +`startSync(electricUrl, electricSecret)` creates an Electric collection over `wake_registrations`: + +```ts +createCollection( + electricCollectionOptions({ + id: `wake-registrations:${tenantId ?? 'all'}`, + getKey: (row) => row.id, + schema, + shapeOptions: { + url: electricUrlWithPath(electricUrl, '/v1/shape').toString(), + params: { + table: 'wake_registrations', + ...(tenantId + ? { where: `tenant_id = ${sqlStringLiteral(tenantId)}` } + : {}), + columns: [ + 'id', + 'tenant_id', + 'subscriber_url', + 'source_url', + 'condition', + 'debounce_ms', + 'timeout_ms', + 'one_shot', + 'timeout_consumed', + 'include_response', + 'manifest_key', + 'created_at', + ], + replica: 'full', + ...(electricSecret ? { secret: electricSecret } : {}), + }, + parser: { + timestamptz: (value: string) => new Date(value), + }, + columnMapper: snakeCamelMapper(), + }, + onInsert, + onUpdate, + onDelete, + }) +) +``` + +Use Electric's built-in `snakeCamelMapper()` in `shapeOptions.columnMapper` to map snake_case Postgres columns to camelCase collection fields. `created_at` must still parse to `Date` on the sync path via the `timestamptz` parser. + +Startup fails if the collection cannot preload. Running with an empty registry after failed sync would silently drop terminal wake events. + +## Local-only collection + +Runtime requires Electric. There is no no-Electric Postgres fallback and no replacement for the old `loadRegistrations()` rebuild path. + +For unit tests only, create or replace a local-only collection: + +```ts +createCollection( + localOnlyCollectionOptions({ + id: `wake-registrations-local:${tenantId ?? 'all'}`, + getKey: (row) => row.id, + initialData: [], + }) +) +``` + +Unit tests should not need Postgres or Electric just to exercise registry logic. They can start with an empty local-only collection and create rows through the same optimistic actions used by runtime. + +Integration tests that need real persistence or cross-process sync should use Postgres + Electric and `startSync(...)`, not a local collection seeded from Postgres. Server startup without an Electric URL should fail rather than silently rebuilding local state from Postgres. + +## Mutation model + +All `WakeRegistry` mutations use `createOptimisticAction`. Public registry methods do not call `collection.insert`, `collection.update`, or `collection.delete` directly. + +Actions: + +- `registerAction` +- `unregisterByManifestKeyAction` +- `unregisterBySubscriberAction` +- `unregisterBySourceAction` +- `unregisterBySubscriberAndSourceAction` +- `markTimeoutConsumedAction` +- `consumeMatchedRegistrationsAction` or equivalent one-shot cleanup action + +Each action has two responsibilities: + +1. `onMutate`: synchronously apply optimistic changes to the collection. +2. `mutationFn`: persist the intent to Postgres in runtime mode and return/await a txid. In local-only mode, this is a no-op or minimal test hook. + +### Register + +`register(reg)` resolves the tenant, normalizes defaults, invokes `registerAction`, and awaits `tx.isPersisted.promise`. + +`registerAction.onMutate` inserts or upserts the normalized row into the collection. + +`registerAction.mutationFn` performs the existing insert: + +- insert into `wake_registrations` +- `ON CONFLICT DO NOTHING` +- insert with the preallocated id +- if conflicted, fetch the existing row and transaction id + +The transaction id comes from the same Postgres transaction: + +```sql +SELECT pg_current_xact_id()::xid::text AS txid +``` + +The Electric collection should hold optimistic state until that txid syncs back. Because the action preallocates the numeric id, `WakeEvalResult.registrationDbId` can use the collection key directly. + +### Bulk unregister actions + +Bulk unregister methods are domain intents, not repeated ad-hoc row deletes. Each uses one optimistic action: + +- `unregisterByManifestKey(subscriberUrl, manifestKey, tenantId?)` +- `unregisterBySubscriber(subscriberUrl, tenantId?)` +- `unregisterBySource(sourceUrl, tenantId?)` +- `unregisterBySubscriberAndSource(subscriberUrl, sourceUrl, tenantId?)` + +Each action’s `onMutate` deletes all currently matching collection rows synchronously. + +Each action’s `mutationFn` runs one SQL/Drizzle delete statement for the same predicate and returns/awaits the resulting txid. + +Public methods await `tx.isPersisted.promise` because callers expect unregister completion. + +### Timeout consumed + +When a wake timeout is delivered or a matching event clears a timeout, `markTimeoutConsumedAction` updates the row: + +```ts +draft.timeoutConsumed = true +``` + +Its runtime `mutationFn` persists `timeout_consumed = true` and returns/awaits a txid. + +Current fire-and-forget semantics can stay fire-and-forget, but failures must be logged with tenant id, registration id, source url, and subscriber url. + +### One-shot cleanup + +When `evaluate()` matches one-shot registrations, it invokes a cleanup action that deletes the matched rows optimistically before `evaluate()` returns. This prevents immediate repeated evaluation from double-delivering. + +Persistence can remain fire-and-forget if the current path is fire-and-forget, with logged failures. + +## Evaluation reads + +`evaluate()` becomes async: + +```ts +async evaluate( + sourceUrl: string, + event: Record, + tenantId?: string +): Promise> +``` + +It reads current registrations with `queryOnce`: + +```ts +const regs = await queryOnce((q) => + q + .from({ reg: this.requireRegistrationsCollection() }) + .where(({ reg }) => + and(eq(reg.tenantId, resolvedTenantId), eq(reg.sourceUrl, sourceUrl)) + ) +) +``` + +`queryOnce` is async because a source collection may need preload. In the registry’s normal runtime path the collection is already loaded, so this should usually be a cheap microtick async boundary. + +The existing condition matching and wake result construction can remain mostly unchanged: + +- `runFinished` matches terminal run updates +- collection-change conditions match collection names and ops +- immediate matches return `WakeEvalResult[]` +- debounced matches append to debounce buffers +- timeout timers are cleared when the expected event arrives first + +No code path should reload all registrations from Postgres on an evaluation miss. + +## Timeout wake side effects + +Wake registration `timeoutMs` measures how long a registration has been waiting for its matching source event. If that event does not arrive before `createdAt + timeoutMs`, the subscriber receives a timeout wake: + +```ts +wakeMessage: { + source: reg.sourceUrl, + timeout: true, + changes: [], +} +``` + +Use `createEffect` over the registration collection to manage these Node timers: + +```ts +createEffect({ + query: (q) => q.from({ reg: registrationsCollection }), + skipInitial: false, + onEnter: ({ value }) => syncTimeoutTimer(value), + onUpdate: ({ value }) => syncTimeoutTimer(value), + onExit: ({ value }) => clearRegistrationState(value), +}) +``` + +Timer behavior: + +- On enter/update, if `timeoutMs <= 0` or `timeoutConsumed`, ensure no timer is active. +- If `createdAt + timeoutMs` is in the future, schedule one Node timer. +- If the deadline has already passed and the timeout has not been delivered, deliver the timeout wake once if a tenant callback is registered. +- On exit, clear timeout/debounce state and remove the row from `timeoutDelivered`. + +When a timeout fires, it delivers the existing timeout wake result and invokes `markTimeoutConsumedAction`. + +## Error handling + +- Startup fails if the Electric collection preload fails. +- Local-only unit-test initialization should not perform DB I/O. +- Runtime startup without an Electric URL fails clearly; there is no Postgres-seeded fallback. +- Awaited public mutation methods reject if persistence fails; TanStack DB rolls back optimistic state. +- Fire-and-forget action failures are logged with enough context to diagnose the row and intent. +- Electric collection owns Shape lifecycle and retry behavior. `WakeRegistry` should not reimplement Shape recovery. +- Evaluation misses are not retried by reloading all rows from Postgres. + +## Call-site changes + +- Update every `wakeRegistry.evaluate(...)` call to `await wakeRegistry.evaluate(...)`. +- Remove `EntityManager.evaluateWakes()` retry/reload-on-cache-miss logic. +- Remove `loadRegistrations()` and update startup paths to require `startSync(...)` for runtime. +- Keep `flushDebounce(...)` synchronous unless it starts reading registration rows. It currently only drains debounce buffers. + +## Test plan + +Update existing wake registry unit tests: + +- Convert direct `registry.evaluate(...)` assertions to `await registry.evaluate(...)`. +- Preserve coverage for: + - tenant scoping + - `runFinished` matching + - collection-change matching + - debounce coalescing + - timeout delivery + - timeout not consumed before callback registration + - one-shot cleanup + - unregister variants + - `includeResponse` + - concurrent child `runFinished` delivery + +Remove or replace tests that only cover manual Shape cache machinery: + +- direct `applyShapeMessage` tests +- malformed Shape message handling +- custom Shape recovery behavior + +Add TanStack DB-specific tests: + +- `registerAction` preallocates an id, inserts into the local collection, and `evaluate()` sees it through `queryOnce`. +- Each bulk unregister action removes all matching rows optimistically. +- One-shot cleanup removes matched rows before a second immediate evaluation. +- `markTimeoutConsumedAction` prevents repeat timeout delivery. +- Local-only unit tests require no Postgres, Electric, or Electric mock. + +Add or keep an integration-style Electric test if practical: + +- registry A registers a wake through Postgres/Electric collection +- registry B observes the row through Electric collection +- a terminal child run event evaluated by registry B produces the parent wake without any reload-on-miss fallback + +## Files likely touched + +- `packages/agents-server/package.json` + - add `@tanstack/db` + - add `@tanstack/electric-db-collection` +- `packages/agents-server/src/wake-registry.ts` + - main refactor +- `packages/agents-server/src/entity-manager.ts` + - await async evaluation and remove reload-on-miss fallback +- `packages/agents-server/test/wake-registry.test.ts` + - update async assertions and action expectations +- `packages/agents-server/test/wake-registry-sync.test.ts` + - remove/replace manual Shape tests with Postgres + Electric integration coverage where needed +- `.changeset/*` + - update package changeset if implementation changes dependencies or behavior + +## Acceptance criteria + +- `WakeRegistry` has no authoritative `Map` cache. +- `WakeRegistry` does not instantiate `Shape` or `ShapeStream` directly. +- Runtime registration state comes from an Electric TanStack DB collection. +- Unit-test registration state comes from a local-only TanStack DB collection. +- Runtime startup without Electric fails instead of calling `loadRegistrations()`. +- All public registry mutations invoke optimistic actions. +- Bulk unregister operations are one action per domain intent. +- Runtime mutation handlers return/await real Postgres txids. +- Wake evaluation uses `queryOnce` over the collection. +- Timeout wake timers are driven by `createEffect` over collection rows. +- `EntityManager` no longer reloads registrations on terminal evaluation miss. +- Existing wake registry behavior tests pass after async/action updates. diff --git a/examples/agents-chat-starter/package.json b/examples/agents-chat-starter/package.json index 3505d09a10..af56f57e4f 100644 --- a/examples/agents-chat-starter/package.json +++ b/examples/agents-chat-starter/package.json @@ -12,7 +12,6 @@ "dependencies": { "@durable-streams/state": "^0.3.1", "@electric-ax/agents-runtime": "workspace:*", - "@mariozechner/pi-agent-core": "^0.57.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/themes": "^3.3.0", "@sinclair/typebox": "^0.34.0", diff --git a/examples/deep-survey/package.json b/examples/deep-survey/package.json index 5bf5e8e240..640c06e398 100644 --- a/examples/deep-survey/package.json +++ b/examples/deep-survey/package.json @@ -13,7 +13,6 @@ "dependencies": { "@durable-streams/state": "^0.3.1", "@electric-ax/agents-runtime": "workspace:*", - "@mariozechner/pi-agent-core": "^0.70.2", "@radix-ui/themes": "^3.3.0", "@sinclair/typebox": "^0.34.0", "@tanstack/db": "^0.6.6", diff --git a/examples/deep-survey/src/server/orchestrator.ts b/examples/deep-survey/src/server/orchestrator.ts index c593f760a9..e6f2f9dbaf 100644 --- a/examples/deep-survey/src/server/orchestrator.ts +++ b/examples/deep-survey/src/server/orchestrator.ts @@ -1,4 +1,5 @@ import type { + AgentTool, EntityRegistry, HandlerContext, SharedStateHandle, @@ -7,7 +8,6 @@ import { db } from '@electric-ax/agents-runtime' import { queryOnce } from '@durable-streams/state/db' import { Type } from '@sinclair/typebox' import { exec, execFile } from 'node:child_process' -import type { AgentTool } from '@mariozechner/pi-agent-core' import { swarmSharedSchema, type WikiEntry, type Xref } from './schema.js' import { explorerSpawnArgs } from './explorer.js' import { SURVEY_WORKER_ENTITY_TYPE } from './survey-worker.js' diff --git a/packages/agents-desktop/package.json b/packages/agents-desktop/package.json index 51e83deed0..438fcb5152 100644 --- a/packages/agents-desktop/package.json +++ b/packages/agents-desktop/package.json @@ -9,7 +9,7 @@ "type": "module", "main": "./dist/main.cjs", "scripts": { - "build": "pnpm --filter @electric-ax/agents build && pnpm --filter electric-ax build:desktop && pnpm --filter @electric-ax/agents-server-ui build:desktop && vite build", + "build": "pnpm --filter @electric-ax/agents-runtime build && pnpm --filter @electric-ax/agents build && pnpm --filter electric-ax build:desktop && pnpm --filter @electric-ax/agents-server-ui build:desktop && vite build", "dev": "pnpm run ensure:electron && concurrently -k -n ui,desktop -c cyan,green \"pnpm run dev:ui\" \"pnpm run dev:desktop\"", "dev:ui": "pnpm --filter @electric-ax/agents-server-ui dev:desktop", "dev:desktop": "wait-on -d 200 http-get://localhost:${ELECTRIC_DESKTOP_UI_PORT:-5183} && vite", @@ -24,7 +24,8 @@ "rebuild:native": "electron-builder install-app-deps", "start": "pnpm run ensure:electron && electron .", "coverage": "true", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "test": "vitest run" }, "dependencies": { "@electric-sql/client": "^1.5.22", @@ -53,6 +54,7 @@ "typescript": "^5.8.3", "vite": "^7.1.7", "vite-plugin-electron": "^0.29.1", - "wait-on": "^9.0.1" + "wait-on": "^9.0.1", + "vitest": "^3.2.4" } } diff --git a/packages/agents-desktop/src/app/controller.ts b/packages/agents-desktop/src/app/controller.ts index 387e55d928..4c671ae8b1 100644 --- a/packages/agents-desktop/src/app/controller.ts +++ b/packages/agents-desktop/src/app/controller.ts @@ -3,6 +3,7 @@ import fs from 'node:fs' import type { DesktopAppContext } from './context' import * as AppLifecycle from './lifecycle' import * as LoginItems from './login-items' +import { createPowerMonitorRecovery } from './power-monitor' import { createDesktopUpdater } from './updater' import * as CloudAuthInjection from '../cloud/auth-injection' import * as ServerFetch from '../cloud/server-fetch' @@ -11,7 +12,10 @@ import { createLocalDiscoveryLoop } from '../discovery/local-discovery' import * as DesktopIpc from '../ipc/register' import { ensureRuntimeEntry as ensureRuntimeEntryInStore } from '../runtime/entries' import { createRuntimeController } from '../runtime/controller' -import { desktopSkillDirectories } from '../runtime/lifecycle' +import { + desktopSkillDirectories, + reconnectPullWakesAfterResume, +} from '../runtime/lifecycle' import * as SettingsBootstrap from '../settings/bootstrap' import * as ServerSelection from '../settings/selection' import { @@ -179,6 +183,10 @@ export function createDesktopMainController(ctx: DesktopAppContext) { selectedServerIdForWindow, }) + const powerMonitorRecovery = createPowerMonitorRecovery({ + onResume: () => reconnectPullWakesAfterResume(runtime.lifecycleDeps), + }) + function hasConnectedLocalRuntime(): boolean { return runtime.hasConnectedLocalRuntime() } @@ -599,9 +607,11 @@ export function createDesktopMainController(ctx: DesktopAppContext) { showOrCreateWindow, syncLaunchAtLoginSetting, connectConfiguredServers, + startPowerMonitorRecovery: powerMonitorRecovery.start, startDiscoveryLoop: localDiscovery.startDiscoveryLoop, initializeUpdater: updater.initialize, quitApp: async (): Promise => { + powerMonitorRecovery.stop() for (const watcher of skillDirectoryWatchers) watcher.close() skillDirectoryWatchers = [] if (skillReloadTimer) clearTimeout(skillReloadTimer) diff --git a/packages/agents-desktop/src/app/power-monitor.test.ts b/packages/agents-desktop/src/app/power-monitor.test.ts new file mode 100644 index 0000000000..11109b11f5 --- /dev/null +++ b/packages/agents-desktop/src/app/power-monitor.test.ts @@ -0,0 +1,66 @@ +import { EventEmitter } from 'node:events' +import { describe, expect, it, vi } from 'vitest' +vi.mock(`electron`, () => ({ + powerMonitor: new EventEmitter(), +})) + +import { createPowerMonitorRecovery } from './power-monitor' + +class FakePowerMonitor extends EventEmitter { + listenerCountFor(event: `suspend` | `resume`): number { + return this.listenerCount(event) + } +} + +describe(`power monitor recovery`, () => { + it(`routes resume through the supplied runtime recovery boundary`, () => { + const monitor = new FakePowerMonitor() + const reconnectPullWakesAfterResume = vi.fn() + const recovery = createPowerMonitorRecovery({ + monitor, + onResume: reconnectPullWakesAfterResume, + }) + recovery.start() + + monitor.emit(`resume`) + + expect(reconnectPullWakesAfterResume).toHaveBeenCalledTimes(1) + }) + + it(`runs resume recovery once and does not duplicate listeners`, () => { + const monitor = new FakePowerMonitor() + const onResume = vi.fn() + const recovery = createPowerMonitorRecovery({ monitor, onResume }) + + recovery.start() + recovery.start() + expect(monitor.listenerCountFor(`suspend`)).toBe(1) + expect(monitor.listenerCountFor(`resume`)).toBe(1) + + monitor.emit(`suspend`) + monitor.emit(`resume`) + expect(onResume).toHaveBeenCalledTimes(1) + + recovery.stop() + expect(monitor.listenerCountFor(`suspend`)).toBe(0) + expect(monitor.listenerCountFor(`resume`)).toBe(0) + }) + + it(`reports resume callback failures without throwing from the event`, () => { + const monitor = new FakePowerMonitor() + const onError = vi.fn() + const recovery = createPowerMonitorRecovery({ + monitor, + onResume: () => { + throw new Error(`resume failed`) + }, + onError, + }) + recovery.start() + + expect(() => monitor.emit(`resume`)).not.toThrow() + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ message: `resume failed` }) + ) + }) +}) diff --git a/packages/agents-desktop/src/app/power-monitor.ts b/packages/agents-desktop/src/app/power-monitor.ts new file mode 100644 index 0000000000..1c3eaa5724 --- /dev/null +++ b/packages/agents-desktop/src/app/power-monitor.ts @@ -0,0 +1,56 @@ +import { powerMonitor } from 'electron' + +export type PowerMonitorLike = { + on: (event: `suspend` | `resume`, listener: () => void) => unknown + removeListener: (event: `suspend` | `resume`, listener: () => void) => unknown +} + +export type PowerMonitorRecovery = { + start: () => void + stop: () => void +} + +export function createPowerMonitorRecovery(options: { + monitor?: PowerMonitorLike + onResume: () => void + onError?: (error: Error) => void +}): PowerMonitorRecovery { + const monitor = options.monitor ?? powerMonitor + let started = false + let suspendedAt: number | null = null + + const handleSuspend = (): void => { + suspendedAt = Date.now() + console.info(`[agents-desktop] System suspended.`) + } + const handleResume = (): void => { + const elapsedMs = suspendedAt === null ? null : Date.now() - suspendedAt + suspendedAt = null + console.info( + `[agents-desktop] System resumed${elapsedMs === null ? `` : ` after ${elapsedMs}ms`}; reconnecting pull-wake streams.` + ) + try { + options.onResume() + } catch (cause) { + const error = cause instanceof Error ? cause : new Error(String(cause)) + if (options.onError) options.onError(error) + else console.warn(`[agents-desktop] Resume recovery failed:`, error) + } + } + + return { + start(): void { + if (started) return + started = true + monitor.on(`suspend`, handleSuspend) + monitor.on(`resume`, handleResume) + }, + stop(): void { + if (!started) return + started = false + monitor.removeListener(`suspend`, handleSuspend) + monitor.removeListener(`resume`, handleResume) + suspendedAt = null + }, + } +} diff --git a/packages/agents-desktop/src/credentials/model-picker.ts b/packages/agents-desktop/src/credentials/model-picker.ts index 93f33db86a..02808799e5 100644 --- a/packages/agents-desktop/src/credentials/model-picker.ts +++ b/packages/agents-desktop/src/credentials/model-picker.ts @@ -17,8 +17,8 @@ export const DEFAULT_ENABLED_MODEL_VALUES = [ `anthropic:claude-opus-4-7`, `anthropic:claude-sonnet-4-6`, `openai:gpt-5.5`, - `openai-codex:gpt-5.4`, - `openai-codex:gpt-5.5`, + `openai-codex:gpt-5.6-terra`, + `openai-codex:gpt-5.6-sol`, `deepseek:deepseek-v4-flash`, `deepseek:deepseek-v4-pro`, `moonshot:kimi-k2.6`, diff --git a/packages/agents-desktop/src/main.ts b/packages/agents-desktop/src/main.ts index 7972220313..664477a7af 100644 --- a/packages/agents-desktop/src/main.ts +++ b/packages/agents-desktop/src/main.ts @@ -129,6 +129,7 @@ async function main(): Promise { if (!LoginItems.isBackgroundLaunch()) { controller.createWindow() } + controller.startPowerMonitorRecovery() controller.connectConfiguredServers() controller.startDiscoveryLoop() controller.initializeUpdater() diff --git a/packages/agents-desktop/src/runtime/lifecycle.test.ts b/packages/agents-desktop/src/runtime/lifecycle.test.ts new file mode 100644 index 0000000000..d58540e372 --- /dev/null +++ b/packages/agents-desktop/src/runtime/lifecycle.test.ts @@ -0,0 +1,206 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { RuntimeEntry, ServerConfig } from '../shared/types' +import type { RuntimeLifecycleDeps } from './lifecycle' + +vi.mock(`@electric-ax/agents`, () => ({ + BuiltinAgentsServer: vi.fn(() => ({ + reconnectPullWake: vi.fn(), + })), +})) + +vi.mock(`electron`, () => ({ + app: { getPath: () => `/tmp` }, + powerSaveBlocker: { + isStarted: () => false, + start: () => 1, + stop: vi.fn(), + }, +})) + +vi.mock(`./health`, async (importOriginal) => { + // prettier-ignore + const actual = await importOriginal() // eslint-disable-line quotes + return { + ...actual, + checkAgentsServerHealth: vi.fn(async () => ({ ok: true })), + } +}) + +import { reconnectPullWakesAfterResume, startRuntime } from './lifecycle' + +type AddedRuntimeOptions = { + id: string + desiredState: ServerConfig[`desiredState`] + localRuntimeEnabled: boolean + localRuntimeStatus: RuntimeEntry[`localRuntimeStatus`] +} + +function addRuntime( + deps: RuntimeLifecycleDeps, + options: AddedRuntimeOptions +): { + reconnect: ReturnType + server: ServerConfig + entry: RuntimeEntry +} { + const reconnect = vi.fn() + const server: ServerConfig = { + id: options.id, + name: options.id, + url: `http://localhost/${options.id}`, + source: `manual`, + desiredState: options.desiredState, + localRuntimeEnabled: options.localRuntimeEnabled, + } + const entry: RuntimeEntry = { + serverId: server.id, + desiredState: options.desiredState, + status: options.desiredState === `connected` ? `connected` : `disconnected`, + localRuntimeStatus: options.localRuntimeStatus, + runtime: { + reconnectPullWake: reconnect, + } as unknown as RuntimeEntry[`runtime`], + runtimeUrl: null, + runtimeError: null, + reconnectTimer: null, + reconnectAttempt: 0, + generation: 0, + lastError: null, + lastConnectedAt: null, + mcpUnsubscribe: null, + } + deps.settings.servers.push(server) + deps.runtimeEntries.set(server.id, entry) + return { reconnect, server, entry } +} + +function setup(): { + deps: RuntimeLifecycleDeps + entry: RuntimeEntry +} { + const server: ServerConfig = { + id: `local`, + name: `Local`, + url: `http://localhost:4437`, + source: `manual`, + desiredState: `connected`, + localRuntimeEnabled: true, + } + const entry: RuntimeEntry = { + serverId: server.id, + desiredState: `connected`, + status: `disconnected`, + localRuntimeStatus: `stopped`, + runtime: null, + runtimeUrl: null, + runtimeError: null, + reconnectTimer: null, + reconnectAttempt: 0, + generation: 0, + lastError: null, + lastConnectedAt: null, + mcpUnsubscribe: null, + } + const deps: RuntimeLifecycleDeps = { + settings: { + servers: [server], + defaultServerId: server.id, + pullWakeRunnerId: null, + }, + runtimeEntries: new Map([[server.id, entry]]), + windowSelections: new Map(), + findServer: (serverId) => + deps.settings.servers.find((candidate) => candidate.id === serverId) ?? + null, + ensureRuntimeEntry: () => entry, + saveSettings: vi.fn(async () => undefined), + refreshDesktopState: vi.fn(), + setState: vi.fn(), + setCredentialsRestartPending: vi.fn(), + injectDevPrincipalHeaders: (value) => value, + configureRuntimeEnvironment: vi.fn(), + applyApiKeys: vi.fn(), + syncCodexEnvironment: vi.fn(async () => undefined), + broadcastMcpSnapshot: vi.fn(), + handleAuthorizeUrl: vi.fn(async () => undefined), + getCloudAgentServers: vi.fn(), + getCloudAuthState: vi.fn(), + } + return { deps, entry } +} + +describe(`desktop runtime lifecycle`, () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + it(`reconnects only connected running local runtimes after resume`, () => { + const { deps, entry } = setup() + const reconnectRunning = vi.fn() + entry.status = `connected` + entry.localRuntimeStatus = `running` + entry.runtime = { + reconnectPullWake: reconnectRunning, + } as unknown as RuntimeEntry[`runtime`] + + const disconnected = addRuntime(deps, { + id: `disconnected`, + desiredState: `disconnected`, + localRuntimeEnabled: true, + localRuntimeStatus: `running`, + }) + const disabled = addRuntime(deps, { + id: `disabled`, + desiredState: `connected`, + localRuntimeEnabled: false, + localRuntimeStatus: `disabled`, + }) + const starting = addRuntime(deps, { + id: `starting`, + desiredState: `connected`, + localRuntimeEnabled: true, + localRuntimeStatus: `starting`, + }) + + reconnectPullWakesAfterResume(deps) + + expect(reconnectRunning).toHaveBeenCalledTimes(1) + expect(disconnected.reconnect).not.toHaveBeenCalled() + expect(disabled.reconnect).not.toHaveBeenCalled() + expect(starting.reconnect).not.toHaveBeenCalled() + }) + + it(`isolates pull-wake reconnect failures between runtimes`, () => { + const { deps, entry } = setup() + entry.status = `connected` + entry.localRuntimeStatus = `running` + entry.runtime = { + reconnectPullWake: vi.fn(() => { + throw new Error(`first failed`) + }), + } as unknown as RuntimeEntry[`runtime`] + const second = addRuntime(deps, { + id: `second`, + desiredState: `connected`, + localRuntimeEnabled: true, + localRuntimeStatus: `running`, + }) + + expect(() => reconnectPullWakesAfterResume(deps)).not.toThrow() + expect(second.reconnect).toHaveBeenCalledTimes(1) + }) + + it(`records and retries failures that happen before the built-in runtime starts`, async () => { + const { deps, entry } = setup() + + await expect(startRuntime(deps, `local`)).resolves.toBeUndefined() + + expect(entry.status).toBe(`error`) + expect(entry.localRuntimeStatus).toBe(`error`) + expect(entry.lastError).toBe( + `Desktop built-in agents require a pull-wake runner id` + ) + expect(entry.reconnectAttempt).toBe(1) + expect(entry.reconnectTimer).not.toBeNull() + }) +}) diff --git a/packages/agents-desktop/src/runtime/lifecycle.ts b/packages/agents-desktop/src/runtime/lifecycle.ts index e6264d79f1..27b090432d 100644 --- a/packages/agents-desktop/src/runtime/lifecycle.ts +++ b/packages/agents-desktop/src/runtime/lifecycle.ts @@ -118,6 +118,31 @@ export function refreshPowerSaveBlocker(deps: RuntimeLifecycleDeps): void { powerSaveBlockerId = null } +export function reconnectPullWakesAfterResume( + deps: RuntimeLifecycleDeps +): void { + for (const entry of deps.runtimeEntries.values()) { + const server = deps.findServer(entry.serverId) + if ( + !server?.localRuntimeEnabled || + entry.desiredState !== `connected` || + entry.localRuntimeStatus !== `running` || + !entry.runtime + ) { + continue + } + + try { + entry.runtime.reconnectPullWake() + } catch (error) { + console.warn( + `[agents-desktop] Failed to reconnect pull-wake after resume for ${entry.serverId}:`, + error + ) + } + } +} + export async function restartConnectedRuntimes( deps: RuntimeLifecycleDeps ): Promise { @@ -185,7 +210,7 @@ export async function stopRuntimeEntry( } } -export async function startRuntime( +async function startRuntimeAttempt( deps: RuntimeLifecycleDeps, serverId: string ): Promise { @@ -376,21 +401,49 @@ export async function startRuntime( error, activeServer.url ) + const message = + startupNetworkError ?? + (error instanceof Error ? error.message : String(error)) entry.status = `error` entry.localRuntimeStatus = `error` entry.runtimeUrl = null - entry.runtimeError = - startupNetworkError ?? - (error instanceof Error ? error.message : String(error)) - entry.lastError = - startupNetworkError ?? - (error instanceof Error ? error.message : String(error)) + entry.runtimeError = message + entry.lastError = message entry.reconnectAttempt += 1 refreshPowerSaveBlocker(deps) scheduleReconnect(deps, serverId) } } +export async function startRuntime( + deps: RuntimeLifecycleDeps, + serverId: string +): Promise { + try { + await startRuntimeAttempt(deps, serverId) + } catch (error) { + const activeServer = deps.findServer(serverId) + if (!activeServer) return + const entry = deps.ensureRuntimeEntry(activeServer) + if (entry.desiredState !== `connected`) return + + const message = error instanceof Error ? error.message : String(error) + console.error( + `[agents-desktop] Failed to start built-in agents runtime for ${activeServer.url}:`, + error + ) + entry.status = `error` + entry.localRuntimeStatus = `error` + entry.runtimeUrl = null + entry.runtimeError = message + entry.lastError = message + entry.reconnectAttempt += 1 + refreshPowerSaveBlocker(deps) + deps.refreshDesktopState() + scheduleReconnect(deps, serverId) + } +} + export async function connectServer( deps: RuntimeLifecycleDeps, serverId: string, diff --git a/packages/agents-mobile/package.json b/packages/agents-mobile/package.json index fd543c25d1..a54d9ca997 100644 --- a/packages/agents-mobile/package.json +++ b/packages/agents-mobile/package.json @@ -25,7 +25,7 @@ "@tanstack/db": "^0.6.5", "@tanstack/electric-db-collection": "^0.3.5", "@tanstack/react-db": "^0.1.85", - "expo": "54.0.35", + "expo": "54.0.36", "expo-build-properties": "~1.0.10", "expo-clipboard": "~8.0.8", "expo-constants": "~18.0.13", diff --git a/packages/agents-runtime/package.json b/packages/agents-runtime/package.json index d886735c25..bdff92177b 100644 --- a/packages/agents-runtime/package.json +++ b/packages/agents-runtime/package.json @@ -108,11 +108,11 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.78.0", - "@durable-streams/client": "^0.2.6", + "@durable-streams/client": "https://pkg.pr.new/@durable-streams/client@293", "@durable-streams/state": "^0.3.1", "@electric-ax/agents-mcp": "workspace:*", - "@mariozechner/pi-agent-core": "^0.70.2", - "@mariozechner/pi-ai": "^0.70.2", + "@earendil-works/pi-agent-core": "^0.80.5", + "@earendil-works/pi-ai": "^0.80.5", "@mozilla/readability": "^0.6.0", "@sinclair/typebox": "^0.34.48", "@standard-schema/spec": "^1.1.0", diff --git a/packages/agents-runtime/src/compaction-summarize.ts b/packages/agents-runtime/src/compaction-summarize.ts index cfa3d9bfcc..f1bf492f89 100644 --- a/packages/agents-runtime/src/compaction-summarize.ts +++ b/packages/agents-runtime/src/compaction-summarize.ts @@ -1,4 +1,4 @@ -import { completeSimple } from '@mariozechner/pi-ai' +import { completeSimple } from '@earendil-works/pi-ai/compat' import { resolvePiModel, toAgentHistory } from './pi-adapter' import { COMPACTION_SUMMARIZATION_PROMPT, diff --git a/packages/agents-runtime/src/create-handler.ts b/packages/agents-runtime/src/create-handler.ts index 7bc8069ec5..7d1bc54571 100644 --- a/packages/agents-runtime/src/create-handler.ts +++ b/packages/agents-runtime/src/create-handler.ts @@ -145,7 +145,10 @@ export interface RuntimeRouter { */ dispatchWake: ( notification: WakeNotification, - options?: Pick + options?: Pick< + ProcessWakeConfig, + `claimHeaders` | `claimTokenHeader` | `onDoneNextWake` + > ) => void /** True when a wake for the stream path is already in flight. */ diff --git a/packages/agents-runtime/src/model-runner.ts b/packages/agents-runtime/src/model-runner.ts index aaacb5366b..9628ce3cd1 100644 --- a/packages/agents-runtime/src/model-runner.ts +++ b/packages/agents-runtime/src/model-runner.ts @@ -1,14 +1,14 @@ import { readFileSync } from 'node:fs' import { homedir } from 'node:os' import { join } from 'node:path' -import { completeSimple, getModel } from '@mariozechner/pi-ai' +import { completeSimple, getModel } from '@earendil-works/pi-ai/compat' import { MOONSHOT_PROVIDER, getMoonshotApiKey, getMoonshotModel, } from './moonshot-models' import type { AgentConfig } from './types' -import type { KnownProvider } from '@mariozechner/pi-ai' +import type { KnownProvider } from '@earendil-works/pi-ai/compat' export interface LowCostModelChoice { provider: KnownProvider | string @@ -29,7 +29,7 @@ export type LowCostModelConfig = Pick< const PREFERRED_IDS_BY_PROVIDER: Record> = { anthropic: [`claude-3-5-haiku-latest`, `claude-3-5-haiku-20241022`], openai: [`gpt-4.1-nano`, `gpt-4o-mini`, `gpt-4.1-mini`], - 'openai-codex': [`gpt-5.4-mini`, `gpt-5.1-codex-mini`], + 'openai-codex': [`gpt-5.6-luna`, `gpt-5.4-mini`, `gpt-5.1-codex-mini`], deepseek: [`deepseek-v4-flash`, `deepseek-v4-pro`], moonshot: [`kimi-k2.6`, `kimi-k2.5`, `moonshot-v1-8k`], } @@ -92,7 +92,7 @@ function envCatalog(): LowCostModelCatalog { if (providers.includes(`openai-codex`)) { choices.push({ provider: `openai-codex`, - id: `gpt-5.4-mini`, + id: `gpt-5.6-luna`, reasoning: false, }) } diff --git a/packages/agents-runtime/src/moonshot-models.ts b/packages/agents-runtime/src/moonshot-models.ts index 3e02656c9a..11ec3636d3 100644 --- a/packages/agents-runtime/src/moonshot-models.ts +++ b/packages/agents-runtime/src/moonshot-models.ts @@ -1,4 +1,4 @@ -import type { Model } from '@mariozechner/pi-ai' +import type { Model } from '@earendil-works/pi-ai/compat' export const MOONSHOT_PROVIDER = `moonshot` as const export type MoonshotProvider = typeof MOONSHOT_PROVIDER diff --git a/packages/agents-runtime/src/pi-adapter.ts b/packages/agents-runtime/src/pi-adapter.ts index a45de6d955..4a24476f41 100644 --- a/packages/agents-runtime/src/pi-adapter.ts +++ b/packages/agents-runtime/src/pi-adapter.ts @@ -7,8 +7,8 @@ * - Delegating ID management and event writing to OutboundBridge */ -import { Agent } from '@mariozechner/pi-agent-core' -import { getModel, streamSimple } from '@mariozechner/pi-ai' +import { Agent } from '@earendil-works/pi-agent-core' +import { getModel, streamSimple } from '@earendil-works/pi-ai/compat' import { createOutboundBridge } from './outbound-bridge' import { MOONSHOT_PROVIDER, getMoonshotModel } from './moonshot-models' import { runtimeLog } from './log' @@ -25,13 +25,13 @@ import type { AgentMessage, AgentTool, StreamFn, -} from '@mariozechner/pi-agent-core' +} from '@earendil-works/pi-agent-core' import type { KnownProvider, Model, - Provider, + ProviderId, SimpleStreamOptions, -} from '@mariozechner/pi-ai' +} from '@earendil-works/pi-ai/compat' import type { LLMContentBlock, LLMMessage, LLMMessageContent } from './types' /** @@ -68,7 +68,7 @@ function parseReasoningSummary(text: string): { export interface PiAdapterOptions { systemPrompt: string model: string | Model - provider?: Provider + provider?: ProviderId tools: Array streamFn?: StreamFn getApiKey?: ( @@ -120,7 +120,7 @@ type PiAgentAdapterFactory = (config: PiAgentAdapterConfig) => PiAgentHandle export function resolvePiModel(opts: { model: string | Model - provider?: Provider + provider?: ProviderId }): Model { if (typeof opts.model !== `string`) { return opts.model @@ -169,15 +169,55 @@ function toAgentContent(content: LLMMessageContent): Array { return content.map(toAgentContentBlock) } +type AssistantAgentMessage = Extract + +type HistoryModel = Pick, `api` | `provider` | `id`> + +function createHistoryAssistantMessage( + content: Array, + model?: HistoryModel +): AssistantAgentMessage { + return { + role: `assistant`, + content, + api: model?.api ?? `unknown`, + provider: model?.provider ?? `unknown`, + model: model?.id ?? `unknown`, + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + total: 0, + }, + }, + stopReason: `stop`, + timestamp: Date.now(), + } as AssistantAgentMessage +} + +function isAssistantAgentMessage( + message: AgentMessage | undefined +): message is AssistantAgentMessage { + return message?.role === `assistant` +} + export function toAgentHistory( - messages: Array + messages: Array, + model?: HistoryModel ): Array { const history: Array = [] const toolNamesById = new Map() - const lastAssistant = (): AgentMessage | undefined => { + const lastAssistant = (): AssistantAgentMessage | undefined => { const last = history[history.length - 1] - return last?.role === `assistant` ? last : undefined + return isAssistantAgentMessage(last) ? last : undefined } for (const message of messages) { @@ -212,11 +252,7 @@ export function toAgentHistory( prevContent.push(...content) } } else { - history.push({ - role: `assistant`, - content, - timestamp: Date.now(), - } as AgentMessage) + history.push(createHistoryAssistantMessage(content, model)) } break } @@ -234,11 +270,7 @@ export function toAgentHistory( if (prev) { ;(prev.content as Array).push(block) } else { - history.push({ - role: `assistant`, - content: [block], - timestamp: Date.now(), - } as AgentMessage) + history.push(createHistoryAssistantMessage([block], model)) } break } @@ -272,8 +304,6 @@ export function createPiAgentAdapter( config.writeEvent, opts.onStepEnd ? { onStepEnd: opts.onStepEnd } : undefined ) - const history = toAgentHistory(config.messages) - let running = false let disposed = false let stepStartTime = 0 @@ -286,6 +316,7 @@ export function createPiAgentAdapter( model: opts.model, ...(opts.provider && { provider: opts.provider }), }) + const history = toAgentHistory(config.messages, model) const modelTimeoutMs = opts.modelTimeoutMs ?? DEFAULT_MODEL_TIMEOUT_MS const modelMaxRetries = opts.modelMaxRetries ?? DEFAULT_MODEL_MAX_RETRIES diff --git a/packages/agents-runtime/src/process-wake.ts b/packages/agents-runtime/src/process-wake.ts index 472cba39b5..0347ba87b7 100644 --- a/packages/agents-runtime/src/process-wake.ts +++ b/packages/agents-runtime/src/process-wake.ts @@ -75,6 +75,7 @@ interface ClaimCallbackResponse { claimToken?: string token?: string writeToken?: string + next_wake?: boolean error?: { code?: string; message?: string } } @@ -84,6 +85,12 @@ interface RawClaimCallbackResponse extends Omit { const DEFAULT_IDLE_TIMEOUT = 20_000 const DEFAULT_HEARTBEAT_INTERVAL = 10_000 + +function appendAll(target: Array, items: Array): void { + for (const item of items) { + target.push(item) + } +} type EntityStreamOptions = NonNullable< Parameters[3] > @@ -179,12 +186,14 @@ function withRegisteredManifestEntry( } } -async function latestNewRunKey( +async function latestStartedNewRunKey( db: EntityStreamDBWithActions, existingRunKeys: ReadonlySet ): Promise { const runs = await queryOnce((q) => q.from({ runs: db.collections.runs })) - return runs.filter((run) => !existingRunKeys.has(run.key)).at(-1)?.key + return runs + .filter((run) => !existingRunKeys.has(run.key) && run.status === `started`) + .at(-1)?.key } async function resolveHeadersProvider( @@ -551,7 +560,29 @@ export async function processWake( failBackgroundWake(error, `WRITE_FAILED`) }, }) + const producedRunStatuses = new Map< + string, + `started` | `completed` | `failed` + >() + const producedRunOrder: Array = [] const writeEvent = (event: ChangeEvent): void => { + if ( + event.type === `run` && + event.value && + typeof event.value === `object` + ) { + const status = (event.value as { status?: unknown }).status + if ( + status === `started` || + status === `completed` || + status === `failed` + ) { + if (!producedRunStatuses.has(event.key)) { + producedRunOrder.push(event.key) + } + producedRunStatuses.set(event.key, status) + } + } producer.append(JSON.stringify(event)) } @@ -644,7 +675,47 @@ export async function processWake( return left < right ? -1 : 1 } + const firstPendingRunnableFreshOffset = (): string | null => { + for (const batch of pendingLiveBatches) { + const events = toChangeEvents(batch) + const cancelledInboxKeys = new Set( + events.filter(isInboxCancellationEvent).map(inboxEventKey) + ) + for (const event of events) { + if (event.type === `wake`) { + return event.headers.offset ?? batch.offset + } + if (isInboxEvent(event)) { + if (cancelledInboxKeys.has(inboxEventKey(event))) continue + const value = event.value as + | { + payload?: unknown + mode?: `immediate` | `queued` | `paused` | `steer` + status?: `pending` | `processed` | `cancelled` + } + | undefined + if ( + value?.status !== `cancelled` && + (value?.payload !== undefined || + value?.mode === `queued` || + value?.mode === `steer`) + ) { + return event.headers.offset ?? batch.offset + } + } + } + } + return null + } + const setSafeAckOffset = (offset: string): void => { + const pendingFreshOffset = firstPendingRunnableFreshOffset() + if ( + pendingFreshOffset !== null && + compareOffsets(offset, pendingFreshOffset) >= 0 + ) { + return + } if (compareOffsets(offset, safeAckOffset) > 0) { safeAckOffset = offset } @@ -1075,7 +1146,7 @@ export async function processWake( // ctx.events so no child result is silently acked without being visible. pendingLiveBatches.shift() batches.push(batch) - deltaEvents.push(...changeEvents) + appendAll(deltaEvents, changeEvents) if (freshKind === `inbox`) { selectedKind = `inbox` @@ -1130,7 +1201,7 @@ export async function processWake( if (!preloaded) { const changeEvents = toChangeEvents(batch) if (changeEvents.length > 0) { - catchUpEvents.push(...changeEvents) + appendAll(catchUpEvents, changeEvents) } lastCatchUpOffset = batch.offset return @@ -1145,7 +1216,7 @@ export async function processWake( handleLatestSignalEvents(changeEvents) - catchUpEvents.push(...changeEvents) + appendAll(catchUpEvents, changeEvents) if ( resolveCurrentWakeReady !== null && @@ -2367,7 +2438,22 @@ export async function processWake( ? setupErr.code : `HANDLER_FAILED` log.error(`handler failed for ${entityUrl}:`, errMsg) - const failedRunKey = await latestNewRunKey(db, existingRunKeys) + const failedRunKey = + (await latestStartedNewRunKey(db, existingRunKeys)) ?? + [...producedRunOrder] + .reverse() + .find((key) => producedRunStatuses.get(key) === `started`) + if (failedRunKey) { + writeEvent( + entityStateSchema.runs.update({ + key: failedRunKey, + value: { + status: `failed`, + finish_reason: `error`, + } as never, + }) as ChangeEvent + ) + } writeEvent( entityStateSchema.errors.insert({ key: `error-${epoch}-${crypto.randomUUID()}`, @@ -2604,7 +2690,7 @@ export async function processWake( log.info(`shutdown requested, sending done callback at checkpoint`) } try { - await sendDone( + const doneResponse = await sendDone( callback, activeClaimToken, claimHeaderConfig, @@ -2612,6 +2698,9 @@ export async function processWake( streamPath, doneOffset === `-1` ? null : doneOffset ) + if (doneResponse.next_wake) { + config.onDoneNextWake?.(streamPath) + } } catch (err) { cleanupErrors.push(toError(err)) } @@ -2653,7 +2742,7 @@ async function sendDone( epoch: number, streamPath: string, offset: string | null -): Promise { +): Promise { const response = await fetch(callback, { method: `POST`, headers: await createClaimCallbackHeaders(claimHeaderConfig, token), @@ -2664,15 +2753,16 @@ async function sendDone( }), }) + const body = await response.text().catch(() => ``) if (!response.ok) { - let body = `` - try { - body = await response.text() - } catch { - body = `` - } throw new Error( `Done callback failed (${response.status}): ${body || response.statusText}` ) } + + const raw = parseClaimCallbackResponseBody(body) + return { + ...raw, + ok: raw.ok === undefined ? response.ok && raw.error === undefined : raw.ok, + } } diff --git a/packages/agents-runtime/src/pull-wake-runner.ts b/packages/agents-runtime/src/pull-wake-runner.ts index 04175fff6f..b3345f24b9 100644 --- a/packages/agents-runtime/src/pull-wake-runner.ts +++ b/packages/agents-runtime/src/pull-wake-runner.ts @@ -29,6 +29,12 @@ export interface PullWakeRunnerConfig { wakeStreamPath?: string heartbeatIntervalMs?: number eventHeartbeatThrottleMs?: number + /** + * If the heartbeat timer fires after a wall-clock gap larger than this, + * assume the process resumed from sleep or a long event-loop stall and force + * the long-lived wake stream to reconnect. Defaults to the runner lease. + */ + resumeGapResetMs?: number leaseMs?: number heartbeatPath?: string claimPath?: string @@ -57,6 +63,7 @@ export interface PullWakeStreamResponse { export interface PullWakeRunner { start: () => void + reconnect: () => void stop: () => Promise waitForStopped: () => Promise readonly running: boolean @@ -108,6 +115,7 @@ export function createPullWakeRunner( let heartbeatTimer: ReturnType | null = null let eventHeartbeatTimer: ReturnType | null = null let heartbeatInFlight: Promise | null = null + let heartbeatInFlightSignal: AbortSignal | null = null let heartbeatPending = false let currentOffset = config.offset ?? `-1` let startedAt: string | null = null @@ -128,7 +136,7 @@ export function createPullWakeRunner( let stopPromise: Promise | null = null const claimActors = new Set>() const claimingStreamPaths = new Set() - const deferredWakeEventsByStreamPath = new Map() + const deferredWakeEventsByStreamPath = new Map() const deferredWakeTimersByStreamPath = new Map< string, ReturnType @@ -145,6 +153,8 @@ export function createPullWakeRunner( config.eventHeartbeatThrottleMs ?? DEFAULT_EVENT_HEARTBEAT_THROTTLE_MS ) const leaseMs = config.leaseMs ?? heartbeatIntervalMs * 3 + const resumeGapResetMs = config.resumeGapResetMs ?? leaseMs + let lastHeartbeatTickAt = Date.now() const heartbeatPath = config.heartbeatPath ?? `/_electric/runners/${encodeURIComponent(config.runnerId)}/heartbeat` @@ -235,10 +245,15 @@ export function createPullWakeRunner( const requestHeartbeat = (signal: AbortSignal): void => { if (signal.aborted) return heartbeatPending = true - if (heartbeatInFlight) return - heartbeatInFlight = flushHeartbeats(signal).finally(() => { - heartbeatInFlight = null + if (heartbeatInFlight && heartbeatInFlightSignal === signal) return + const inFlight = flushHeartbeats(signal).finally(() => { + if (heartbeatInFlight === inFlight) { + heartbeatInFlight = null + heartbeatInFlightSignal = null + } }) + heartbeatInFlight = inFlight + heartbeatInFlightSignal = signal } const flushHeartbeats = async (signal: AbortSignal): Promise => { @@ -290,8 +305,25 @@ export function createPullWakeRunner( const startHeartbeat = (signal: AbortSignal): void => { if (heartbeatIntervalMs <= 0) return + lastHeartbeatTickAt = Date.now() requestHeartbeat(signal) heartbeatTimer = setInterval(() => { + const now = Date.now() + const elapsedMs = now - lastHeartbeatTickAt + lastHeartbeatTickAt = now + if ( + resumeGapResetMs > 0 && + elapsedMs > resumeGapResetMs && + isRunningState() && + !signal.aborted + ) { + actor.send({ + type: `STREAM_RESET`, + error: new Error( + `Pull-wake runner heartbeat timer resumed after ${elapsedMs}ms gap` + ), + }) + } requestHeartbeat(signal) }, heartbeatIntervalMs) } @@ -358,35 +390,66 @@ export function createPullWakeRunner( deferredWakeEventsByStreamPath.clear() } - const scheduleDeferredWakeClaim = ( - event: PullWakeEvent, + const drainQueuedWakeClaims = ( + streamPath: string, signal: AbortSignal ): void => { - const streamPath = normalizeStreamPath(event.stream) - deferredWakeEventsByStreamPath.set(streamPath, event) - if (deferredWakeTimersByStreamPath.has(streamPath)) return - - const retry = (): void => { + const timer = deferredWakeTimersByStreamPath.get(streamPath) + if (timer) { + clearTimeout(timer) deferredWakeTimersByStreamPath.delete(streamPath) - const deferredEvent = deferredWakeEventsByStreamPath.get(streamPath) - if (!deferredEvent || signal.aborted || !isRunningState()) { - deferredWakeEventsByStreamPath.delete(streamPath) - return - } - if (hasActiveStreamClaim(streamPath)) { - scheduleDeferredWakeClaim(deferredEvent, signal) - return - } + } + + const deferredEvents = deferredWakeEventsByStreamPath.get(streamPath) + if (!deferredEvents?.length || signal.aborted || !isRunningState()) { + deferredWakeEventsByStreamPath.delete(streamPath) + return + } + if (hasActiveStreamClaim(streamPath)) { + scheduleDeferredWakeRetry(streamPath, signal) + return + } + const deferredEvent = deferredEvents.shift()! + if (deferredEvents.length === 0) { deferredWakeEventsByStreamPath.delete(streamPath) - spawnClaimActor(deferredEvent, signal) } + spawnClaimActor(deferredEvent, signal) + if (deferredEvents.length > 0) { + scheduleDeferredWakeRetry(streamPath, signal) + } + } + + const scheduleDeferredWakeRetry = ( + streamPath: string, + signal: AbortSignal + ): void => { + if (deferredWakeTimersByStreamPath.has(streamPath)) return - const timer = setTimeout(retry, DEFERRED_WAKE_RETRY_MS) + const timer = setTimeout( + () => drainQueuedWakeClaims(streamPath, signal), + DEFERRED_WAKE_RETRY_MS + ) timer.unref?.() deferredWakeTimersByStreamPath.set(streamPath, timer) } + const scheduleDeferredWakeClaim = ( + event: PullWakeEvent, + signal: AbortSignal + ): void => { + const streamPath = normalizeStreamPath(event.stream) + const deferredEvents = deferredWakeEventsByStreamPath.get(streamPath) ?? [] + deferredEvents.push(event) + deferredWakeEventsByStreamPath.set(streamPath, deferredEvents) + if (hasActiveStreamClaim(streamPath)) { + recordClaimSkipped() + scheduleDeferredWakeRetry(streamPath, signal) + return + } + drainQueuedWakeClaims(streamPath, signal) + } + const claimWake = async ( event: PullWakeEvent, signal: AbortSignal @@ -459,6 +522,12 @@ export function createPullWakeRunner( config.runtime.dispatchWake(notification, { claimHeaders: resolveClaimHeaders, claimTokenHeader: config.claimTokenHeader, + onDoneNextWake: (nextStreamPath) => { + scheduleDeferredWakeClaim( + { ...event, stream: nextStreamPath }, + signal + ) + }, }) } catch (err) { reportError(err) @@ -523,7 +592,7 @@ export function createPullWakeRunner( eventsReceived++ notifyHeartbeatChange() const signal = controller?.signal - if (signal && !signal.aborted) spawnClaimActor(event, signal) + if (signal && !signal.aborted) scheduleDeferredWakeClaim(event, signal) }, onOffset: (offset) => { if (offset !== currentOffset) { @@ -599,6 +668,13 @@ export function createPullWakeRunner( startHeartbeat(controller.signal) actor.send({ type: `START` }) }, + reconnect() { + if (!isRunningState()) return + actor.send({ + type: `STREAM_RESET`, + error: new Error(`Pull-wake runner reconnect requested`), + }) + }, async stop() { if (stopPromise) return stopPromise if (actor.getSnapshot().matches(`stopped`)) return diff --git a/packages/agents-runtime/src/tools/bash.ts b/packages/agents-runtime/src/tools/bash.ts index 545d058f84..85a6a83a3b 100644 --- a/packages/agents-runtime/src/tools/bash.ts +++ b/packages/agents-runtime/src/tools/bash.ts @@ -1,6 +1,6 @@ import { Type } from '@sinclair/typebox' import type { Sandbox } from '../sandbox/types' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' const DEFAULT_TIMEOUT_MS = 120_000 const MAX_TIMEOUT_MS = 600_000 diff --git a/packages/agents-runtime/src/tools/brave-search.ts b/packages/agents-runtime/src/tools/brave-search.ts index 46b5c42fa4..4316a4197f 100644 --- a/packages/agents-runtime/src/tools/brave-search.ts +++ b/packages/agents-runtime/src/tools/brave-search.ts @@ -1,6 +1,6 @@ import Anthropic from '@anthropic-ai/sdk' import { Type } from '@sinclair/typebox' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' const BRAVE_API_URL = `https://api.search.brave.com/res/v1/web/search` const SEARCH_FALLBACK_MODEL = `claude-haiku-4-5-20251001` diff --git a/packages/agents-runtime/src/tools/edit.ts b/packages/agents-runtime/src/tools/edit.ts index d21605d6ed..afc37c1e60 100644 --- a/packages/agents-runtime/src/tools/edit.ts +++ b/packages/agents-runtime/src/tools/edit.ts @@ -4,7 +4,7 @@ import { Type } from '@sinclair/typebox' import { runtimeLog } from '../log' import { SandboxError } from '../sandbox/types' import type { Sandbox } from '../sandbox/types' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' export function createEditTool(sandbox: Sandbox): AgentTool { return { diff --git a/packages/agents-runtime/src/tools/fetch-url.ts b/packages/agents-runtime/src/tools/fetch-url.ts index 63c6d60cff..7b08725eac 100644 --- a/packages/agents-runtime/src/tools/fetch-url.ts +++ b/packages/agents-runtime/src/tools/fetch-url.ts @@ -6,7 +6,7 @@ import TurndownService from 'turndown' import { completeWithLowCostModel } from '../model-runner' import { SandboxError } from '../sandbox/types' import type { Sandbox } from '../sandbox/types' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' import type { LowCostModelCatalog, LowCostModelConfig } from '../model-runner' const MAX_RAW_CHARS = 100_000 diff --git a/packages/agents-runtime/src/tools/read-file.ts b/packages/agents-runtime/src/tools/read-file.ts index bfae4bb147..c1d23f2d59 100644 --- a/packages/agents-runtime/src/tools/read-file.ts +++ b/packages/agents-runtime/src/tools/read-file.ts @@ -2,7 +2,7 @@ import { Type } from '@sinclair/typebox' import { runtimeLog } from '../log' import { SandboxError } from '../sandbox/types' import type { Sandbox } from '../sandbox/types' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' const MAX_FILE_SIZE = 512 * 1024 // 512 KB const DEFAULT_READ_LIMIT_LINES = 2_000 diff --git a/packages/agents-runtime/src/tools/schedules.ts b/packages/agents-runtime/src/tools/schedules.ts index b529ba80ad..2617008217 100644 --- a/packages/agents-runtime/src/tools/schedules.ts +++ b/packages/agents-runtime/src/tools/schedules.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'node:crypto' import { Type } from '@sinclair/typebox' import { runtimeLog } from '../log' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' import type { EntityStreamDBWithActions } from '../entity-stream-db' type ScheduleManifest = { diff --git a/packages/agents-runtime/src/tools/webhook-sources.ts b/packages/agents-runtime/src/tools/webhook-sources.ts index bcfbf98c6c..365849e34b 100644 --- a/packages/agents-runtime/src/tools/webhook-sources.ts +++ b/packages/agents-runtime/src/tools/webhook-sources.ts @@ -5,7 +5,7 @@ import { defaultWebhookSourceSubscriptionLifetime, webhookSourceSubscriptionManifestKey, } from '../webhook-sources' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' import type { EntityStreamDBWithActions } from '../types' import type { WebhookSourceContract, diff --git a/packages/agents-runtime/src/tools/write.ts b/packages/agents-runtime/src/tools/write.ts index 838a118712..11665ec4a7 100644 --- a/packages/agents-runtime/src/tools/write.ts +++ b/packages/agents-runtime/src/tools/write.ts @@ -4,7 +4,7 @@ import { Type } from '@sinclair/typebox' import { runtimeLog } from '../log' import { SandboxError } from '../sandbox/types' import type { Sandbox } from '../sandbox/types' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' export function createWriteTool(sandbox: Sandbox): AgentTool { return { diff --git a/packages/agents-runtime/src/types.ts b/packages/agents-runtime/src/types.ts index 8cee5d4722..9ef8096ae1 100644 --- a/packages/agents-runtime/src/types.ts +++ b/packages/agents-runtime/src/types.ts @@ -22,8 +22,12 @@ import type { import type { AgentTool as PiAgentTool, StreamFn, -} from '@mariozechner/pi-agent-core' -import type { Model, Provider, SimpleStreamOptions } from '@mariozechner/pi-ai' +} from '@earendil-works/pi-agent-core' +import type { + Model, + ProviderId, + SimpleStreamOptions, +} from '@earendil-works/pi-ai/compat' import type { EntityStreamDB as RuntimeEntityStreamDB, EntityStreamDBWithActions as RuntimeEntityStreamDBWithActions, @@ -788,6 +792,11 @@ export interface ProcessWakeConfig { }) => Array | Promise> /** Optional shutdown signal to end idle waits during host teardown. */ shutdownSignal?: AbortSignal + /** + * Called when the Durable Streams done callback reports that releasing this + * claim left pending work for the same subscription. + */ + onDoneNextWake?: (streamPath: string) => void /** Idle timeout in ms before closing the wake (default: 20_000) */ idleTimeout?: number /** Heartbeat interval in ms (default: 10_000) */ @@ -953,7 +962,7 @@ export type AgentModel = string | Model export interface AgentConfig { systemPrompt: string model: AgentModel - provider?: Provider + provider?: ProviderId tools: Array streamFn?: StreamFn getApiKey?: ( diff --git a/packages/agents-runtime/test/compaction-trigger.test.ts b/packages/agents-runtime/test/compaction-trigger.test.ts index db0127d992..9f5dd860bd 100644 --- a/packages/agents-runtime/test/compaction-trigger.test.ts +++ b/packages/agents-runtime/test/compaction-trigger.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it } from 'vitest' -import { createAssistantMessageEventStream } from '@mariozechner/pi-ai' +import { createAssistantMessageEventStream } from '@earendil-works/pi-ai/compat' import { buildStreamFixture, createTestHandlerContext, diff --git a/packages/agents-runtime/test/context-factory.test.ts b/packages/agents-runtime/test/context-factory.test.ts index e8e4f7a4b8..881489585f 100644 --- a/packages/agents-runtime/test/context-factory.test.ts +++ b/packages/agents-runtime/test/context-factory.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest' -import { createAssistantMessageEventStream } from '@mariozechner/pi-ai' +import { createAssistantMessageEventStream } from '@earendil-works/pi-ai/compat' import { getCronStreamPath } from '../src/cron-utils' import { createHandlerContext } from '../src/context-factory' import { ENTITY_COLLECTIONS } from '../src/entity-schema' diff --git a/packages/agents-runtime/test/model-runner.test.ts b/packages/agents-runtime/test/model-runner.test.ts index 2f082e1d17..1b3db40c7d 100644 --- a/packages/agents-runtime/test/model-runner.test.ts +++ b/packages/agents-runtime/test/model-runner.test.ts @@ -1,9 +1,9 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' const completeSimple = vi.fn() -const getModel = vi.fn(() => ({ provider: `openai-codex`, id: `gpt-5.4-mini` })) +const getModel = vi.fn(() => ({ provider: `openai-codex`, id: `gpt-5.6-luna` })) -vi.mock(`@mariozechner/pi-ai`, () => ({ completeSimple, getModel })) +vi.mock(`@earendil-works/pi-ai/compat`, () => ({ completeSimple, getModel })) const { completeWithLowCostModel, @@ -33,7 +33,7 @@ describe(`completeWithLowCostModel`, () => { await completeWithLowCostModel({ catalog: { choices: [ - { provider: `openai-codex`, id: `gpt-5.4-mini`, reasoning: false }, + { provider: `openai-codex`, id: `gpt-5.6-luna`, reasoning: false }, ], }, purpose: `URL extraction`, @@ -54,7 +54,7 @@ describe(`completeWithLowCostModel`, () => { await completeWithLowCostModel({ catalog: { choices: [ - { provider: `openai-codex`, id: `gpt-5.4-mini`, reasoning: false }, + { provider: `openai-codex`, id: `gpt-5.6-luna`, reasoning: false }, ], }, purpose: `URL extraction`, diff --git a/packages/agents-runtime/test/pi-adapter.test.ts b/packages/agents-runtime/test/pi-adapter.test.ts index 45a3bb8128..ea7db607e8 100644 --- a/packages/agents-runtime/test/pi-adapter.test.ts +++ b/packages/agents-runtime/test/pi-adapter.test.ts @@ -4,18 +4,19 @@ import { resolvePiModel, toAgentHistory, } from '../src/pi-adapter' -import { createAssistantMessageEventStream } from '@mariozechner/pi-ai' +import { createAssistantMessageEventStream } from '@earendil-works/pi-ai/compat' import { Type } from '@sinclair/typebox' import type { OutboundIdSeed } from '../src/outbound-bridge' import type { LLMMessage } from '../src/types' import type { ChangeEvent } from '@durable-streams/state' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' +import { estimateContextTokens } from '@earendil-works/pi-agent-core' import type { AssistantMessage, Model, ToolResultMessage, UserMessage, -} from '@mariozechner/pi-ai' +} from '@earendil-works/pi-ai/compat' interface PiAgentAdapterConfig { entityUrl: string @@ -710,7 +711,7 @@ describe(`createPiAgentAdapter`, () => { content: [], api: `openai-codex-responses`, provider: `openai-codex`, - model: `gpt-5.4`, + model: `gpt-5.6-terra`, usage, stopReason: `stop`, timestamp: Date.now(), @@ -732,7 +733,7 @@ describe(`createPiAgentAdapter`, () => { const factory = createPiAgentAdapter({ systemPrompt: `Test system prompt`, provider: `openai-codex`, - model: `gpt-5.4`, + model: `gpt-5.6-terra`, tools: [], streamFn: () => { const stream = createAssistantMessageEventStream() @@ -958,6 +959,41 @@ describe(`toAgentHistory`, () => { expect(msg?.role).toBe(`assistant`) const content = msg?.content as Array<{ type: string; text: string }> expect(content[0]?.text).toBe(`Hello world`) + expect(msg?.usage).toEqual({ + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + total: 0, + }, + }) + expect(() => estimateContextTokens(history)).not.toThrow() + }) + + it(`uses the active model metadata for reconstructed assistant messages`, () => { + const model = resolvePiModel({ + provider: `openai-codex`, + model: `gpt-5.6-terra`, + }) + + const history = toAgentHistory( + [{ role: `assistant`, content: `Prior response` }], + model + ) + const assistant = history[0] as AssistantMessage + + expect(assistant).toMatchObject({ + api: model.api, + provider: model.provider, + model: model.id, + stopReason: `stop`, + }) }) it(`includes custom event turns from the context pipeline as user messages`, () => { diff --git a/packages/agents-runtime/test/process-wake.test.ts b/packages/agents-runtime/test/process-wake.test.ts index 49c3a5460f..c70922bc28 100644 --- a/packages/agents-runtime/test/process-wake.test.ts +++ b/packages/agents-runtime/test/process-wake.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createTransaction } from '@durable-streams/state/db' -import { createAssistantMessageEventStream } from '@mariozechner/pi-ai' +import { createAssistantMessageEventStream } from '@earendil-works/pi-ai/compat' import { getCronSourceRef } from '../src/cron-utils' import { buildWebhookSourceManifestEntry, @@ -19,7 +19,7 @@ import type { MockInstance } from 'vitest' import type { ProcessWakeConfig, WebhookNotification } from '../src/types' import type { ChangeEvent } from '@durable-streams/state' import type { ErrorEvent, Manifest } from '../src/entity-schema' -import type { AssistantMessage } from '@mariozechner/pi-ai' +import type { AssistantMessage } from '@earendil-works/pi-ai/compat' // --------------------------------------------------------------------------- // Mock @durable-streams/client @@ -46,8 +46,12 @@ const { mockInitialManifests, mockEntityOnEvent, mockEntityOnBatch, + mockProducerLastSuccessfulOffset, } = vi.hoisted(() => ({ mockProducerAppend: vi.fn(), + mockProducerLastSuccessfulOffset: { + current: undefined as string | undefined, + }, mockProducerFlush: vi.fn().mockResolvedValue(undefined), mockProducerDetach: vi.fn().mockResolvedValue(undefined), mockConstructedProducers: [] as Array<{ @@ -131,6 +135,9 @@ vi.mock(`@durable-streams/client`, async (importOriginal) => { append = mockProducerAppend flush = mockProducerFlush detach = mockProducerDetach + get lastSuccessfulOffset() { + return mockProducerLastSuccessfulOffset.current + } } return { ...actual, @@ -208,7 +215,9 @@ vi.mock(`../src/entity-stream-db`, () => ({ ? errors : event.type === `signal` ? signals - : undefined + : event.type === `run` + ? runs + : undefined if (!collection) { return } @@ -457,6 +466,7 @@ describe(`processWake`, () => { mockSourceEvents.current = [] mockStreamOffset.value = `10_100` mockDbOffset.value = `10_100` + mockProducerLastSuccessfulOffset.current = undefined mockEntityOnEvent.current = null mockEntityOnBatch.current = null mockStreamHead.mockResolvedValue({ @@ -505,6 +515,72 @@ describe(`processWake`, () => { fetchMock.mockRestore() }) + it(`marks a newly-started run failed when the handler throws before ending it`, async () => { + defineEntity(`test-agent`, { + handler: (ctx) => { + ctx.recordRun() + throw new Error(`boom after run start`) + }, + }) + + await expect(processWake(makeNotification(), BASE_CONFIG)).rejects.toThrow( + `boom after run start` + ) + + const events = mockProducerAppend.mock.calls.map(([body]) => + JSON.parse(String(body)) + ) as Array + const startedRun = events.find( + (event) => + event.type === `run` && + event.headers.operation === `insert` && + (event.value as { status?: string }).status === `started` + ) + + expect(startedRun).toBeDefined() + expect(events).toContainEqual( + expect.objectContaining({ + type: `run`, + key: startedRun!.key, + headers: expect.objectContaining({ operation: `update` }), + value: expect.objectContaining({ + status: `failed`, + finish_reason: `error`, + }), + }) + ) + }) + + it(`does not mark a completed run failed when later handler cleanup throws`, async () => { + defineEntity(`test-agent`, { + handler: (ctx) => { + ctx.db.collections.runs.insert({ + key: `externally-visible-run`, + status: `completed`, + finish_reason: `stop`, + }) + throw new Error(`boom after completed run`) + }, + }) + + await expect(processWake(makeNotification(), BASE_CONFIG)).rejects.toThrow( + `boom after completed run` + ) + + const events = mockProducerAppend.mock.calls.map(([body]) => + JSON.parse(String(body)) + ) as Array + + expect(events).not.toContainEqual( + expect.objectContaining({ + type: `run`, + key: `externally-visible-run`, + headers: expect.objectContaining({ operation: `update` }), + value: expect.objectContaining({ status: `failed` }), + }) + ) + }) + it(`returns null without acking for unknown entity types`, async () => { // No entity type registered — runtime should silently bail const result = await processWake(makeNotification(), BASE_CONFIG) @@ -560,6 +636,34 @@ describe(`processWake`, () => { expect(body.epoch).toBe(1) }) + it(`notifies the runner when done leaves another wake pending`, async () => { + defineEntity(`test-agent`, { + handler: () => {}, + }) + const onDoneNextWake = vi.fn() + fetchMock.mockImplementation((url, opts) => { + const body = opts?.body ? JSON.parse(String(opts.body)) : {} + if (String(url).includes(`/_electric/wakes/wake-abc`) && body.done) { + return Promise.resolve( + new Response(JSON.stringify({ ok: true, next_wake: true }), { + status: 200, + headers: { 'content-type': `application/json` }, + }) + ) + } + return Promise.resolve( + new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { 'content-type': `application/json` }, + }) + ) + }) + + await processWake(makeNotification(), { ...BASE_CONFIG, onDoneNextWake }) + + expect(onDoneNextWake).toHaveBeenCalledWith(`/streams/entity:agent-1`) + }) + it(`acks the local consumed offset on done even when the stream tail is ahead`, async () => { defineEntity(`test-agent`, { handler: () => {}, @@ -985,6 +1089,80 @@ describe(`processWake`, () => { ) }) + it(`does not ack queued inbox that arrived before SIGINT aborts an active run`, async () => { + const wakePayloads: Array = [] + let handlerStartedResolve: (() => void) | null = null + const handlerStarted = new Promise((resolve) => { + handlerStartedResolve = resolve + }) + let signalAbortedResolve: (() => void) | null = null + const signalAborted = new Promise((resolve) => { + signalAbortedResolve = resolve + }) + + defineEntity(`test-agent`, { + handler: async (ctx, wake) => { + wakePayloads.push(wake.payload) + handlerStartedResolve?.() + if (ctx.signal.aborted) { + signalAbortedResolve?.() + return + } + await new Promise((resolve) => { + ctx.signal.addEventListener( + `abort`, + () => { + signalAbortedResolve?.() + resolve() + }, + { once: true } + ) + }) + }, + }) + + const notification = makeNotification({ triggerEvent: `inbox` }) + notification.entity!.status = `running` + const wakePromise = processWake(notification, BASE_CONFIG) + await handlerStarted + + mockEntityOnBatch.current?.({ + items: [ + ev( + `inbox`, + `m-before-sigint`, + `insert`, + { payload: `should run in a later wake` }, + { offset: `10_500` } + ), + ev( + `signal`, + `sigint-handler`, + `insert`, + { signal: `SIGINT`, status: `unhandled` }, + { offset: `10_600` } + ), + ], + offset: `10_600`, + }) + + await signalAborted + await wakePromise + + expect(wakePayloads).toEqual([undefined]) + const doneCalls = fetchMock.mock.calls.filter( + ([url, opts]) => + String(url).includes(`/_electric/wakes/wake-abc`) && + (opts?.body as string | undefined)?.includes(`"done":true`) + ) + const body = JSON.parse(doneCalls.at(-1)![1]!.body as string) as { + acks: Array<{ path: string; offset: string }> + } + expect(body.acks).toEqual([ + { path: `/streams/entity:agent-1`, offset: `10_100` }, + ]) + }) + it(`does not continue fresh inbox work after SIGINT aborts an active run`, async () => { const wakePayloads: Array = [] let handlerStartedResolve: (() => void) | null = null @@ -1064,6 +1242,85 @@ describe(`processWake`, () => { ]) }) + it(`does not ack a queued inbox when SIGINT cleanup writes advance the stream tail`, async () => { + const wakePayloads: Array = [] + let handlerStartedResolve: (() => void) | null = null + const handlerStarted = new Promise((resolve) => { + handlerStartedResolve = resolve + }) + let signalAbortedResolve: (() => void) | null = null + const signalAborted = new Promise((resolve) => { + signalAbortedResolve = resolve + }) + + defineEntity(`test-agent`, { + handler: async (ctx, wake) => { + wakePayloads.push(wake.payload) + handlerStartedResolve?.() + await new Promise((resolve) => { + ctx.signal.addEventListener( + `abort`, + () => { + signalAbortedResolve?.() + resolve() + }, + { once: true } + ) + }) + }, + }) + + const notification = makeNotification({ triggerEvent: `inbox` }) + notification.entity!.status = `running` + const wakePromise = processWake(notification, BASE_CONFIG) + await handlerStarted + + mockEntityOnBatch.current?.({ + items: [ + ev( + `signal`, + `sigint-handler`, + `insert`, + { signal: `SIGINT`, status: `unhandled` }, + { offset: `10_500` } + ), + ], + offset: `10_500`, + }) + mockEntityOnBatch.current?.({ + items: [ + ev( + `inbox`, + `m-after-sigint`, + `insert`, + { payload: `should run in a later wake` }, + { offset: `10_600` } + ), + ], + offset: `10_600`, + }) + + await signalAborted + // Simulate cleanup writes (e.g. signal handled / aborted run rows) being + // appended after the queued inbox. Acking this offset would swallow the + // unprocessed inbox and prevent Durable Streams from issuing next_wake. + mockProducerLastSuccessfulOffset.current = `10_700` + await wakePromise + + expect(wakePayloads).toEqual([undefined]) + const doneCalls = fetchMock.mock.calls.filter( + ([url, opts]) => + String(url).includes(`/_electric/wakes/wake-abc`) && + (opts?.body as string | undefined)?.includes(`"done":true`) + ) + const body = JSON.parse(doneCalls.at(-1)![1]!.body as string) as { + acks: Array<{ path: string; offset: string }> + } + expect(body.acks).toEqual([ + { path: `/streams/entity:agent-1`, offset: `10_500` }, + ]) + }) + it(`waits for async signal handlers even when the main handler fails`, async () => { let handlerStartedResolve: (() => void) | null = null const handlerStarted = new Promise((resolve) => { diff --git a/packages/agents-runtime/test/pull-wake-runner.test.ts b/packages/agents-runtime/test/pull-wake-runner.test.ts index 519a9c86f2..a3b7a88d4a 100644 --- a/packages/agents-runtime/test/pull-wake-runner.test.ts +++ b/packages/agents-runtime/test/pull-wake-runner.test.ts @@ -123,6 +123,92 @@ describe(`createPullWakeRunner`, () => { await runner.stop() }) + it(`explicitly reconnects a stalled stream without aborting active wakes`, async () => { + vi.useFakeTimers() + const firstCancelled = deferred() + const secondCancelled = deferred() + let firstOffset = `-1` + const firstResponse: PullWakeStreamResponse = { + get offset() { + return firstOffset + }, + async *jsonStream() { + firstOffset = `42` + yield wakeEvent(`one`) + await firstCancelled.promise + }, + cancel: vi.fn(() => firstCancelled.resolve()), + closed: firstCancelled.promise, + } + const secondResponse: PullWakeStreamResponse = { + offset: `42`, + async *jsonStream() { + await secondCancelled.promise + }, + cancel: vi.fn(() => secondCancelled.resolve()), + closed: secondCancelled.promise, + } + const streamFactory = vi + .fn() + .mockResolvedValueOnce(firstResponse) + .mockResolvedValueOnce(secondResponse) + const activeWakeReleased = deferred() + const activeWakeCompleted = vi.fn() + let activeWake = false + vi.stubGlobal( + `fetch`, + vi.fn(async () => Response.json(notification(`one`))) + ) + const testRuntime = { + ...runtime(), + dispatchWake: vi.fn(() => { + activeWake = true + void activeWakeReleased.promise.then(() => { + activeWake = false + activeWakeCompleted() + }) + }), + isWakeActive: vi.fn((_streamPath: string) => activeWake), + } + const runner = createPullWakeRunner({ + baseUrl: `http://server`, + runnerId: `runner-1`, + runtime: testRuntime, + heartbeatIntervalMs: 0, + streamFactory, + }) + + runner.start() + await vi.waitFor(() => expect(runner.offset).toBe(`42`)) + await vi.waitFor(() => + expect(testRuntime.dispatchWake).toHaveBeenCalledTimes(1) + ) + expect(testRuntime.isWakeActive(`/chat/one/main`)).toBe(true) + + runner.reconnect() + runner.reconnect() + await vi.waitFor(() => + expect(firstResponse.cancel).toHaveBeenCalledTimes(1) + ) + await vi.advanceTimersByTimeAsync(1_000) + await vi.waitFor(() => expect(streamFactory).toHaveBeenCalledTimes(2)) + + expect(streamFactory).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ offset: `42` }) + ) + expect(testRuntime.abortWakes).not.toHaveBeenCalled() + expect(testRuntime.drainWakes).not.toHaveBeenCalled() + expect(testRuntime.isWakeActive(`/chat/one/main`)).toBe(true) + expect(activeWakeCompleted).not.toHaveBeenCalled() + + activeWakeReleased.resolve() + await vi.waitFor(() => expect(activeWakeCompleted).toHaveBeenCalledTimes(1)) + expect(testRuntime.isWakeActive(`/chat/one/main`)).toBe(false) + + await runner.stop() + }) + it(`claims compact DS wake events before dispatching runtime wakes`, async () => { const event = wakeEvent(`one`) const claimed = notification(`one`) @@ -170,6 +256,7 @@ describe(`createPullWakeRunner`, () => { expect(testRuntime.dispatchWake).toHaveBeenCalledWith(claimed, { claimHeaders: expect.any(Function), claimTokenHeader: `electric-claim-token`, + onDoneNextWake: expect.any(Function), }) expect(testRuntime.drainWakes).not.toHaveBeenCalled() expect(runner.offset).toBe(`42`) @@ -278,6 +365,160 @@ describe(`createPullWakeRunner`, () => { await runner.stop() }) + it(`retries every wake notification skipped while the same stream is already being claimed`, async () => { + const events = [ + { ...wakeEvent(`parent`), generation: 1 }, + { ...wakeEvent(`parent`), generation: 2 }, + { ...wakeEvent(`parent`), generation: 3 }, + ] + const claims = [`one`, `two`, `three`].map(notification) + const firstClaimResponse = deferred() + const fetchMock = vi + .fn() + .mockImplementationOnce( + (_input: RequestInfo | URL) => firstClaimResponse.promise + ) + .mockImplementationOnce(async (_input: RequestInfo | URL) => + Response.json(claims[1]) + ) + .mockImplementationOnce(async (_input: RequestInfo | URL) => + Response.json(claims[2]) + ) + vi.stubGlobal(`fetch`, fetchMock) + const testRuntime = runtime() + const streamFactory = vi.fn(async () => ({ + offset: `42`, + async *jsonStream() { + yield* events + }, + closed: Promise.resolve(), + })) + + const runner = createPullWakeRunner({ + baseUrl: `http://server`, + runnerId: `runner-1`, + runtime: testRuntime, + heartbeatIntervalMs: 0, + eventHeartbeatThrottleMs: 0, + streamFactory, + }) + + runner.start() + await waitFor(() => { + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + expect(testRuntime.dispatchWake).not.toHaveBeenCalled() + + firstClaimResponse.resolve(Response.json(claims[0])) + await waitFor(() => { + expect(fetchMock).toHaveBeenCalledTimes(3) + }) + await waitFor(() => { + expect(testRuntime.dispatchWake).toHaveBeenCalledTimes(3) + }) + + await runner.stop() + }) + + it(`does not let a new same-stream notification jump ahead of a queued trigger`, async () => { + const continueStream = deferred() + const firstClaimResponse = deferred() + const requestGenerations: Array = [] + const fetchMock = vi.fn( + async (_input: RequestInfo | URL, init?: RequestInit) => { + requestGenerations.push(JSON.parse(String(init?.body)).generation) + if (requestGenerations.length === 1) return firstClaimResponse.promise + return Response.json(notification(`coalesced`)) + } + ) + vi.stubGlobal(`fetch`, fetchMock) + const testRuntime = runtime() + const streamFactory = vi.fn(async () => ({ + offset: `42`, + async *jsonStream() { + yield { ...wakeEvent(`parent`), generation: 1 } + yield { ...wakeEvent(`parent`), generation: 2 } + await continueStream.promise + yield { ...wakeEvent(`parent`), generation: 3 } + }, + closed: Promise.resolve(), + })) + + const runner = createPullWakeRunner({ + baseUrl: `http://server`, + runnerId: `runner-1`, + runtime: testRuntime, + heartbeatIntervalMs: 0, + eventHeartbeatThrottleMs: 0, + streamFactory, + }) + + runner.start() + await waitFor(() => { + expect(requestGenerations).toEqual([1]) + }) + + firstClaimResponse.resolve(Response.json(notification(`one`))) + continueStream.resolve() + + await waitFor(() => { + expect(requestGenerations).toEqual([1, 2]) + }) + await waitFor(() => { + expect(requestGenerations).toEqual([1, 2, 3]) + }) + + await runner.stop() + }) + + it(`reclaims immediately when a dispatched wake reports pending work on done`, async () => { + const event = { ...wakeEvent(`one`), generation: 1 } + const claimed = notification(`one`) + const fetchMock = vi.fn(async (_input: RequestInfo | URL) => + Response.json(claimed) + ) + vi.stubGlobal(`fetch`, fetchMock) + let doneNextWake: ((streamPath: string) => void) | undefined + const testRuntime = { + ...runtime(), + dispatchWake: vi.fn((_notification, options) => { + doneNextWake = options?.onDoneNextWake + }), + } + const streamFactory = vi.fn(async () => ({ + offset: `42`, + async *jsonStream() { + yield event + }, + closed: Promise.resolve(), + })) + + const runner = createPullWakeRunner({ + baseUrl: `http://server`, + runnerId: `runner-1`, + runtime: testRuntime, + heartbeatIntervalMs: 0, + eventHeartbeatThrottleMs: 0, + streamFactory, + }) + + runner.start() + await waitFor(() => { + expect(testRuntime.dispatchWake).toHaveBeenCalledTimes(1) + }) + + doneNextWake?.(`/chat/one/main`) + + await waitFor(() => { + expect(fetchMock).toHaveBeenCalledTimes(2) + }) + await waitFor(() => { + expect(testRuntime.dispatchWake).toHaveBeenCalledTimes(2) + }) + + await runner.stop() + }) + it(`skips stale wake events when claim returns no pending work`, async () => { const event = wakeEvent(`one`) const fetchMock = vi.fn(async (_input: RequestInfo | URL) => @@ -797,6 +1038,69 @@ describe(`createPullWakeRunner`, () => { await runner.stop() }) + it(`forces the stream to reconnect when heartbeat timer observes a resume gap`, async () => { + vi.useFakeTimers() + const firstStreamOpened = deferred() + const secondStreamOpened = deferred() + const firstStreamClosed = deferred() + const secondStreamClosed = deferred() + const firstCancel = vi.fn(() => firstStreamClosed.resolve()) + const streamFactory = vi.fn(async () => { + if (streamFactory.mock.calls.length === 1) { + firstStreamOpened.resolve() + return { + async *jsonStream() { + await firstStreamClosed.promise + }, + cancel: firstCancel, + closed: firstStreamClosed.promise, + } + } + secondStreamOpened.resolve() + return { + async *jsonStream() { + await secondStreamClosed.promise + }, + cancel: () => secondStreamClosed.resolve(), + closed: secondStreamClosed.promise, + } + }) + vi.stubGlobal( + `fetch`, + vi.fn(async () => Response.json({})) + ) + + const runner = createPullWakeRunner({ + baseUrl: `http://server`, + runnerId: `runner-1`, + runtime: runtime(), + heartbeatIntervalMs: 10, + resumeGapResetMs: 25, + eventHeartbeatThrottleMs: 0, + streamFactory, + }) + + runner.start() + await firstStreamOpened.promise + + // Simulate the computer being asleep: wall-clock time jumps forward before + // the next heartbeat timer gets CPU again, while networking is healthy. + vi.setSystemTime(Date.now() + 60) + await vi.advanceTimersByTimeAsync(10) + + await waitFor(() => { + expect(firstCancel).toHaveBeenCalledWith(expect.any(Error)) + }) + + await vi.advanceTimersByTimeAsync(1_000) + await secondStreamOpened.promise + + expect(streamFactory).toHaveBeenCalledTimes(2) + expect(runner.getHealth().reconnect_count).toBe(1) + + await runner.stop() + }) + it(`forces the stream to reconnect after repeated heartbeat failures`, async () => { vi.useFakeTimers() const firstStreamOpened = deferred() diff --git a/packages/agents-server-conformance-tests/package.json b/packages/agents-server-conformance-tests/package.json index 4e53be6917..140843c239 100644 --- a/packages/agents-server-conformance-tests/package.json +++ b/packages/agents-server-conformance-tests/package.json @@ -41,14 +41,14 @@ "stylecheck": "eslint . --quiet" }, "dependencies": { - "@durable-streams/client": "^0.2.6", - "@electric-sql/client": "^1.5.22", + "@durable-streams/client": "https://pkg.pr.new/@durable-streams/client@293", + "@electric-sql/client": "^1.5.21", "fast-check": "^4.6.0", "vitest": "^4.1.0" }, "devDependencies": { "@electric-ax/agents-server": "workspace:*", - "@mariozechner/pi-agent-core": "^0.70.2", + "@earendil-works/pi-agent-core": "^0.80.5", "tsdown": "^0.9.0", "typescript": "^5.0.0" }, diff --git a/packages/agents-server-conformance-tests/src/mock-stream.ts b/packages/agents-server-conformance-tests/src/mock-stream.ts index 97365a5338..1756117b32 100644 --- a/packages/agents-server-conformance-tests/src/mock-stream.ts +++ b/packages/agents-server-conformance-tests/src/mock-stream.ts @@ -1,4 +1,4 @@ -import type { StreamFn } from '@mariozechner/pi-agent-core' +import type { StreamFn } from '@earendil-works/pi-agent-core' export function createMockStreamFn(text: string): StreamFn { return () => { diff --git a/packages/agents-server-ui/package.json b/packages/agents-server-ui/package.json index ffc51a19c2..28eab3cff0 100644 --- a/packages/agents-server-ui/package.json +++ b/packages/agents-server-ui/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@base-ui/react": "^1.4.1", - "@durable-streams/client": "^0.2.6", + "@durable-streams/client": "https://pkg.pr.new/@durable-streams/client@293", "@durable-streams/state": "^0.3.1", "@electric-ax/agents-runtime": "workspace:*", "@handlewithcare/react-prosemirror": "^3.0.6", diff --git a/packages/agents-server/package.json b/packages/agents-server/package.json index 7ddb70eb19..89e53d1a1f 100644 --- a/packages/agents-server/package.json +++ b/packages/agents-server/package.json @@ -45,14 +45,16 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.78.0", - "@durable-streams/client": "^0.2.6", + "@durable-streams/client": "https://pkg.pr.new/@durable-streams/client@293", "@durable-streams/server": "^0.3.7", "@durable-streams/state": "^0.3.1", "@electric-ax/agents-runtime": "workspace:*", "@electric-sql/client": "^1.5.22", - "@mariozechner/pi-agent-core": "^0.70.2", + "@earendil-works/pi-agent-core": "^0.80.5", "@opentelemetry/api": "^1.9.1", "@sinclair/typebox": "^0.34.48", + "@tanstack/db": "^0.6.7", + "@tanstack/electric-db-collection": "^0.3.5", "@whatwg-node/server": "^0.10.18", "ajv": "^8.18.0", "cron-parser": "^5.5.0", diff --git a/packages/agents-server/src/electric-agents/adapter-types.ts b/packages/agents-server/src/electric-agents/adapter-types.ts index 36120fbc8e..f97f5161ae 100644 --- a/packages/agents-server/src/electric-agents/adapter-types.ts +++ b/packages/agents-server/src/electric-agents/adapter-types.ts @@ -5,7 +5,7 @@ * SDK events into State Protocol writes via the provided writeEvent callback. */ -import type { AgentTool, StreamFn } from '@mariozechner/pi-agent-core' +import type { AgentTool, StreamFn } from '@earendil-works/pi-agent-core' /** A State Protocol event to be written via the adapter's writeEvent callback. */ export interface WriteEvent { diff --git a/packages/agents-server/src/entity-manager.ts b/packages/agents-server/src/entity-manager.ts index 200f518661..26d9c972f1 100644 --- a/packages/agents-server/src/entity-manager.ts +++ b/packages/agents-server/src/entity-manager.ts @@ -81,6 +81,7 @@ type SpawnPersistResult = [ PromiseSettledResult, ] type SpawnPersistJob = () => Promise + type WriteTokenValidator = ( entity: ElectricAgentsEntity, token: string @@ -429,12 +430,10 @@ export class EntityManager { electricUrl?: string, electricSecret?: string ): Promise { - if (electricUrl) { - await this.wakeRegistry.startSync(electricUrl, electricSecret) - return + if (!electricUrl) { + throw new Error(`WakeRegistry runtime requires an Electric URL`) } - - await this.wakeRegistry.loadRegistrations() + await this.wakeRegistry.startSync(electricUrl, electricSecret) } setWriteTokenValidator(validator: WriteTokenValidator): void { @@ -3292,7 +3291,7 @@ export class EntityManager { ): Promise { return await withSpan(`electric_agents.evaluateWakes`, async (span) => { span.setAttribute(ATTR.WAKE_SOURCE, sourceUrl) - const results = this.wakeRegistry.evaluate( + let results = await this.wakeRegistry.evaluate( sourceUrl, event, this.tenantId @@ -4079,9 +4078,19 @@ export class EntityManager { // Ensure the backing stream exists const exists = await this.streamClient.exists(streamPath) if (!exists) { - await this.streamClient.create(streamPath, { - contentType: `application/json`, - }) + try { + await this.streamClient.create(streamPath, { + contentType: `application/json`, + }) + } catch (err) { + const status = + err instanceof Error && `status` in err + ? (err as { status?: unknown }).status + : undefined + if (status !== 409 && !/\b409\b/.test(String(err))) { + throw err + } + } } const fireAt = getNextCronFireAt(spec.expression, spec.timezone) diff --git a/packages/agents-server/src/host.ts b/packages/agents-server/src/host.ts index de556f8bdd..21d281b614 100644 --- a/packages/agents-server/src/host.ts +++ b/packages/agents-server/src/host.ts @@ -113,11 +113,10 @@ export class AgentsHost { this.running = true try { - if (this.electricUrl) { - await this.wakeRegistry.startSync(this.electricUrl, this.electricSecret) - } else { - await this.wakeRegistry.loadRegistrations() + if (!this.electricUrl) { + throw new Error(`WakeRegistry runtime requires an Electric URL`) } + await this.wakeRegistry.startSync(this.electricUrl, this.electricSecret) if (this.startEntityBridgeManager) { await this.entityProjector.start() } diff --git a/packages/agents-server/src/server.ts b/packages/agents-server/src/server.ts index 8e265dca4d..74892d34f1 100644 --- a/packages/agents-server/src/server.ts +++ b/packages/agents-server/src/server.ts @@ -24,7 +24,7 @@ import { createEd25519WebhookSigner } from './webhook-signing.js' import type { DrizzleDB, PgClient } from './db/index.js' import type { Server } from 'node:http' import type { DurableStreamTestServer } from '@durable-streams/server' -import type { StreamFn } from '@mariozechner/pi-agent-core' +import type { StreamFn } from '@earendil-works/pi-agent-core' import type { AgentModel, EntityRegistry, diff --git a/packages/agents-server/src/wake-registry.ts b/packages/agents-server/src/wake-registry.ts index ff8ef6e75b..be2ff670d9 100644 --- a/packages/agents-server/src/wake-registry.ts +++ b/packages/agents-server/src/wake-registry.ts @@ -1,15 +1,35 @@ +import { snakeCamelMapper } from '@electric-sql/client' import { - ShapeStream, - isChangeMessage, - isControlMessage, -} from '@electric-sql/client' -import { and, eq } from 'drizzle-orm' + and as dbAnd, + createCollection, + createEffect, + createOptimisticAction, + eq as dbEq, + localOnlyCollectionOptions, + queryOnce, +} from '@tanstack/db' +import { and, eq, inArray, sql } from 'drizzle-orm' +import { electricCollectionOptions } from '@tanstack/electric-db-collection' import { wakeRegistrations } from './db/schema.js' import { serverLog } from './utils/log.js' import { electricUrlWithPath } from './utils/electric-url.js' import { DEFAULT_TENANT_ID } from './tenant.js' import type { DrizzleDB } from './db/index.js' -import type { Message, Row, Value } from '@electric-sql/client' +import type { Collection } from '@tanstack/db' + +class WakeRegistrationConflictError extends Error { + constructor(readonly row: WakeRegistrationCollectionRow) { + super(`Wake registration insert conflicted with an existing row`) + this.name = `WakeRegistrationConflictError` + } +} + +class WakeRegistrationStaleError extends Error { + constructor() { + super(`Wake registration row was no longer present`) + this.name = `WakeRegistrationStaleError` + } +} export interface WakeRegistration { tenantId?: string @@ -58,26 +78,46 @@ export interface WakeEvalResult { export type WakeTimeoutCallback = (result: WakeEvalResult) => void export type WakeDebounceCallback = (result: WakeEvalResult) => void -interface CachedWakeRegistration extends WakeRegistration { +export interface WakeRegistrationCollectionRow { + id: number tenantId: string - dbId: number - createdAt?: Date - timeoutConsumed?: boolean + subscriberUrl: string + sourceUrl: string + condition: WakeRegistration[`condition`] + debounceMs: number + timeoutMs: number + oneShot: boolean + timeoutConsumed: boolean + includeResponse: boolean + manifestKey: string | null + createdAt: Date } -interface WakeRegistrationShapeRow extends Row { - id: number - tenant_id: string - subscriber_url: string - source_url: string - condition: WakeRegistration[`condition`] & Value - debounce_ms: number - timeout_ms: number - one_shot: boolean - timeout_consumed: boolean - include_response: boolean - manifest_key: string | null - created_at: Date +type WakeRegistryMode = `unstarted` | `local-test` | `electric` + +type PersistInsertResult = { + txid: number + row: WakeRegistrationCollectionRow +} + +type DeleteRowsInput = { + rows: Array + persist: + | { + kind: `manifestKey` + tenantId: string + subscriberUrl: string + manifestKey: string + } + | { kind: `subscriber`; tenantId: string; subscriberUrl: string } + | { kind: `source`; tenantId: string; sourceUrl: string } + | { + kind: `subscriberAndSource` + tenantId: string + subscriberUrl: string + sourceUrl: string + } + | { kind: `oneShot` } } function wakeSourceEventId(event: Record): string { @@ -105,9 +145,18 @@ function sqlStringLiteral(value: string): string { return `'${value.replace(/'/g, `''`)}'` } +let nextWakeRegistryCollectionInstance = 1 + export class WakeRegistry { private db: DrizzleDB - private registrationCache = new Map>() + private registrationsCollection: Collection< + WakeRegistrationCollectionRow, + number, + any + > | null = null + private mode: WakeRegistryMode = `unstarted` + private nextLocalId = 1 + private registrationsEffect: { dispose(): Promise } | null = null private debounceTimers = new Map() private debounceBuffers = new Map< string, @@ -118,12 +167,7 @@ export class WakeRegistry { private timeoutDelivered = new Set() private timeoutCallbacks = new Map() private debounceCallbacks = new Map() - private syncElectricUrl: string | null = null - private syncElectricSecret: string | undefined - private syncAbortController: AbortController | null = null - private syncUnsubscribe: (() => void) | null = null - private syncReadyPromise: Promise | null = null - private syncRecoveryPromise: Promise | null = null + private readonly collectionInstance = nextWakeRegistryCollectionInstance++ constructor( db: DrizzleDB, @@ -132,10 +176,220 @@ export class WakeRegistry { this.db = db } + requireCollection(): Collection { + if (!this.registrationsCollection) { + throw new Error(`WakeRegistry has not been started`) + } + return this.registrationsCollection + } + + async startLocalForTests(): Promise { + if (this.registrationsCollection) return + this.mode = `local-test` + this.registrationsCollection = createCollection( + localOnlyCollectionOptions({ + id: `wake-registrations-local:${this.tenantId ?? `all`}`, + getKey: (row) => row.id, + initialData: [], + }) + ) + await this.requireCollection().preload() + this.startRegistrationEffect() + } + + private startRegistrationEffect(): void { + if (this.registrationsEffect) return + const collection = this.requireCollection() + this.registrationsEffect = createEffect< + WakeRegistrationCollectionRow, + number + >({ + query: (q) => q.from({ reg: collection }), + skipInitial: false, + onEnter: ({ value }) => { + this.syncTimeoutTimer(value) + }, + onUpdate: ({ value }) => { + this.syncTimeoutTimer(value) + }, + onExit: ({ value }) => { + this.clearRegistrationState(value) + this.timeoutDelivered.delete(value.id) + }, + }) + } + + private allocateLocalId(): number { + return this.nextLocalId++ + } + + private normalizeRegistration( + reg: WakeRegistration, + tenantId: string, + id: number + ): WakeRegistrationCollectionRow { + return { + id, + tenantId, + subscriberUrl: reg.subscriberUrl, + sourceUrl: reg.sourceUrl, + condition: reg.condition, + debounceMs: reg.debounceMs ?? 0, + timeoutMs: reg.timeoutMs ?? 0, + oneShot: reg.oneShot, + timeoutConsumed: false, + includeResponse: reg.includeResponse !== false, + manifestKey: reg.manifestKey ?? ``, + createdAt: new Date(), + } + } + + private normalizeQueriedRows( + rows: Array< + WakeRegistrationCollectionRow | { reg: WakeRegistrationCollectionRow } + > + ): Array { + return rows.map( + (row) => + ((row as { reg?: WakeRegistrationCollectionRow }).reg ?? + row) as WakeRegistrationCollectionRow + ) + } + + private async rowsByPredicate( + predicate: (row: WakeRegistrationCollectionRow) => boolean + ): Promise> { + const rows = await queryOnce((q) => + q.from({ reg: this.requireCollection() }) + ) + return this.normalizeQueriedRows( + rows as Array< + WakeRegistrationCollectionRow | { reg: WakeRegistrationCollectionRow } + > + ).filter(predicate) + } + + private async rowsForSource( + tenantId: string, + sourceUrl: string + ): Promise> { + const rows = await queryOnce((q) => + q + .from({ reg: this.requireCollection() }) + .where(({ reg }) => + dbAnd(dbEq(reg.tenantId, tenantId), dbEq(reg.sourceUrl, sourceUrl)) + ) + ) + return this.normalizeQueriedRows( + rows as Array< + WakeRegistrationCollectionRow | { reg: WakeRegistrationCollectionRow } + > + ) + } + + private registerAction = + createOptimisticAction({ + onMutate: (row) => { + this.requireCollection().insert(row) + }, + mutationFn: async (row, { transaction }) => { + if (this.mode === `local-test`) { + this.requireCollection().utils.acceptMutations(transaction) + return + } + if (this.mode === `electric`) { + const result = await this.persistInsert(row) + try { + await this.requireCollection().utils.awaitTxId(result.txid, 10_000) + } catch (error) { + if ( + error instanceof Error && + error.name === `TimeoutWaitingForTxIdError` + ) { + this.requireCollection().utils.acceptMutations(transaction) + return { txid: result.txid } + } + throw error + } + return { txid: result.txid } + } + throw new Error(`WakeRegistry registerAction called before startup`) + }, + }) + + private deleteRowsAction = createOptimisticAction({ + onMutate: ({ rows }) => { + const collection = this.requireCollection() + for (const row of rows) { + this.clearRegistrationState(row) + this.timeoutDelivered.delete(row.id) + collection.delete(row.id) + } + }, + mutationFn: async (input, { transaction }) => { + if (this.mode === `local-test`) { + this.requireCollection().utils.acceptMutations(transaction) + return + } + if (this.mode === `electric`) { + const txid = await this.persistDeleteRows(input) + if (txid === undefined) { + this.requireCollection().utils.acceptMutations(transaction) + return + } + try { + await this.requireCollection().utils.awaitTxId(txid, 10_000) + } catch (error) { + if ( + error instanceof Error && + error.name === `TimeoutWaitingForTxIdError` + ) { + this.requireCollection().utils.acceptMutations(transaction) + return { txid } + } + throw error + } + return { txid } + } + throw new Error(`WakeRegistry deleteRowsAction called before startup`) + }, + }) + + private markTimeoutConsumedAction = createOptimisticAction<{ + row: WakeRegistrationCollectionRow + }>({ + onMutate: ({ row }) => { + this.requireCollection().update(row.id, (draft) => { + draft.timeoutConsumed = true + }) + }, + mutationFn: async ({ row }, { transaction }) => { + if (this.mode === `local-test`) { + this.requireCollection().utils.acceptMutations(transaction) + return + } + if (this.mode === `electric`) { + const txid = await this.persistTimeoutConsumed({ + ...row, + timeoutConsumed: true, + }) + if (txid === undefined) { + throw new WakeRegistrationStaleError() + } + await this.requireCollection().utils.awaitTxId(txid, 10_000) + return { txid } + } + throw new Error( + `WakeRegistry markTimeoutConsumedAction called before startup` + ) + }, + }) + setTimeoutCallback(cb: WakeTimeoutCallback, tenantId?: string): void { const resolvedTenantId = this.resolveTenantId(tenantId) this.timeoutCallbacks.set(resolvedTenantId, cb) this.syncTenantTimeoutTimers(resolvedTenantId) + void this.syncTenantCollectionTimeoutTimers(resolvedTenantId) } setDebounceCallback(cb: WakeDebounceCallback, tenantId?: string): void { @@ -148,11 +402,7 @@ export class WakeRegistry { throw new Error(`WakeRegistry tenantId is required in shared mode`) } - private cacheKey(tenantId: string, sourceUrl: string): string { - return `${tenantId}:${sourceUrl}` - } - - private registrationKey(reg: CachedWakeRegistration): string { + private registrationKey(reg: WakeRegistrationCollectionRow): string { return [ reg.tenantId, reg.subscriberUrl, @@ -178,197 +428,358 @@ export class WakeRegistry { } async startSync(electricUrl: string, electricSecret?: string): Promise { - if (this.syncReadyPromise) { - await this.syncReadyPromise + if (this.registrationsCollection) { + await this.registrationsCollection.preload() + this.startRegistrationEffect() return } - this.syncElectricUrl = electricUrl - this.syncElectricSecret = electricSecret - - const abortController = new AbortController() - const stream = new ShapeStream({ - url: electricUrlWithPath(electricUrl, `/v1/shape`).toString(), - params: { - table: `wake_registrations`, - ...(this.tenantId - ? { where: `tenant_id = ${sqlStringLiteral(this.tenantId)}` } - : {}), - ...(electricSecret ? { secret: electricSecret } : {}), - columns: [ - `id`, - `tenant_id`, - `subscriber_url`, - `source_url`, - `condition`, - `debounce_ms`, - `timeout_ms`, - `one_shot`, - `timeout_consumed`, - `include_response`, - `manifest_key`, - `created_at`, - ], - replica: `full`, - }, - parser: { - timestamptz: (value: string) => new Date(value), - }, - signal: abortController.signal, - onError: (error) => { - if (abortController.signal.aborted) { - return {} - } - if (this.syncReadyPromise) { - void this.recoverSync(error, `shape stream error`) - } - return {} - }, - }) - - this.syncAbortController = abortController - this.syncReadyPromise = new Promise((resolve, reject) => { - let settled = false - - this.syncUnsubscribe = stream.subscribe( - async (messages) => { - try { - for (const message of messages) { - await this.applyShapeMessage(message) - if ( - !settled && - isControlMessage(message) && - message.headers.control === `up-to-date` - ) { - settled = true - resolve() - } - } - } catch (error) { - if (!settled) { - settled = true - reject(error) - return - } - serverLog.error( - `[wake-registry] failed to apply shape change:`, - error - ) - } + this.mode = `electric` + this.registrationsCollection = createCollection( + electricCollectionOptions({ + id: `wake-registrations:${this.tenantId ?? `all`}:${electricUrlWithPath(electricUrl, `/v1/shape`).toString()}:${this.collectionInstance}`, + getKey: (row: any) => row.id as number, + shapeOptions: { + url: electricUrlWithPath(electricUrl, `/v1/shape`).toString(), + params: { + table: `wake_registrations`, + ...(this.tenantId + ? { where: `tenant_id = ${sqlStringLiteral(this.tenantId)}` } + : {}), + ...(electricSecret ? { secret: electricSecret } : {}), + columns: [ + `id`, + `tenant_id`, + `subscriber_url`, + `source_url`, + `condition`, + `debounce_ms`, + `timeout_ms`, + `one_shot`, + `timeout_consumed`, + `include_response`, + `manifest_key`, + `created_at`, + ], + replica: `full`, + }, + parser: { + timestamptz: (value: string) => new Date(value), + }, + columnMapper: snakeCamelMapper(), }, - (error) => { - if (!settled) { - settled = true - reject(error) - return - } - void this.recoverSync(error, `subscription error`) - } - ) - }) + } as any) + ) as any try { - await this.syncReadyPromise + await this.requireCollection().preload() } catch (error) { - await this.stopSync() + const collection = this.registrationsCollection + this.registrationsCollection = null + this.mode = `unstarted` + await collection?.cleanup?.() throw error } + this.startRegistrationEffect() } async stopSync(): Promise { - this.syncUnsubscribe?.() - this.syncUnsubscribe = null - this.syncAbortController?.abort() - this.syncAbortController = null - this.syncReadyPromise = null + const collection = this.registrationsCollection + await this.registrationsEffect?.dispose() + this.registrationsEffect = null + this.registrationsCollection = null + this.mode = `unstarted` + this.resetRuntimeState() + await collection?.cleanup?.() } - private async recoverSync( - error: unknown, - source: `shape stream error` | `subscription error` - ): Promise { - if (this.syncRecoveryPromise) { - return this.syncRecoveryPromise + private resetRuntimeState(): void { + for (const timer of this.debounceTimers.values()) { + clearTimeout(timer) } + this.debounceTimers.clear() + this.debounceBuffers.clear() + this.debounceRunStatus.clear() - const electricUrl = this.syncElectricUrl - if (!electricUrl) { - serverLog.error( - `[wake-registry] Electric sync failed (${source}):`, - error - ) - return + for (const timer of this.timeoutTimers.values()) { + clearTimeout(timer) } + this.timeoutTimers.clear() + this.timeoutDelivered.clear() + } - this.syncRecoveryPromise = (async () => { - serverLog.error( - `[wake-registry] Electric sync failed (${source}):`, - error - ) + private async allocateRegistrationId(): Promise { + if (this.mode !== `electric`) { + return this.allocateLocalId() + } - await this.stopSync() - await this.loadRegistrations() + for (let attempt = 0; attempt < 100; attempt++) { + const id = await this.allocateRuntimeId() + const existing = await this.rowsByPredicate((row) => row.id === id) + if (existing.length === 0) return id + } - try { - await this.startSync(electricUrl, this.syncElectricSecret) - serverLog.info(`[wake-registry] Electric sync recovered`) - } catch (recoveryError) { - serverLog.error( - `[wake-registry] Electric sync recovery failed:`, - recoveryError - ) - } finally { - this.syncRecoveryPromise = null - } - })() + throw new Error(`Failed to allocate unused wake registration id`) + } - return this.syncRecoveryPromise + private async allocateRuntimeId(): Promise { + const rows = await this.db.execute( + sql<{ + id: string + }>`select nextval('wake_registrations_id_seq')::text as id` + ) + const value = Array.isArray(rows) + ? rows[0]?.id + : ((rows as any).rows?.[0]?.id ?? (rows as any)[0]?.id) + const id = Number(value) + if (!Number.isInteger(id)) { + throw new Error(`Failed to allocate wake registration id`) + } + return id } - async register(reg: WakeRegistration): Promise { - const tenantId = this.resolveTenantId(reg.tenantId) - const result = await this.db - .insert(wakeRegistrations) - .values({ - tenantId, - subscriberUrl: reg.subscriberUrl, - sourceUrl: reg.sourceUrl, - condition: reg.condition, - debounceMs: reg.debounceMs ?? 0, - timeoutMs: reg.timeoutMs ?? 0, - oneShot: reg.oneShot, - includeResponse: reg.includeResponse !== false, - manifestKey: reg.manifestKey ?? null, - }) - .onConflictDoNothing() - .returning({ id: wakeRegistrations.id }) + private async persistInsert( + row: WakeRegistrationCollectionRow + ): Promise { + return await this.db.transaction(async (tx) => { + const rows = await tx + .insert(wakeRegistrations) + .values({ + id: row.id, + tenantId: row.tenantId, + subscriberUrl: row.subscriberUrl, + sourceUrl: row.sourceUrl, + condition: row.condition, + debounceMs: row.debounceMs, + timeoutMs: row.timeoutMs, + oneShot: row.oneShot, + timeoutConsumed: row.timeoutConsumed, + includeResponse: row.includeResponse, + manifestKey: row.manifestKey ?? ``, + createdAt: row.createdAt, + }) + .onConflictDoUpdate({ + target: [ + wakeRegistrations.tenantId, + wakeRegistrations.subscriberUrl, + wakeRegistrations.sourceUrl, + wakeRegistrations.oneShot, + wakeRegistrations.debounceMs, + wakeRegistrations.timeoutMs, + wakeRegistrations.condition, + wakeRegistrations.manifestKey, + ], + set: { + id: row.id, + timeoutConsumed: row.timeoutConsumed, + includeResponse: row.includeResponse, + createdAt: sql`${wakeRegistrations.createdAt}`, + }, + }) + .returning({ + id: wakeRegistrations.id, + tenantId: wakeRegistrations.tenantId, + subscriberUrl: wakeRegistrations.subscriberUrl, + sourceUrl: wakeRegistrations.sourceUrl, + condition: wakeRegistrations.condition, + debounceMs: wakeRegistrations.debounceMs, + timeoutMs: wakeRegistrations.timeoutMs, + oneShot: wakeRegistrations.oneShot, + timeoutConsumed: wakeRegistrations.timeoutConsumed, + includeResponse: wakeRegistrations.includeResponse, + manifestKey: wakeRegistrations.manifestKey, + createdAt: wakeRegistrations.createdAt, + txid: sql`pg_current_xact_id()::xid::text`, + }) + const inserted = rows[0] + if (!inserted) throw new Error(`Wake registration insert returned no row`) + return { + txid: Number(inserted.txid), + row: { + id: inserted.id, + tenantId: inserted.tenantId, + subscriberUrl: inserted.subscriberUrl, + sourceUrl: inserted.sourceUrl, + condition: inserted.condition as WakeRegistration[`condition`], + debounceMs: inserted.debounceMs, + timeoutMs: inserted.timeoutMs, + oneShot: inserted.oneShot, + timeoutConsumed: inserted.timeoutConsumed, + includeResponse: inserted.includeResponse, + manifestKey: inserted.manifestKey, + createdAt: inserted.createdAt, + }, + } + }) + } - if (result.length === 0) { - // Another path (e.g. manifest-sync) may have created the row first. - // Refresh the cache so this process still sees the effective registration. - await this.loadRegistrations() - return + private async persistTimeoutConsumed( + row: WakeRegistrationCollectionRow + ): Promise { + return await this.db.transaction(async (tx) => { + const rows = await tx + .update(wakeRegistrations) + .set({ timeoutConsumed: row.timeoutConsumed }) + .where( + and( + eq(wakeRegistrations.tenantId, row.tenantId), + eq(wakeRegistrations.id, row.id) + ) + ) + .returning({ txid: sql`pg_current_xact_id()::xid::text` }) + return rows[0]?.txid === undefined ? undefined : Number(rows[0].txid) + }) + } + + private deletePredicate(input: DeleteRowsInput) { + switch (input.persist.kind) { + case `manifestKey`: + return and( + eq(wakeRegistrations.tenantId, input.persist.tenantId), + eq(wakeRegistrations.subscriberUrl, input.persist.subscriberUrl), + eq(wakeRegistrations.manifestKey, input.persist.manifestKey) + ) + case `subscriber`: + return and( + eq(wakeRegistrations.tenantId, input.persist.tenantId), + eq(wakeRegistrations.subscriberUrl, input.persist.subscriberUrl) + ) + case `source`: + return and( + eq(wakeRegistrations.tenantId, input.persist.tenantId), + eq(wakeRegistrations.sourceUrl, input.persist.sourceUrl) + ) + case `subscriberAndSource`: + return and( + eq(wakeRegistrations.tenantId, input.persist.tenantId), + eq(wakeRegistrations.subscriberUrl, input.persist.subscriberUrl), + eq(wakeRegistrations.sourceUrl, input.persist.sourceUrl) + ) + case `oneShot`: + return input.rows.length === 0 + ? undefined + : inArray( + wakeRegistrations.id, + input.rows.map((row) => row.id) + ) } + } - const dbId = result[0]!.id - this.upsertCachedRegistration({ - ...reg, - tenantId, - dbId, - createdAt: new Date(), - timeoutConsumed: false, + private async persistDeleteRows( + input: DeleteRowsInput + ): Promise { + const predicate = this.deletePredicate(input) + if (!predicate) return undefined + return await this.db.transaction(async (tx) => { + const deleted = await tx + .delete(wakeRegistrations) + .where(predicate) + .returning({ txid: sql`pg_current_xact_id()::xid::text` }) + return deleted[0]?.txid === undefined + ? undefined + : Number(deleted[0].txid) }) } - private startTimeoutTimer(reg: CachedWakeRegistration, dbId: number): void { - if (reg.timeoutMs == null || reg.timeoutMs <= 0) return - this.startTimeoutTimerWithDuration(reg, dbId, reg.timeoutMs) + private registrationRowsMatch( + row: WakeRegistrationCollectionRow, + other: WakeRegistrationCollectionRow + ): boolean { + return ( + row.tenantId === other.tenantId && + row.subscriberUrl === other.subscriberUrl && + row.sourceUrl === other.sourceUrl && + JSON.stringify(row.condition) === JSON.stringify(other.condition) && + row.debounceMs === other.debounceMs && + row.timeoutMs === other.timeoutMs && + row.oneShot === other.oneShot && + row.manifestKey === other.manifestKey + ) + } + + private registrationMatches( + row: WakeRegistrationCollectionRow, + reg: WakeRegistration, + tenantId: string + ): boolean { + return this.registrationRowsMatch( + row, + this.normalizeRegistration(reg, tenantId, row.id) + ) + } + + private async waitForRegistrationVisible( + row: WakeRegistrationCollectionRow, + timeoutMs = 10_000 + ): Promise { + const deadline = Date.now() + timeoutMs + do { + const rows = await this.rowsByPredicate((candidate) => + this.registrationRowsMatch(candidate, row) + ) + if (rows.length > 0) return + await new Promise((resolve) => setTimeout(resolve, 25)) + } while (Date.now() < deadline) + } + + async register(reg: WakeRegistration): Promise { + const tenantId = this.resolveTenantId(reg.tenantId) + if (this.registrationsCollection) { + const existing = await this.rowsByPredicate((row) => + this.registrationMatches(row, reg, tenantId) + ) + if (existing.length > 0) return + } + const id = await this.allocateRegistrationId() + const tx = this.registerAction( + this.normalizeRegistration(reg, tenantId, id) + ) + try { + await tx.isPersisted.promise + } catch (error) { + if (error instanceof WakeRegistrationConflictError) { + await this.waitForRegistrationVisible(error.row) + return + } + throw error + } + } + + private startTimeoutTimer(reg: WakeRegistrationCollectionRow): void { + if (reg.timeoutMs <= 0) return + this.startTimeoutTimerWithDuration(reg, reg.timeoutMs) } private async markTimeoutConsumed( dbId: number, tenantId: string ): Promise { + if (this.registrationsCollection) { + const row = await queryOnce((q) => + q + .from({ reg: this.requireCollection() }) + .where(({ reg }) => + dbAnd(dbEq(reg.tenantId, tenantId), dbEq(reg.id, dbId)) + ) + .findOne() + ) + const queried = row as + | { reg?: WakeRegistrationCollectionRow } + | WakeRegistrationCollectionRow + | undefined + const normalized = ( + queried && `reg` in queried ? queried.reg : queried + ) as WakeRegistrationCollectionRow | undefined + if (!normalized) return + const tx = this.markTimeoutConsumedAction({ row: normalized }) + try { + await tx.isPersisted.promise + } catch (error) { + if (error instanceof WakeRegistrationStaleError) return + throw error + } + return + } await this.db .update(wakeRegistrations) .set({ timeoutConsumed: true }) @@ -380,11 +791,42 @@ export class WakeRegistry { ) } + private async deleteRows(input: DeleteRowsInput): Promise { + if (this.registrationsCollection && input.rows.length > 0) { + const tx = this.deleteRowsAction(input) + await tx.isPersisted.promise + return + } + if (this.mode === `electric`) { + await this.persistDeleteRows(input) + } + } + async unregisterByManifestKey( subscriberUrl: string, manifestKey: string, tenantId?: string ): Promise { + if (this.registrationsCollection) { + const resolvedTenantId = this.resolveTenantId(tenantId) + const rows = await this.rowsByPredicate( + (row) => + row.tenantId === resolvedTenantId && + row.subscriberUrl === subscriberUrl && + row.manifestKey === manifestKey + ) + await this.deleteRows({ + rows, + persist: { + kind: `manifestKey`, + tenantId: resolvedTenantId, + subscriberUrl, + manifestKey, + }, + }) + return + } + const resolvedTenantId = this.resolveTenantId(tenantId) await this.db .delete(wakeRegistrations) @@ -395,28 +837,30 @@ export class WakeRegistry { eq(wakeRegistrations.manifestKey, manifestKey) ) ) - - const toRemove = Array.from(this.registrationCache.values()).flatMap( - (regs) => - regs - .filter( - (r) => - r.tenantId === resolvedTenantId && - r.subscriberUrl === subscriberUrl && - r.manifestKey === manifestKey - ) - .map((r) => r.dbId) - ) - - for (const dbId of toRemove) { - this.removeCachedRegistrationByDbId(dbId) - } } async unregisterBySubscriber( subscriberUrl: string, tenantId?: string ): Promise { + if (this.registrationsCollection) { + const resolvedTenantId = this.resolveTenantId(tenantId) + const rows = await this.rowsByPredicate( + (row) => + row.tenantId === resolvedTenantId && + row.subscriberUrl === subscriberUrl + ) + await this.deleteRows({ + rows, + persist: { + kind: `subscriber`, + tenantId: resolvedTenantId, + subscriberUrl, + }, + }) + return + } + const resolvedTenantId = this.resolveTenantId(tenantId) await this.db .delete(wakeRegistrations) @@ -426,26 +870,22 @@ export class WakeRegistry { eq(wakeRegistrations.subscriberUrl, subscriberUrl) ) ) - - const toRemove = Array.from(this.registrationCache.values()).flatMap( - (regs) => - regs - .filter( - (r) => - r.tenantId === resolvedTenantId && - r.subscriberUrl === subscriberUrl - ) - .map((r) => r.dbId) - ) - for (const dbId of toRemove) { - this.removeCachedRegistrationByDbId(dbId) - } } async unregisterBySource( sourceUrl: string, tenantId?: string ): Promise { + if (this.registrationsCollection) { + const resolvedTenantId = this.resolveTenantId(tenantId) + const rows = await this.rowsForSource(resolvedTenantId, sourceUrl) + await this.deleteRows({ + rows, + persist: { kind: `source`, tenantId: resolvedTenantId, sourceUrl }, + }) + return + } + const resolvedTenantId = this.resolveTenantId(tenantId) await this.db .delete(wakeRegistrations) @@ -455,15 +895,6 @@ export class WakeRegistry { eq(wakeRegistrations.sourceUrl, sourceUrl) ) ) - - const key = this.cacheKey(resolvedTenantId, sourceUrl) - const regs = this.registrationCache.get(key) - if (regs) { - for (const reg of [...regs]) { - this.removeCachedRegistrationByDbId(reg.dbId) - } - this.registrationCache.delete(key) - } } async unregisterBySubscriberAndSource( @@ -471,6 +902,26 @@ export class WakeRegistry { sourceUrl: string, tenantId?: string ): Promise { + if (this.registrationsCollection) { + const resolvedTenantId = this.resolveTenantId(tenantId) + const rows = await this.rowsByPredicate( + (row) => + row.tenantId === resolvedTenantId && + row.subscriberUrl === subscriberUrl && + row.sourceUrl === sourceUrl + ) + await this.deleteRows({ + rows, + persist: { + kind: `subscriberAndSource`, + tenantId: resolvedTenantId, + subscriberUrl, + sourceUrl, + }, + }) + return + } + const resolvedTenantId = this.resolveTenantId(tenantId) await this.db .delete(wakeRegistrations) @@ -481,62 +932,16 @@ export class WakeRegistry { eq(wakeRegistrations.sourceUrl, sourceUrl) ) ) - - const regs = this.registrationCache.get( - this.cacheKey(resolvedTenantId, sourceUrl) - ) - if (regs) { - const toRemove = regs - .filter( - (r) => - r.tenantId === resolvedTenantId && r.subscriberUrl === subscriberUrl - ) - .map((r) => r.dbId) - for (const dbId of toRemove) { - this.removeCachedRegistrationByDbId(dbId) - } - } - } - - async loadRegistrations(): Promise { - const rows = - this.tenantId === null - ? await this.db.select().from(wakeRegistrations) - : await this.db - .select() - .from(wakeRegistrations) - .where(eq(wakeRegistrations.tenantId, this.tenantId)) - - this.resetCachedRegistrations() - - for (const row of rows) { - const reg: CachedWakeRegistration = { - tenantId: row.tenantId, - subscriberUrl: row.subscriberUrl, - sourceUrl: row.sourceUrl, - condition: row.condition as WakeRegistration[`condition`], - debounceMs: row.debounceMs || undefined, - timeoutMs: row.timeoutMs || undefined, - oneShot: row.oneShot, - includeResponse: row.includeResponse === false ? false : undefined, - manifestKey: row.manifestKey ?? undefined, - dbId: row.id, - createdAt: row.createdAt, - timeoutConsumed: row.timeoutConsumed, - } - this.upsertCachedRegistration(reg) - } } private startTimeoutTimerWithDuration( - reg: CachedWakeRegistration, - dbId: number, + reg: WakeRegistrationCollectionRow, durationMs: number ): void { const timerKey = this.registrationKey(reg) const timer = setTimeout(() => { this.timeoutTimers.delete(timerKey) - this.deliverTimeoutForRegistration(reg, dbId) + this.deliverTimeoutForRegistration(reg) }, durationMs) this.timeoutTimers.set(timerKey, timer) } @@ -559,88 +964,17 @@ export class WakeRegistry { } } - private clearRegistrationState(reg: CachedWakeRegistration): void { + private clearRegistrationState(reg: WakeRegistrationCollectionRow): void { const timerKey = this.registrationKey(reg) this.clearDebounceState(timerKey) this.clearTimeoutState(timerKey) } - private resetCachedRegistrations(): void { - for (const timer of this.debounceTimers.values()) { - clearTimeout(timer) - } - this.debounceTimers.clear() - this.debounceBuffers.clear() - this.debounceRunStatus.clear() - - for (const timer of this.timeoutTimers.values()) { - clearTimeout(timer) - } - this.timeoutTimers.clear() - this.registrationCache.clear() - } - - private findCachedRegistration( - dbId: number - ): { cacheKey: string; index: number; reg: CachedWakeRegistration } | null { - for (const [cacheKey, regs] of this.registrationCache) { - const index = regs.findIndex((reg) => reg.dbId === dbId) - if (index >= 0) { - return { - cacheKey, - index, - reg: regs[index]!, - } - } - } - - return null - } - - private upsertCachedRegistration(reg: CachedWakeRegistration): void { - const existing = this.findCachedRegistration(reg.dbId) - const nextKey = this.registrationKey(reg) - - if (existing) { - const previousKey = this.registrationKey(existing.reg) - const regs = this.registrationCache.get(existing.cacheKey) - if (regs) { - regs.splice(existing.index, 1) - if (regs.length === 0) { - this.registrationCache.delete(existing.cacheKey) - } - } - if (previousKey !== nextKey) { - this.clearRegistrationState(existing.reg) - } - } - - const cacheKey = this.cacheKey(reg.tenantId, reg.sourceUrl) - const cached = this.registrationCache.get(cacheKey) ?? [] - cached.push(reg) - this.registrationCache.set(cacheKey, cached) - this.syncTimeoutTimer(reg) - } - - private removeCachedRegistrationByDbId(dbId: number): void { - const existing = this.findCachedRegistration(dbId) - if (!existing) return - - this.clearRegistrationState(existing.reg) - this.timeoutDelivered.delete(dbId) - - const regs = this.registrationCache.get(existing.cacheKey) - if (!regs) return - regs.splice(existing.index, 1) - if (regs.length === 0) { - this.registrationCache.delete(existing.cacheKey) - } - } - - private syncTimeoutTimer(reg: CachedWakeRegistration): void { + private syncTimeoutTimer(registration: WakeRegistrationCollectionRow): void { + const reg = registration const timerKey = this.registrationKey(reg) - if (reg.timeoutConsumed || reg.timeoutMs == null || reg.timeoutMs <= 0) { + if (reg.timeoutConsumed || reg.timeoutMs <= 0) { this.clearTimeoutState(timerKey) return } @@ -649,52 +983,60 @@ export class WakeRegistry { return } - if (!reg.createdAt) { - this.startTimeoutTimer(reg, reg.dbId) - return - } - const remaining = reg.createdAt.getTime() + reg.timeoutMs - Date.now() if (remaining > 0) { - this.startTimeoutTimerWithDuration(reg, reg.dbId, remaining) + this.startTimeoutTimerWithDuration(reg, remaining) return } - if (this.timeoutDelivered.has(reg.dbId)) { + if (this.timeoutDelivered.has(reg.id)) { return } - this.deliverTimeoutForRegistration(reg, reg.dbId) + this.deliverTimeoutForRegistration(reg) } private deliverTimeoutForRegistration( - reg: CachedWakeRegistration, - dbId: number + reg: WakeRegistrationCollectionRow ): void { - if (this.deliverTimeout(this.timeoutWakeResult(reg, dbId))) { - this.timeoutDelivered.add(dbId) - void this.markTimeoutConsumed(dbId, reg.tenantId) + if (this.deliverTimeout(this.timeoutWakeResult(reg))) { + this.timeoutDelivered.add(reg.id) + void this.markTimeoutConsumed(reg.id, reg.tenantId).catch((error) => { + serverLog.warn( + `[wake-registry] failed to mark timeout consumed for registration ${reg.id} (${reg.tenantId}, ${reg.sourceUrl} -> ${reg.subscriberUrl}):`, + error + ) + }) } } private syncTenantTimeoutTimers(tenantId: string): void { - for (const regs of this.registrationCache.values()) { - for (const reg of regs) { - if (reg.tenantId === tenantId) { - this.syncTimeoutTimer(reg) - } - } + void this.syncTenantCollectionTimeoutTimers(tenantId) + } + + private async syncTenantCollectionTimeoutTimers( + tenantId: string + ): Promise { + if (!this.registrationsCollection) return + const rows = await queryOnce((q) => + q + .from({ reg: this.requireCollection() }) + .where(({ reg }) => dbEq(reg.tenantId, tenantId)) + ) + for (const queriedRow of rows) { + const row = ((queriedRow as { reg?: WakeRegistrationCollectionRow }) + .reg ?? queriedRow) as WakeRegistrationCollectionRow + this.syncTimeoutTimer(row) } } private timeoutWakeResult( - reg: CachedWakeRegistration, - dbId: number + reg: WakeRegistrationCollectionRow ): WakeEvalResult { return { tenantId: reg.tenantId, subscriberUrl: reg.subscriberUrl, - registrationDbId: dbId, + registrationDbId: reg.id, sourceEventKey: `timeout`, wakeMessage: { source: reg.sourceUrl, @@ -704,79 +1046,33 @@ export class WakeRegistry { } } - private normalizeShapeRow( - row: WakeRegistrationShapeRow - ): CachedWakeRegistration { - return { - tenantId: - (row as { tenant_id?: string }).tenant_id ?? this.resolveTenantId(), - subscriberUrl: row.subscriber_url, - sourceUrl: row.source_url, - condition: row.condition, - debounceMs: row.debounce_ms || undefined, - timeoutMs: row.timeout_ms || undefined, - oneShot: row.one_shot, - includeResponse: row.include_response === false ? false : undefined, - manifestKey: row.manifest_key ?? undefined, - dbId: row.id, - createdAt: row.created_at, - timeoutConsumed: row.timeout_consumed, - } - } - - private async applyShapeMessage( - message: Message - ): Promise { - if (isControlMessage(message)) { - if (message.headers.control === `must-refetch`) { - this.resetCachedRegistrations() - } - return - } - - if (!isChangeMessage(message)) { - return - } - - if (message.headers.operation === `delete`) { - // Shape keys are protocol-level identifiers and are not guaranteed to be - // the table primary key. The wake_registrations shape uses - // `replica: full`, so deletes should carry the deleted row in old_value; - // use that row id to remove the matching in-memory registration. If the - // id is unavailable, reset the cache so we fail closed rather than - // keeping a stale wake registration alive. - const oldValue = ( - message as unknown as { - old_value?: { id?: unknown } - } - ).old_value - const oldId = Number(oldValue?.id) - if (Number.isFinite(oldId)) { - this.removeCachedRegistrationByDbId(oldId) - } else { - this.resetCachedRegistrations() - } - return - } - - this.upsertCachedRegistration(this.normalizeShapeRow(message.value)) - } - - evaluate( + async evaluate( sourceUrl: string, event: Record, tenantId?: string - ): Array { + ): Promise> { const resolvedTenantId = this.resolveTenantId(tenantId) - const cacheKey = this.cacheKey(resolvedTenantId, sourceUrl) - const regs = this.registrationCache.get(cacheKey) - if (!regs || regs.length === 0) return [] + const queriedRegs = await queryOnce((q) => + q + .from({ reg: this.requireCollection() }) + .where(({ reg }) => + dbAnd( + dbEq(reg.tenantId, resolvedTenantId), + dbEq(reg.sourceUrl, sourceUrl) + ) + ) + ) + const regs = queriedRegs.map( + (queriedReg) => + ((queriedReg as { reg?: WakeRegistrationCollectionRow }).reg ?? + queriedReg) as WakeRegistrationCollectionRow + ) + if (regs.length === 0) return [] const results: Array = [] - const toRemove: Array = [] + const oneShotRows: Array = [] - for (let i = 0; i < regs.length; i++) { - const reg = regs[i]! + for (const reg of regs) { const match = this.matchCondition(reg, event) if (!match) continue @@ -785,15 +1081,19 @@ export class WakeRegistry { if (timeoutTimer) { clearTimeout(timeoutTimer) this.timeoutTimers.delete(timerKey) - void this.markTimeoutConsumed(reg.dbId, reg.tenantId) + void this.markTimeoutConsumed(reg.id, reg.tenantId).catch((error) => { + console.warn( + `[wake-registry] failed to persist timeout consumption:`, + error + ) + }) } - if (reg.debounceMs != null && reg.debounceMs > 0) { + if (reg.debounceMs > 0) { const buffer = this.debounceBuffers.get(timerKey) ?? [] buffer.push(match.change) this.debounceBuffers.set(timerKey, buffer) - // Preserve the latest runFinished status for debounced delivery if (match.runFinishedStatus) { this.debounceRunStatus.set(timerKey, match.runFinishedStatus) } @@ -811,7 +1111,7 @@ export class WakeRegistry { this.deliverDebounce({ tenantId: reg.tenantId, subscriberUrl: reg.subscriberUrl, - registrationDbId: reg.dbId, + registrationDbId: reg.id, sourceEventKey: flushed[flushed.length - 1]!.key, wakeMessage: { source: sourceUrl, @@ -828,7 +1128,7 @@ export class WakeRegistry { results.push({ tenantId: reg.tenantId, subscriberUrl: reg.subscriberUrl, - registrationDbId: reg.dbId, + registrationDbId: reg.id, sourceEventKey: wakeSourceEventId(event), wakeMessage: { source: sourceUrl, @@ -841,27 +1141,21 @@ export class WakeRegistry { } if (reg.oneShot) { - toRemove.push(i) + oneShotRows.push(reg) } } - for (let j = toRemove.length - 1; j >= 0; j--) { - const removed = regs.splice(toRemove[j]!, 1) - if (removed[0]) { - this.clearRegistrationState(removed[0]) - this.timeoutDelivered.delete(removed[0].dbId) - void this.db - .delete(wakeRegistrations) - .where( - and( - eq(wakeRegistrations.tenantId, removed[0].tenantId), - eq(wakeRegistrations.id, removed[0].dbId) - ) - ) - } - } - if (regs.length === 0) { - this.registrationCache.delete(cacheKey) + if (oneShotRows.length > 0) { + const tx = this.deleteRowsAction({ + rows: oneShotRows, + persist: { kind: `oneShot` }, + }) + void tx.isPersisted.promise.catch((error) => { + console.warn( + `[wake-registry] failed to persist one-shot cleanup:`, + error + ) + }) } return results @@ -906,7 +1200,7 @@ export class WakeRegistry { } private matchCondition( - reg: WakeRegistration, + reg: WakeRegistration | WakeRegistrationCollectionRow, event: Record ): { change: WakeEvalResult[`wakeMessage`][`changes`][number] diff --git a/packages/agents-server/test/horton-pull-wake-e2e.test.ts b/packages/agents-server/test/horton-pull-wake-e2e.test.ts index 17bfd5ea79..ab5a9935fd 100644 --- a/packages/agents-server/test/horton-pull-wake-e2e.test.ts +++ b/packages/agents-server/test/horton-pull-wake-e2e.test.ts @@ -10,10 +10,11 @@ import { waitFor, } from './test-utils' import { + TEST_ELECTRIC_URL, TEST_POSTGRES_URL, resetElectricAgentsTestBackend, } from './test-backend' -import type { StreamFn } from '@mariozechner/pi-agent-core' +import type { StreamFn } from '@earendil-works/pi-agent-core' function createMockStreamFn(responseText: string): StreamFn { return vi.fn(((model) => { @@ -266,7 +267,7 @@ describe(`pull-wake Horton e2e with mocked LLM`, () => { durableStreamsUrl: durableStreamTestServerUrl(dsServer.url), port: 0, postgresUrl: TEST_POSTGRES_URL, - electricUrl: undefined, + electricUrl: TEST_ELECTRIC_URL, authenticateRequest: (req) => req.headers.get(`authorization`) === authHeaders.authorization ? testPrincipal @@ -276,6 +277,7 @@ describe(`pull-wake Horton e2e with mocked LLM`, () => { streamBaseUrl = electricAgentsServer.streamClient.baseUrl builtinAgentsServer = new BuiltinAgentsServer({ agentServerUrl: baseUrl, + durableStreamsFetchCache: false, mockStreamFn, pullWake: { runnerId, diff --git a/packages/agents-server/test/host.test.ts b/packages/agents-server/test/host.test.ts index 3ac40d548a..903b62a90f 100644 --- a/packages/agents-server/test/host.test.ts +++ b/packages/agents-server/test/host.test.ts @@ -86,8 +86,11 @@ describe(`AgentsHost`, () => { const host = new AgentsHost({ db: createMockDb(), pgClient: vi.fn() as any, + electricUrl: `http://electric.test`, }) + vi.spyOn(host.wakeRegistry, `startSync`).mockResolvedValue(undefined) + const runtime = await host.registerTenant({ serviceId: `svc-before-start`, durableStreamsUrl: `https://streams.test/v1/streams/svc-before-start`, @@ -98,6 +101,8 @@ describe(`AgentsHost`, () => { const loadTenantBridges = vi .spyOn(host.entityProjector, `loadTenantBridges`) .mockResolvedValue(undefined) + vi.spyOn(host.entityProjector, `start`).mockResolvedValue(undefined) + vi.spyOn(host.entityProjector, `stop`).mockResolvedValue(undefined) vi.spyOn(host.scheduler, `start`).mockResolvedValue(undefined) vi.spyOn(host.scheduler, `stop`).mockResolvedValue(undefined) vi.spyOn(host.tagStreamOutboxDrainer, `start`).mockImplementation(() => {}) diff --git a/packages/agents-server/test/pg-sync-wake-delivery.test.ts b/packages/agents-server/test/pg-sync-wake-delivery.test.ts index 5a0b70d60a..7a0559abd1 100644 --- a/packages/agents-server/test/pg-sync-wake-delivery.test.ts +++ b/packages/agents-server/test/pg-sync-wake-delivery.test.ts @@ -31,6 +31,7 @@ function event(operation: `insert` | `update` | `delete`, key = operation) { describe(`pgSync wake delivery matching`, () => { it(`insert wakes insert subscriber and delete does not wake insert-only subscriber`, async () => { const registry = new WakeRegistry(createDb() as any, `default`) + await registry.startLocalForTests() await registry.register({ subscriberUrl: `/horton/a`, sourceUrl: `/_electric/pg-sync/test`, @@ -38,7 +39,7 @@ describe(`pgSync wake delivery matching`, () => { oneShot: false, }) - const insertResults = registry.evaluate( + const insertResults = await registry.evaluate( `/_electric/pg-sync/test`, event(`insert`), `default` @@ -49,12 +50,17 @@ describe(`pgSync wake delivery matching`, () => { oldValue: { id: `entity-1`, status: `spawning` }, }) expect( - registry.evaluate(`/_electric/pg-sync/test`, event(`delete`), `default`) + await registry.evaluate( + `/_electric/pg-sync/test`, + event(`delete`), + `default` + ) ).toEqual([]) }) it(`splits two subscribers on the same source by operation`, async () => { const registry = new WakeRegistry(createDb() as any, `default`) + await registry.startLocalForTests() await registry.register({ subscriberUrl: `/horton/a`, sourceUrl: `/_electric/pg-sync/test`, @@ -69,19 +75,28 @@ describe(`pgSync wake delivery matching`, () => { }) expect( - registry - .evaluate(`/_electric/pg-sync/test`, event(`insert`), `default`) - .map((r) => r.subscriberUrl) + ( + await registry.evaluate( + `/_electric/pg-sync/test`, + event(`insert`), + `default` + ) + ).map((r) => r.subscriberUrl) ).toEqual([`/horton/a`]) expect( - registry - .evaluate(`/_electric/pg-sync/test`, event(`delete`), `default`) - .map((r) => r.subscriberUrl) + ( + await registry.evaluate( + `/_electric/pg-sync/test`, + event(`delete`), + `default` + ) + ).map((r) => r.subscriberUrl) ).toEqual([`/horton/b`]) }) it(`filters pgSync events by collection`, async () => { const registry = new WakeRegistry(createDb() as any, `default`) + await registry.startLocalForTests() await registry.register({ subscriberUrl: `/horton/a`, sourceUrl: `/_electric/pg-sync/test`, @@ -90,10 +105,14 @@ describe(`pgSync wake delivery matching`, () => { }) expect( - registry.evaluate(`/_electric/pg-sync/test`, event(`insert`), `default`) + await registry.evaluate( + `/_electric/pg-sync/test`, + event(`insert`), + `default` + ) ).toHaveLength(1) expect( - registry.evaluate( + await registry.evaluate( `/_electric/pg-sync/test`, { ...event(`insert`), type: `other` }, `default` diff --git a/packages/agents-server/test/pull-wake-subscription-stack.test.ts b/packages/agents-server/test/pull-wake-subscription-stack.test.ts index 241cde77db..e43a6e182c 100644 --- a/packages/agents-server/test/pull-wake-subscription-stack.test.ts +++ b/packages/agents-server/test/pull-wake-subscription-stack.test.ts @@ -98,6 +98,69 @@ describe(`pull-wake subscription stack`, () => { }) }) + it(`emits another runner wake when ack leaves later work pending`, async () => { + dsServer = new DurableStreamTestServer({ + port: 0, + longPollTimeout: 100, + webhooks: true, + }) + await dsServer.start() + const streamBaseUrl = durableStreamTestServerUrl(dsServer.url) + const client = new StreamClient(streamBaseUrl) + + await client.ensure(`/runners/runner-1/wake`, { + contentType: `application/json`, + }) + await client.ensure(`/horton/one/main`, { + contentType: `application/json`, + }) + await client.putSubscription(`runner:runner-1:one`, { + type: `pull-wake`, + streams: [`/horton/one/main`], + wake_stream: `/runners/runner-1/wake`, + }) + + const first = await client.append( + `/horton/one/main`, + JSON.stringify({ type: `message`, value: `first` }) + ) + const claim = await client.claimSubscription( + `runner:runner-1:one`, + `worker-1` + ) + expect(claim).toMatchObject({ + generation: 1, + token: expect.any(String), + streams: [expect.objectContaining({ has_pending: true })], + }) + + await client.append( + `/horton/one/main`, + JSON.stringify({ type: `message`, value: `second` }) + ) + + const ack = await client.ackSubscription( + `runner:runner-1:one`, + claim!.token, + { + wake_id: claim!.wake_id, + generation: claim!.generation, + acks: [{ path: `horton/one/main`, offset: first.offset }], + done: true, + } + ) + expect(ack).toMatchObject({ ok: true, next_wake: true }) + + const wakes = await readJsonStream>( + streamBaseUrl, + `/runners/runner-1/wake` + ) + expect(wakes).toEqual([ + expect.objectContaining({ generation: 1 }), + expect.objectContaining({ generation: 2 }), + ]) + }) + it(`proxies pre-existing runner wake events to pull-wake runners`, async () => { dsServer = new DurableStreamTestServer({ port: 0, diff --git a/packages/agents-server/test/scheduler-integration.test.ts b/packages/agents-server/test/scheduler-integration.test.ts index af5ba79fbb..f1f4be9e38 100644 --- a/packages/agents-server/test/scheduler-integration.test.ts +++ b/packages/agents-server/test/scheduler-integration.test.ts @@ -1,6 +1,10 @@ +import { execFile } from 'node:child_process' +import { promisify } from 'node:util' +import postgres from 'postgres' import { afterAll, beforeAll, describe, expect, it } from 'vitest' import { getCronStreamPath } from '@electric-ax/agents-runtime' import { DurableStreamTestServer } from '@durable-streams/server' +import { runMigrations } from '../src/db' import { ElectricAgentsServer } from '../src/server' import { durableStreamTestServerUrl, @@ -8,10 +12,81 @@ import { waitFor, } from './test-utils' import { - TEST_ELECTRIC_URL, - TEST_POSTGRES_URL, - resetElectricAgentsTestBackend, -} from './test-backend' + ELECTRIC_AGENTS_COMPOSE_FILE, + getElectricAgentsComposeProject, + getElectricAgentsDevPorts, +} from './electric-agents-compose-utils' + +const execFileAsync = promisify(execFile) +const { postgresPort, electricPort } = getElectricAgentsDevPorts() +const TEST_POSTGRES_PORT = postgresPort + 20 +const TEST_ELECTRIC_PORT = electricPort + 20 +const TEST_COMPOSE_PROJECT = `${getElectricAgentsComposeProject()}-agent-server-scheduler` +const TEST_POSTGRES_URL = `postgres://electric_agents:electric_agents@localhost:${TEST_POSTGRES_PORT}/electric_agents` +const TEST_ELECTRIC_URL = `http://localhost:${TEST_ELECTRIC_PORT}` +const TEST_BACKEND_ENV = { + ...process.env, + ELECTRIC_AGENTS_COMPOSE_PROJECT: TEST_COMPOSE_PROJECT, + PG_HOST_PORT: String(TEST_POSTGRES_PORT), + ELECTRIC_HOST_PORT: String(TEST_ELECTRIC_PORT), + JAEGER_UI_PORT: `0`, + JAEGER_OTLP_HTTP_PORT: `0`, + JAEGER_OTLP_GRPC_PORT: `0`, + DATABASE_URL: TEST_POSTGRES_URL, + ELECTRIC_URL: TEST_ELECTRIC_URL, + ELECTRIC_AGENTS_TEST_BACKEND_MANAGED: `1`, +} + +async function startTestBackend(): Promise { + await execFileAsync( + `docker`, + [ + `compose`, + `-p`, + TEST_COMPOSE_PROJECT, + `-f`, + ELECTRIC_AGENTS_COMPOSE_FILE, + `up`, + `-d`, + `--wait`, + ], + { env: TEST_BACKEND_ENV } + ) +} + +async function stopTestBackend(): Promise { + await execFileAsync( + `docker`, + [ + `compose`, + `-p`, + TEST_COMPOSE_PROJECT, + `-f`, + ELECTRIC_AGENTS_COMPOSE_FILE, + `down`, + `-v`, + ], + { env: TEST_BACKEND_ENV } + ) +} + +async function resetTestBackend(): Promise { + await startTestBackend() + const pg = postgres(TEST_POSTGRES_URL, { + max: 1, + onnotice: () => {}, + }) + try { + await pg.unsafe(` + DROP SCHEMA IF EXISTS drizzle CASCADE; + DROP SCHEMA IF EXISTS public CASCADE; + CREATE SCHEMA public AUTHORIZATION CURRENT_USER; + `) + } finally { + await pg.end() + } + await runMigrations(TEST_POSTGRES_URL) +} describe(`Scheduler Integration`, () => { let dsServer: DurableStreamTestServer @@ -90,12 +165,16 @@ describe(`Scheduler Integration`, () => { longPollTimeout: 500, webhooks: true, }) - await Promise.all([resetElectricAgentsTestBackend(), dsServer.start()]) + await Promise.all([resetTestBackend(), dsServer.start()]) await startElectricAgentsServer() }, 120_000) afterAll(async () => { - await Promise.allSettled([stopElectricAgentsServer(), dsServer.stop()]) + await Promise.allSettled([ + stopElectricAgentsServer(), + dsServer.stop(), + stopTestBackend(), + ]) }, 120_000) it(`delayed_send survives server restart and lands exactly once`, async () => { @@ -289,7 +368,7 @@ describe(`Scheduler Integration`, () => { }), } ) - expect(scheduleRes.status).toBe(200) + expect(scheduleRes.status, await scheduleRes.clone().text()).toBe(200) await waitFor( async () => { diff --git a/packages/agents-server/test/server-start.test.ts b/packages/agents-server/test/server-start.test.ts index 20532f2819..067969bb55 100644 --- a/packages/agents-server/test/server-start.test.ts +++ b/packages/agents-server/test/server-start.test.ts @@ -147,9 +147,6 @@ vi.mock(`../src/wake-registry`, () => ({ stopSync(): Promise { return Promise.resolve() } - loadRegistrations(): Promise { - return Promise.resolve() - } }, })) @@ -279,6 +276,19 @@ describe(`ElectricAgentsServer.start`, () => { } }) + it(`fails host startup without Electric URL for wake registry sync`, async () => { + server = new ElectricAgentsServer({ + durableStreamsUrl: `http://durable.test`, + port: 0, + postgresUrl: TEST_POSTGRES_URL, + electricUrl: undefined, + }) + + await expect(server.start()).rejects.toThrow( + `WakeRegistry runtime requires an Electric URL` + ) + }) + it(`rejects startup and cleans up when scheduler startup fails`, async () => { schedulerStartMock.mockRejectedValueOnce(new Error(`scheduler exploded`)) @@ -286,6 +296,7 @@ describe(`ElectricAgentsServer.start`, () => { durableStreamsUrl: `http://durable.test`, port: 0, postgresUrl: TEST_POSTGRES_URL, + electricUrl: `http://electric.test`, }) await expect(server.start()).rejects.toThrow(`scheduler exploded`) @@ -313,6 +324,7 @@ describe(`ElectricAgentsServer.start`, () => { durableStreamsUrl: `http://durable.test`, port: 0, postgresUrl: `postgres://electric_agents:electric_agents@localhost:5432/electric_agents`, + electricUrl: `http://electric.test`, }) await expect(server.start()).resolves.toMatch(/^http:\/\//) @@ -354,6 +366,7 @@ describe(`ElectricAgentsServer.start`, () => { durableStreamsUrl: `http://durable.test`, port: 0, postgresUrl: `postgres://electric_agents:electric_agents@localhost:5432/electric_agents`, + electricUrl: `http://electric.test`, }) await expect(server.start()).resolves.toMatch(/^http:\/\//) @@ -376,6 +389,7 @@ describe(`ElectricAgentsServer.start`, () => { mockStreamFn: streamFn as any, port: 0, postgresUrl: TEST_POSTGRES_URL, + electricUrl: `http://electric.test`, }) await expect(server.start()).resolves.toMatch(/^http:\/\//) @@ -399,6 +413,7 @@ describe(`ElectricAgentsServer.start`, () => { mockStreamFn: streamFn as any, port: 0, postgresUrl: TEST_POSTGRES_URL, + electricUrl: `http://electric.test`, }) await expect(server.start()).resolves.toMatch(/^http:\/\//) diff --git a/packages/agents-server/test/wake-registry-sync.test.ts b/packages/agents-server/test/wake-registry-sync.test.ts index 1e60ac71a5..79dd27fa58 100644 --- a/packages/agents-server/test/wake-registry-sync.test.ts +++ b/packages/agents-server/test/wake-registry-sync.test.ts @@ -1,155 +1,141 @@ -import { describe, expect, it, vi } from 'vitest' +import { execFile } from 'node:child_process' +import { randomUUID } from 'node:crypto' +import { promisify } from 'node:util' +import postgres from 'postgres' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { eq } from 'drizzle-orm' +import { createDb, runMigrations } from '../src/db' +import { wakeRegistrations } from '../src/db/schema' import { WakeRegistry } from '../src/wake-registry' +import { + ELECTRIC_AGENTS_COMPOSE_FILE, + getElectricAgentsComposeProject, + getElectricAgentsDevPorts, +} from './electric-agents-compose-utils' + +const execFileAsync = promisify(execFile) +const { postgresPort, electricPort } = getElectricAgentsDevPorts() +const TEST_POSTGRES_PORT = postgresPort + 40 +const TEST_ELECTRIC_PORT = electricPort + 40 +const TEST_COMPOSE_PROJECT = `${getElectricAgentsComposeProject()}-wake-registry-sync` +const TEST_POSTGRES_URL = `postgres://electric_agents:electric_agents@localhost:${TEST_POSTGRES_PORT}/electric_agents` +const TEST_ELECTRIC_URL = `http://localhost:${TEST_ELECTRIC_PORT}` +const TEST_BACKEND_ENV = { + ...process.env, + ELECTRIC_AGENTS_COMPOSE_PROJECT: TEST_COMPOSE_PROJECT, + PG_HOST_PORT: String(TEST_POSTGRES_PORT), + ELECTRIC_HOST_PORT: String(TEST_ELECTRIC_PORT), + JAEGER_UI_PORT: `0`, + JAEGER_OTLP_HTTP_PORT: `0`, + JAEGER_OTLP_GRPC_PORT: `0`, + DATABASE_URL: TEST_POSTGRES_URL, + ELECTRIC_URL: TEST_ELECTRIC_URL, + ELECTRIC_AGENTS_TEST_BACKEND_MANAGED: `1`, +} -const { shapeStreamState } = vi.hoisted(() => ({ - shapeStreamState: { - latest: null as null | { - emit: (messages: Array>) => Promise - signal?: AbortSignal - }, - }, -})) - -vi.mock(`@electric-sql/client`, () => ({ - isControlMessage: (message: { headers?: Record }) => - typeof message.headers?.control === `string`, - isChangeMessage: (message: { headers?: Record }) => - typeof message.headers?.operation === `string`, - ShapeStream: class MockShapeStream { - private onMessages: - | ((messages: Array>) => Promise | void) - | null = null - - constructor(options: { signal?: AbortSignal }) { - shapeStreamState.latest = { - signal: options.signal, - emit: async (messages) => { - await this.onMessages?.(messages) - }, - } - } +async function startTestBackend(): Promise { + await execFileAsync( + `docker`, + [ + `compose`, + `-p`, + TEST_COMPOSE_PROJECT, + `-f`, + ELECTRIC_AGENTS_COMPOSE_FILE, + `up`, + `-d`, + `--wait`, + ], + { env: TEST_BACKEND_ENV } + ) +} - subscribe( - callback: (messages: Array>) => Promise, - _onError?: (error: Error) => void - ): () => void { - this.onMessages = callback - return () => { - this.onMessages = null - } - } - }, -})) +async function stopTestBackend(): Promise { + await execFileAsync( + `docker`, + [ + `compose`, + `-p`, + TEST_COMPOSE_PROJECT, + `-f`, + ELECTRIC_AGENTS_COMPOSE_FILE, + `down`, + `-v`, + ], + { env: TEST_BACKEND_ENV } + ) +} -function createMockDb(): any { - return { - insert: () => ({ - values: () => ({ - onConflictDoNothing: () => ({ - returning: () => Promise.resolve([{ id: 1 }]), - }), - }), - }), - delete: () => ({ - where: () => Promise.resolve(), - }), - update: () => ({ - set: () => ({ - where: () => Promise.resolve(), - }), - }), - select: () => ({ - from: () => Promise.resolve([]), - }), +async function resetTestBackend(): Promise { + await startTestBackend() + const pg = postgres(TEST_POSTGRES_URL, { max: 1, onnotice: () => {} }) + try { + await pg.unsafe(` + DROP SCHEMA IF EXISTS drizzle CASCADE; + DROP SCHEMA IF EXISTS public CASCADE; + CREATE SCHEMA public AUTHORIZATION CURRENT_USER; + `) + } finally { + await pg.end() } + await runMigrations(TEST_POSTGRES_URL) } -describe(`WakeRegistry Electric sync`, () => { - it(`ignores malformed shape messages without headers while waiting for up-to-date`, async () => { - const registry = new WakeRegistry(createMockDb()) - - const startPromise = registry.startSync(`http://electric.test`) - - await expect( - shapeStreamState.latest!.emit([ - { - key: `ignored-malformed-message`, - }, - { - headers: { - control: `up-to-date`, - }, - }, - ]) - ).resolves.toBeUndefined() - - await expect(startPromise).resolves.toBeUndefined() - - await registry.stopSync() - }) - - it(`hydrates and updates the cache from shape changes`, async () => { - const registry = new WakeRegistry(createMockDb()) - - const startPromise = registry.startSync(`http://electric.test`) - - await shapeStreamState.latest!.emit([ - { - key: `1`, - value: { - id: 1, - subscriber_url: `/parent/p1`, - source_url: `/child/c1`, - condition: `runFinished`, - debounce_ms: 0, - timeout_ms: 0, - one_shot: false, - timeout_consumed: false, - include_response: true, - manifest_key: null, - created_at: new Date(), - }, - headers: { - operation: `insert`, - }, - }, - { - headers: { - control: `up-to-date`, - }, - }, - ]) - - await startPromise - - expect( - registry.evaluate(`/child/c1`, { - type: `run`, - key: `run-1`, - value: { status: `completed` }, - headers: { operation: `update` }, +type DbConnection = ReturnType + +let connection: DbConnection +let db: DbConnection[`db`] + +describe(`WakeRegistry Electric collection sync`, () => { + beforeAll(async () => { + await resetTestBackend() + connection = createDb(TEST_POSTGRES_URL) + db = connection.db + }, 120_000) + + afterAll(async () => { + await Promise.allSettled([connection?.client.end(), stopTestBackend()]) + }, 120_000) + + it(`syncs wake rows from Postgres through Electric`, async () => { + const suffix = randomUUID() + const subscriberUrl = `/parent/sync-${suffix}` + const sourceUrl = `/child/sync-${suffix}` + const registry = new WakeRegistry(db as any) + await registry.startSync(TEST_ELECTRIC_URL) + + const rows = await db + .insert(wakeRegistrations) + .values({ + subscriberUrl, + sourceUrl, + condition: `runFinished`, + oneShot: false, }) - ).toHaveLength(1) - - await shapeStreamState.latest!.emit([ - { - key: `1`, - headers: { - operation: `delete`, - }, - }, - ]) + .returning() - expect( - registry.evaluate(`/child/c1`, { + try { + let results: Awaited> = [] + const event = { type: `run`, - key: `run-2`, + key: `run-1`, value: { status: `completed` }, headers: { operation: `update` }, - }) - ).toHaveLength(0) - - await registry.stopSync() - expect(shapeStreamState.latest!.signal?.aborted).toBe(true) - }) + } + const deadline = Date.now() + 15_000 + do { + results = await registry.evaluate(sourceUrl, event) + if (results.length > 0) break + await new Promise((resolve) => setTimeout(resolve, 100)) + } while (Date.now() < deadline) + + expect(results).toHaveLength(1) + expect(results[0]!.registrationDbId).toBe(rows[0]!.id) + } finally { + await registry.stopSync() + await db + .delete(wakeRegistrations) + .where(eq(wakeRegistrations.sourceUrl, sourceUrl)) + } + }, 25_000) }) diff --git a/packages/agents-server/test/wake-registry.test.ts b/packages/agents-server/test/wake-registry.test.ts index c879a124ca..28d4eae148 100644 --- a/packages/agents-server/test/wake-registry.test.ts +++ b/packages/agents-server/test/wake-registry.test.ts @@ -29,6 +29,14 @@ import { import type { Server } from 'node:http' import type { WakeEvalResult } from '../src/wake-registry' +async function createLocalRegistry( + tenantId?: string | null +): Promise { + const registry = new WakeRegistry(createMockDb(), tenantId) + await registry.startLocalForTests() + return registry +} + let nextDbId = 1 function createMockDb(): any { return { @@ -61,8 +69,237 @@ function createMockDb(): any { } describe(`Wake Registry`, () => { + it(`evaluates registrations from local TanStack DB collection`, async () => { + const registry = await createLocalRegistry() + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/c1`, + condition: `runFinished`, + oneShot: false, + }) + + const results = await registry.evaluate(`/child/c1`, { + type: `run`, + key: `run-1`, + value: { status: `completed` }, + headers: { operation: `update` }, + }) + + expect(results).toHaveLength(1) + expect(results[0]!.subscriberUrl).toBe(`/parent/p1`) + expect(results[0]!.registrationDbId).toBe(1) + expect(results[0]!.sourceEventKey).toBe(`update:run-1`) + }) + + it(`reconciles invisible insert conflicts to the locally allocated database row`, async () => { + const registry = await createLocalRegistry() + ;(registry as any).mode = `electric` + ;(registry as any).allocateRuntimeId = async () => 42 + ;(registry as any).persistInsert = async (row: any) => ({ + txid: 123, + row, + }) + const timeout = new Error(`timed out`) + timeout.name = `TimeoutWaitingForTxIdError` + ;(registry as any).requireCollection().utils.awaitTxId = async () => { + throw timeout + } + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/conflict`, + condition: `runFinished`, + oneShot: false, + }) + + const results = await registry.evaluate(`/child/conflict`, { + type: `run`, + key: `run-1`, + value: { status: `completed` }, + headers: { operation: `update` }, + }) + + expect(results).toHaveLength(1) + expect(results[0]!.registrationDbId).toBe(42) + }) + + it(`keeps registrations locally visible when Electric tx visibility times out`, async () => { + const registry = await createLocalRegistry() + ;(registry as any).mode = `electric` + ;(registry as any).allocateRuntimeId = async () => 42 + ;(registry as any).persistInsert = async (row: any) => ({ txid: 123, row }) + const timeout = new Error(`timed out`) + timeout.name = `TimeoutWaitingForTxIdError` + ;(registry as any).requireCollection().utils.awaitTxId = async () => { + throw timeout + } + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/slow-visible`, + condition: `runFinished`, + oneShot: false, + }) + + const results = await registry.evaluate(`/child/slow-visible`, { + type: `run`, + key: `run-1`, + value: { status: `completed` }, + headers: { operation: `update` }, + }) + + expect(results).toHaveLength(1) + }) + + it(`keeps committed one-shot deletes locally deleted when Electric tx visibility times out`, async () => { + const registry = await createLocalRegistry() + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/one-shot-timeout`, + condition: `runFinished`, + oneShot: true, + }) + ;(registry as any).mode = `electric` + ;(registry as any).persistDeleteRows = async () => 123 + const timeout = new Error(`timed out`) + timeout.name = `TimeoutWaitingForTxIdError` + ;(registry as any).requireCollection().utils.awaitTxId = async () => { + throw timeout + } + + const first = await registry.evaluate(`/child/one-shot-timeout`, { + type: `run`, + key: `run-1`, + value: { status: `completed` }, + headers: { operation: `update` }, + }) + await new Promise((resolve) => setTimeout(resolve, 0)) + const second = await registry.evaluate(`/child/one-shot-timeout`, { + type: `run`, + key: `run-2`, + value: { status: `completed` }, + headers: { operation: `update` }, + }) + + expect(first).toHaveLength(1) + expect(second).toHaveLength(0) + }) + + it(`persists unregister predicates even when matching rows are not locally visible`, async () => { + const registry = await createLocalRegistry() + ;(registry as any).mode = `electric` + let persisted: unknown + ;(registry as any).persistDeleteRows = async (input: unknown) => { + persisted = input + return undefined + } + + await registry.unregisterBySubscriber(`/parent/missing`) + + expect(persisted).toEqual({ + rows: [], + persist: { + kind: `subscriber`, + tenantId: `default`, + subscriberUrl: `/parent/missing`, + }, + }) + }) + + it(`cleans up the Electric collection on stopSync`, async () => { + const registry = await createLocalRegistry() + const cleanup = vi.fn(async () => undefined) + const dispose = vi.fn(async () => undefined) + ;(registry as any).registrationsCollection = { cleanup } + ;(registry as any).registrationsEffect = { dispose } + + await registry.stopSync() + + expect(dispose).toHaveBeenCalledTimes(1) + expect(cleanup).toHaveBeenCalledTimes(1) + }) + + it(`starts the registration effect after retrying an existing collection preload`, async () => { + const registry = await createLocalRegistry() + const preload = vi.fn(async () => undefined) + const startEffect = vi.fn() + ;(registry as any).registrationsCollection = { preload } + ;(registry as any).startRegistrationEffect = startEffect + + await registry.startSync(`http://electric.test`) + + expect(preload).toHaveBeenCalledTimes(1) + expect(startEffect).toHaveBeenCalledTimes(1) + }) + + it(`does not await an invisible txid when runtime timeout update matches no rows`, async () => { + const registry = await createLocalRegistry() + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/stale-timeout`, + condition: `runFinished`, + oneShot: false, + timeoutMs: 1_000, + }) + ;(registry as any).mode = `electric` + ;(registry as any).persistTimeoutConsumed = async () => undefined + + await expect( + (registry as any).markTimeoutConsumed(1, `default`) + ).resolves.toBeUndefined() + }) + + it(`timeout effect delivers timeout wake once and marks row consumed`, async () => { + const registry = await createLocalRegistry() + const delivered: Array = [] + registry.setTimeoutCallback((result) => delivered.push(result)) + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/c1`, + condition: `runFinished`, + oneShot: false, + timeoutMs: 25, + }) + + await new Promise((resolve) => setTimeout(resolve, 80)) + + expect(delivered).toHaveLength(1) + expect(delivered[0]!.wakeMessage.timeout).toBe(true) + + await new Promise((resolve) => setTimeout(resolve, 80)) + expect(delivered).toHaveLength(1) + }) + + it(`local timeout effect does not consume timeout before callback is registered`, async () => { + const registry = await createLocalRegistry() + const delivered: Array = [] + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/c1`, + condition: `runFinished`, + oneShot: false, + timeoutMs: 25, + }) + + await new Promise((resolve) => setTimeout(resolve, 80)) + + expect(delivered).toHaveLength(0) + + registry.setTimeoutCallback((result) => delivered.push(result)) + + await new Promise((resolve) => setTimeout(resolve, 0)) + + expect(delivered).toHaveLength(1) + expect(delivered[0]!.sourceEventKey).toBe(`timeout`) + }) + it(`keeps shared registrations scoped by tenant`, async () => { - const registry = new WakeRegistry(createMockDb(), null) + const registry = await createLocalRegistry(null) await registry.register({ tenantId: `tenant-a`, subscriberUrl: `/tenant-a/parent`, @@ -84,8 +321,8 @@ describe(`Wake Registry`, () => { value: {}, headers: { operation: `insert` }, } - const tenantA = registry.evaluate(`/source/shared`, event, `tenant-a`) - const tenantB = registry.evaluate(`/source/shared`, event, `tenant-b`) + const tenantA = await registry.evaluate(`/source/shared`, event, `tenant-a`) + const tenantB = await registry.evaluate(`/source/shared`, event, `tenant-b`) expect(tenantA).toHaveLength(1) expect(tenantA[0]!.tenantId).toBe(`tenant-a`) @@ -96,7 +333,7 @@ describe(`Wake Registry`, () => { }) it(`dispatches shared timeout callbacks by tenant`, async () => { - const registry = new WakeRegistry(createMockDb(), null) + const registry = await createLocalRegistry(null) const tenantA: Array = [] const tenantB: Array = [] registry.setTimeoutCallback((result) => tenantA.push(result), `tenant-a`) @@ -128,7 +365,7 @@ describe(`Wake Registry`, () => { }) it(`does not consume shared timeout before tenant callback is registered`, async () => { - const registry = new WakeRegistry(createMockDb(), null) + const registry = await createLocalRegistry(null) const delivered: Array = [] await registry.register({ @@ -148,6 +385,7 @@ describe(`Wake Registry`, () => { (result) => delivered.push(result), `tenant-late` ) + await new Promise((resolve) => setTimeout(resolve, 0)) expect(delivered).toHaveLength(1) expect(delivered[0]!.tenantId).toBe(`tenant-late`) @@ -155,7 +393,7 @@ describe(`Wake Registry`, () => { }) it(`evaluates runFinished condition on completed run event`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/parent/p1`, sourceUrl: `/child/c1`, @@ -163,7 +401,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const results = registry.evaluate(`/child/c1`, { + const results = await registry.evaluate(`/child/c1`, { type: `run`, key: `run-1`, value: { status: `completed` }, @@ -181,7 +419,7 @@ describe(`Wake Registry`, () => { }) it(`returns registrationDbId and sourceEventKey for immediate matches`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/watcher/w1`, sourceUrl: `/_cron/abc`, @@ -189,7 +427,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const results = registry.evaluate(`/_cron/abc`, { + const results = await registry.evaluate(`/_cron/abc`, { type: `cron_tick`, key: `tick-7`, value: {}, @@ -201,40 +439,8 @@ describe(`Wake Registry`, () => { expect(results[0]!.sourceEventKey).toBe(`insert:tick-7`) }) - it(`removes cached registrations from shape delete old_value ids`, async () => { - const registry = new WakeRegistry(createMockDb()) - await registry.register({ - subscriberUrl: `/watcher/w1`, - sourceUrl: `/_cron/abc`, - condition: { on: `change` }, - oneShot: false, - }) - - const before = registry.evaluate(`/_cron/abc`, { - type: `cron_tick`, - key: `tick-7`, - value: {}, - headers: { operation: `insert` }, - }) - expect(before).toHaveLength(1) - - await (registry as any).applyShapeMessage({ - key: `shape-key-is-not-the-registration-id`, - old_value: { id: before[0]!.registrationDbId }, - headers: { operation: `delete` }, - }) - - const after = registry.evaluate(`/_cron/abc`, { - type: `cron_tick`, - key: `tick-8`, - value: {}, - headers: { operation: `insert` }, - }) - expect(after).toHaveLength(0) - }) - it(`keeps distinct registrations distinct for the same source event`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/watcher/w1`, sourceUrl: `/_cron/abc`, @@ -248,7 +454,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const results = registry.evaluate(`/_cron/abc`, { + const results = await registry.evaluate(`/_cron/abc`, { type: `cron_tick`, key: `tick-7`, value: {}, @@ -263,7 +469,7 @@ describe(`Wake Registry`, () => { }) it(`runFinished remains active for repeated child completions`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/parent/p1`, sourceUrl: `/child/c1`, @@ -271,7 +477,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const first = registry.evaluate(`/child/c1`, { + const first = await registry.evaluate(`/child/c1`, { type: `run`, key: `run-1`, value: { status: `completed` }, @@ -279,7 +485,7 @@ describe(`Wake Registry`, () => { }) expect(first).toHaveLength(1) - const second = registry.evaluate(`/child/c1`, { + const second = await registry.evaluate(`/child/c1`, { type: `run`, key: `run-2`, value: { status: `completed` }, @@ -289,7 +495,7 @@ describe(`Wake Registry`, () => { }) it(`evaluates collection change condition`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/watcher/w1`, sourceUrl: `/source/s1`, @@ -297,7 +503,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const results = registry.evaluate(`/source/s1`, { + const results = await registry.evaluate(`/source/s1`, { type: `texts`, key: `text-1`, value: { content: `hello` }, @@ -310,7 +516,7 @@ describe(`Wake Registry`, () => { }) it(`includes inbox message details in wake changes`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/agent/self`, sourceUrl: `/agent/self`, @@ -318,7 +524,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const results = registry.evaluate(`/agent/self`, { + const results = await registry.evaluate(`/agent/self`, { type: `inbox`, key: `msg-1`, value: { @@ -347,7 +553,7 @@ describe(`Wake Registry`, () => { }) it(`ignores events for non-matching collections`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/watcher/w1`, sourceUrl: `/source/s1`, @@ -355,7 +561,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const results = registry.evaluate(`/source/s1`, { + const results = await registry.evaluate(`/source/s1`, { type: `toolCalls`, key: `tc-1`, value: {}, @@ -366,7 +572,7 @@ describe(`Wake Registry`, () => { }) it(`filters collection wakes by operation kind when ops is provided`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/watcher/w1`, sourceUrl: `/source/s1`, @@ -378,13 +584,13 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const insertResults = registry.evaluate(`/source/s1`, { + const insertResults = await registry.evaluate(`/source/s1`, { type: `members`, key: `/task/a`, value: { url: `/task/a` }, headers: { operation: `insert` }, }) - const deleteResults = registry.evaluate(`/source/s1`, { + const deleteResults = await registry.evaluate(`/source/s1`, { type: `members`, key: `/task/a`, old_value: { url: `/task/a` }, @@ -401,7 +607,7 @@ describe(`Wake Registry`, () => { }) it(`maps pg-sync old_value from the event value into change.oldValue`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/watcher/w1`, sourceUrl: `/_electric/pg-sync/default/pg-source-1`, @@ -409,7 +615,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const results = registry.evaluate( + const results = await registry.evaluate( `/_electric/pg-sync/default/pg-source-1`, { type: `pg_sync_change`, @@ -434,8 +640,71 @@ describe(`Wake Registry`, () => { }) }) + it(`unregisterBySource removes all matching local collection rows`, async () => { + const registry = await createLocalRegistry() + + await registry.register({ + subscriberUrl: `/parent/a`, + sourceUrl: `/source/1`, + condition: { on: `change` }, + oneShot: false, + }) + await registry.register({ + subscriberUrl: `/parent/b`, + sourceUrl: `/source/1`, + condition: { on: `change` }, + oneShot: false, + }) + await registry.register({ + subscriberUrl: `/parent/c`, + sourceUrl: `/source/2`, + condition: { on: `change` }, + oneShot: false, + }) + + await registry.unregisterBySource(`/source/1`) + + expect( + await registry.evaluate(`/source/1`, { + type: `texts`, + key: `t1`, + value: {}, + headers: { operation: `insert` }, + }) + ).toHaveLength(0) + expect( + await registry.evaluate(`/source/2`, { + type: `texts`, + key: `t2`, + value: {}, + headers: { operation: `insert` }, + }) + ).toHaveLength(1) + }) + + it(`oneShot match removes row before a second immediate evaluation`, async () => { + const registry = await createLocalRegistry() + + await registry.register({ + subscriberUrl: `/parent/p1`, + sourceUrl: `/child/c1`, + condition: `runFinished`, + oneShot: true, + }) + + const event = { + type: `run`, + key: `run-1`, + value: { status: `completed` }, + headers: { operation: `update` }, + } + + expect(await registry.evaluate(`/child/c1`, event)).toHaveLength(1) + expect(await registry.evaluate(`/child/c1`, event)).toHaveLength(0) + }) + it(`cleanup on subscriber deletion removes all registrations`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/parent/p1`, sourceUrl: `/child/c1`, @@ -451,13 +720,13 @@ describe(`Wake Registry`, () => { await registry.unregisterBySubscriber(`/parent/p1`) - const r1 = registry.evaluate(`/child/c1`, { + const r1 = await registry.evaluate(`/child/c1`, { type: `run`, key: `run-1`, value: { status: `completed` }, headers: { operation: `update` }, }) - const r2 = registry.evaluate(`/child/c2`, { + const r2 = await registry.evaluate(`/child/c2`, { type: `texts`, key: `t-1`, value: {}, @@ -469,7 +738,7 @@ describe(`Wake Registry`, () => { }) it(`surgical unregister removes only the targeted subscriber+source pair`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/parent/p1`, sourceUrl: `/child/c1`, @@ -485,13 +754,13 @@ describe(`Wake Registry`, () => { await registry.unregisterBySubscriberAndSource(`/parent/p1`, `/child/c1`) - const r1 = registry.evaluate(`/child/c1`, { + const r1 = await registry.evaluate(`/child/c1`, { type: `run`, key: `run-1`, value: { status: `completed` }, headers: { operation: `update` }, }) - const r2 = registry.evaluate(`/child/c2`, { + const r2 = await registry.evaluate(`/child/c2`, { type: `texts`, key: `t-1`, value: {}, @@ -502,7 +771,7 @@ describe(`Wake Registry`, () => { expect(r2).toHaveLength(1) }) - it(`register() rejects when DB insert fails so callers can catch`, async () => { + it(`local register does not require a backing DB insert`, async () => { const failingDb = { ...createMockDb(), insert: () => ({ @@ -514,6 +783,7 @@ describe(`Wake Registry`, () => { }), } const registry = new WakeRegistry(failingDb) + await registry.startLocalForTests() await expect( registry.register({ @@ -522,11 +792,11 @@ describe(`Wake Registry`, () => { condition: `runFinished`, oneShot: false, }) - ).rejects.toThrow(`connection refused`) + ).resolves.toBeUndefined() }) it(`rebuilds registry from register calls`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/parent/p1`, sourceUrl: `/child/c1`, @@ -540,7 +810,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const runResults = registry.evaluate(`/child/c1`, { + const runResults = await registry.evaluate(`/child/c1`, { type: `run`, key: `run-1`, value: { status: `completed` }, @@ -549,7 +819,7 @@ describe(`Wake Registry`, () => { expect(runResults).toHaveLength(1) expect(runResults[0]!.subscriberUrl).toBe(`/parent/p1`) - const changeResults = registry.evaluate(`/source/s1`, { + const changeResults = await registry.evaluate(`/source/s1`, { type: `texts`, key: `t-1`, value: {}, @@ -560,7 +830,7 @@ describe(`Wake Registry`, () => { }) it(`evaluates shared-state wakes against the shared-state stream path`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/parent/p1`, sourceUrl: `/_electric/shared-state/board-1`, @@ -568,7 +838,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const results = registry.evaluate(`/_electric/shared-state/board-1`, { + const results = await registry.evaluate(`/_electric/shared-state/board-1`, { type: `texts`, key: `t-1`, value: {}, @@ -580,7 +850,7 @@ describe(`Wake Registry`, () => { }) it(`delivers timeout wake when timeoutMs expires`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() const delivered: Array = [] registry.setTimeoutCallback((result) => { delivered.push(result) @@ -605,7 +875,7 @@ describe(`Wake Registry`, () => { }) it(`debounce coalesces rapid events into single wake`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() const debounced: Array = [] registry.setDebounceCallback((result) => { debounced.push(result) @@ -620,7 +890,7 @@ describe(`Wake Registry`, () => { }) for (let i = 0; i < 3; i++) { - const immediate = registry.evaluate(`/source/s1`, { + const immediate = await registry.evaluate(`/source/s1`, { type: `texts`, key: `text-${i}`, value: { content: `msg-${i}` }, @@ -639,7 +909,7 @@ describe(`Wake Registry`, () => { }) it(`keeps debounced registrations distinct for different conditions on the same source`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() const debounced: Array = [] registry.setDebounceCallback((result) => { debounced.push(result) @@ -661,7 +931,7 @@ describe(`Wake Registry`, () => { }) expect( - registry.evaluate(`/source/s1`, { + await registry.evaluate(`/source/s1`, { type: `texts`, key: `text-1`, value: { content: `hello` }, @@ -670,7 +940,7 @@ describe(`Wake Registry`, () => { ).toHaveLength(0) expect( - registry.evaluate(`/source/s1`, { + await registry.evaluate(`/source/s1`, { type: `toolCalls`, key: `tool-1`, value: { name: `search` }, @@ -694,6 +964,40 @@ describe(`Wake Registry`, () => { ).toBe(2) }) + it(`awaits wake registration evaluation results`, async () => { + const wakeResult = { + tenantId: `default`, + subscriberUrl: `/parent/p1`, + registrationDbId: 12, + sourceEventKey: `run-1`, + wakeMessage: { + source: `/child/c1`, + timeout: false, + changes: [{ collection: `runs`, kind: `update`, key: `run-1` }], + }, + runFinishedStatus: `completed`, + } + const wakeRegistry = { + evaluate: vi.fn().mockResolvedValue([wakeResult]), + } + const deliverWakeResult = vi.fn().mockResolvedValue(undefined) + const manager = { + tenantId: `default`, + wakeRegistry, + deliverWakeResult, + } as any + + await EntityManager.prototype.evaluateWakes.call(manager, `/child/c1`, { + type: `run`, + key: `run-1`, + value: { status: `completed` }, + headers: { operation: `update` }, + }) + + expect(wakeRegistry.evaluate).toHaveBeenCalledTimes(1) + expect(deliverWakeResult).toHaveBeenCalledWith(wakeResult) + }) + it(`deliverWakeResult keys fanout by registrationDbId and sourceEventKey`, async () => { const appendIdempotent = vi.fn().mockResolvedValue(undefined) const manager = { @@ -736,7 +1040,7 @@ describe(`Wake Registry`, () => { }) it(`passes includeResponse through evaluate result`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/parent/p1`, sourceUrl: `/child/c1`, @@ -745,7 +1049,7 @@ describe(`Wake Registry`, () => { includeResponse: false, }) - const results = registry.evaluate(`/child/c1`, { + const results = await registry.evaluate(`/child/c1`, { type: `run`, key: `run-1`, value: { status: `completed` }, @@ -756,8 +1060,8 @@ describe(`Wake Registry`, () => { expect(results[0]!.includeResponse).toBe(false) }) - it(`includeResponse defaults to undefined when not set`, async () => { - const registry = new WakeRegistry(createMockDb()) + it(`includeResponse defaults to true when not set`, async () => { + const registry = await createLocalRegistry() await registry.register({ subscriberUrl: `/parent/p1`, sourceUrl: `/child/c1`, @@ -765,7 +1069,7 @@ describe(`Wake Registry`, () => { oneShot: false, }) - const results = registry.evaluate(`/child/c1`, { + const results = await registry.evaluate(`/child/c1`, { type: `run`, key: `run-1`, value: { status: `completed` }, @@ -773,11 +1077,11 @@ describe(`Wake Registry`, () => { }) expect(results).toHaveLength(1) - expect(results[0]!.includeResponse).toBeUndefined() + expect(results[0]!.includeResponse).toBe(true) }) it(`debounced runFinished preserves runFinishedStatus and includeResponse`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() const delivered: Array = [] registry.setDebounceCallback((result) => { delivered.push(result) @@ -793,7 +1097,7 @@ describe(`Wake Registry`, () => { }) // Immediate evaluate should return nothing (debounced) - const results = registry.evaluate(`/child/c1`, { + const results = await registry.evaluate(`/child/c1`, { type: `run`, key: `run-1`, value: { status: `completed` }, @@ -812,7 +1116,7 @@ describe(`Wake Registry`, () => { }) it(`debounced multi-run: status matches changes[0] run key`, async () => { - const registry = new WakeRegistry(createMockDb()) + const registry = await createLocalRegistry() const delivered: Array = [] registry.setDebounceCallback((result) => { delivered.push(result) @@ -827,7 +1131,7 @@ describe(`Wake Registry`, () => { }) // run-1 completes - registry.evaluate(`/child/c1`, { + await registry.evaluate(`/child/c1`, { type: `run`, key: `run-1`, value: { status: `completed` }, @@ -835,7 +1139,7 @@ describe(`Wake Registry`, () => { }) // run-2 fails in the same debounce window - registry.evaluate(`/child/c1`, { + await registry.evaluate(`/child/c1`, { type: `run`, key: `run-2`, value: { status: `failed` }, @@ -968,6 +1272,79 @@ describe(`Wake Registry Integration`, () => { expect(subRes.status).toBeLessThan(300) } + it(`delivers concurrent runFinished wakes from multiple children to the same parent`, async () => { + const ts = Date.now() + const typeName = `wakemulti${ts}` + + const typeRes = await fetch(`${baseUrl}/_electric/entity-types`, { + method: `POST`, + headers: { 'content-type': `application/json` }, + body: JSON.stringify({ + name: typeName, + description: `multiple child runFinished wake test`, + }), + }) + expect(typeRes.status).toBe(201) + + const parentRes = await fetch( + `${baseUrl}/_electric/entities/${typeName}/parent-${ts}`, + { + method: `PUT`, + headers: { 'content-type': `application/json` }, + body: JSON.stringify({}), + } + ) + expect(parentRes.status).toBe(201) + const parent = (await parentRes.json()) as { + url: string + streams: { main: string } + } + + const manager = getElectricAgentsManager() + const children: Array<{ url: string; streams: { main: string } }> = [] + for (const childName of [`a`, `b`, `c`]) { + const childRes = await fetch( + `${baseUrl}/_electric/entities/${typeName}/child-${childName}-${ts}`, + { + method: `PUT`, + headers: { 'content-type': `application/json` }, + body: JSON.stringify({ parent: parent.url }), + } + ) + expect(childRes.status).toBe(201) + const child = (await childRes.json()) as { + url: string + streams: { main: string } + } + children.push(child) + await manager.wakeRegistry.register({ + subscriberUrl: parent.url, + sourceUrl: child.url, + condition: `runFinished`, + oneShot: false, + }) + } + + await Promise.all( + children.map((child, index) => + manager.evaluateWakes(child.url, { + type: `run`, + key: `run-${index}`, + value: { status: `completed`, result: `done-${index}` }, + headers: { operation: `update` }, + }) + ) + ) + + const wakeEvents = await waitForWakeEvents(parent.streams.main, 3) + expect(wakeEvents).toHaveLength(3) + expect( + new Set( + wakeEvents.map((event) => (event.value as { source?: string }).source) + ) + ).toEqual(new Set(children.map((child) => child.url))) + }, 15_000) + it(`spawn with wake registers condition and delivers wake on child run completion`, async () => { const ts = Date.now() const typeName = `wakerf${ts}` diff --git a/packages/agents/package.json b/packages/agents/package.json index 0755d890c9..b39631673d 100644 --- a/packages/agents/package.json +++ b/packages/agents/package.json @@ -54,8 +54,8 @@ "@durable-streams/state": "^0.3.1", "@electric-ax/agents-mcp": "workspace:*", "@electric-ax/agents-runtime": "workspace:*", - "@mariozechner/pi-agent-core": "^0.70.2", - "@mariozechner/pi-ai": "^0.70.2", + "@earendil-works/pi-agent-core": "^0.80.5", + "@earendil-works/pi-ai": "^0.80.5", "@sinclair/typebox": "^0.34.48", "better-sqlite3": "^12.9.0", "nanoid": "^3.3.11", diff --git a/packages/agents/src/agents/horton.ts b/packages/agents/src/agents/horton.ts index 502dc5716e..d0d1928f02 100644 --- a/packages/agents/src/agents/horton.ts +++ b/packages/agents/src/agents/horton.ts @@ -16,7 +16,7 @@ import { type BuiltinAgentModelConfig, type BuiltinModelCatalog, } from '../model-catalog' -import type { AgentTool, StreamFn } from '@mariozechner/pi-agent-core' +import type { AgentTool, StreamFn } from '@earendil-works/pi-agent-core' import { GOAL_SLASH_COMMAND, buildSkillSlashCommands, diff --git a/packages/agents/src/agents/worker.ts b/packages/agents/src/agents/worker.ts index 1b25201d66..c45db121a9 100644 --- a/packages/agents/src/agents/worker.ts +++ b/packages/agents/src/agents/worker.ts @@ -18,7 +18,7 @@ import { type BuiltinModelCatalog, } from '../model-catalog' import type { WorkerToolName } from '../tools/spawn-worker' -import type { AgentTool, StreamFn } from '@mariozechner/pi-agent-core' +import type { AgentTool, StreamFn } from '@earendil-works/pi-agent-core' import type { EntityRegistry, HandlerContext, diff --git a/packages/agents/src/bootstrap.ts b/packages/agents/src/bootstrap.ts index fe88943c51..0b53b3dfc6 100644 --- a/packages/agents/src/bootstrap.ts +++ b/packages/agents/src/bootstrap.ts @@ -33,7 +33,7 @@ import type { ProcessWakeConfig, RuntimeHandler, } from '@electric-ax/agents-runtime' -import type { StreamFn } from '@mariozechner/pi-agent-core' +import type { StreamFn } from '@earendil-works/pi-agent-core' import type { IncomingMessage, ServerResponse } from 'node:http' import type { SkillsRegistry } from '@electric-ax/agents-runtime' diff --git a/packages/agents/src/model-catalog.ts b/packages/agents/src/model-catalog.ts index af901c79cf..cc19e3de60 100644 --- a/packages/agents/src/model-catalog.ts +++ b/packages/agents/src/model-catalog.ts @@ -1,4 +1,4 @@ -import { getModels } from '@mariozechner/pi-ai' +import { getModels } from '@earendil-works/pi-ai/compat' import { MOONSHOT_API_BASE_URL, MOONSHOT_PROVIDER, @@ -62,7 +62,7 @@ export type BuiltinAgentModelConfig = Pick< const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6` const DEFAULT_OPENAI_MODEL = `gpt-4.1` -const DEFAULT_CODEX_MODEL = `gpt-5.4` +const DEFAULT_CODEX_MODEL = `gpt-5.6-terra` const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash` const DEFAULT_MOONSHOT_MODEL = `kimi-k2.6` diff --git a/packages/agents/src/server.ts b/packages/agents/src/server.ts index 8f4f772346..a923e2eebb 100644 --- a/packages/agents/src/server.ts +++ b/packages/agents/src/server.ts @@ -31,7 +31,7 @@ import type { PullWakeRunnerConfig, } from '@electric-ax/agents-runtime' import type { DurableStreamsFetchCacheOptions } from './durable-streams-cache.js' -import type { StreamFn } from '@mariozechner/pi-agent-core' +import type { StreamFn } from '@earendil-works/pi-agent-core' export interface BuiltinAgentsServerOptions { agentServerUrl: string @@ -114,6 +114,11 @@ export class BuiltinAgentsServer { return this._mcpRegistry } + /** Reconnect only the pull-wake subscription, preserving active wake work. */ + reconnectPullWake(): void { + this.pullWakeRunner?.reconnect() + } + /** * Replace the in-memory `extras` list and re-apply the merged config * against the last-known workspace `mcp.json` state. Workspace diff --git a/packages/agents/src/tools/fork.ts b/packages/agents/src/tools/fork.ts index 79779ca345..a30cc35f66 100644 --- a/packages/agents/src/tools/fork.ts +++ b/packages/agents/src/tools/fork.ts @@ -1,7 +1,7 @@ import { Type } from '@sinclair/typebox' import { nanoid } from 'nanoid' import { serverLog } from '../log' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' import type { HandlerContext } from '@electric-ax/agents-runtime' export function createForkTool(ctx: HandlerContext): AgentTool { diff --git a/packages/agents/src/tools/observe-pg-sync.ts b/packages/agents/src/tools/observe-pg-sync.ts index c6c67f50a7..c581dfe6b3 100644 --- a/packages/agents/src/tools/observe-pg-sync.ts +++ b/packages/agents/src/tools/observe-pg-sync.ts @@ -1,6 +1,6 @@ import { Type } from '@sinclair/typebox' import { pgSync, type HandlerContext } from '@electric-ax/agents-runtime' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' function asToolResult(value: unknown) { return { diff --git a/packages/agents/src/tools/set-title.ts b/packages/agents/src/tools/set-title.ts index c0c40efc43..5e705e4a16 100644 --- a/packages/agents/src/tools/set-title.ts +++ b/packages/agents/src/tools/set-title.ts @@ -1,5 +1,5 @@ import { Type } from '@sinclair/typebox' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' import type { HandlerContext } from '@electric-ax/agents-runtime' export function createSetTitleTool(ctx: HandlerContext): AgentTool { diff --git a/packages/agents/src/tools/spawn-worker.ts b/packages/agents/src/tools/spawn-worker.ts index e0a6a5f57d..06d070589d 100644 --- a/packages/agents/src/tools/spawn-worker.ts +++ b/packages/agents/src/tools/spawn-worker.ts @@ -2,7 +2,7 @@ import { Type } from '@sinclair/typebox' import { randomBytes } from 'node:crypto' import { serverLog } from '../log' import type { BuiltinAgentModelConfig } from '../model-catalog' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' import type { HandlerContext } from '@electric-ax/agents-runtime' export const WORKER_TOOL_NAMES = [ diff --git a/packages/agents/src/tools/unobserve-pg-sync.ts b/packages/agents/src/tools/unobserve-pg-sync.ts index bc83490451..86fce7bcc9 100644 --- a/packages/agents/src/tools/unobserve-pg-sync.ts +++ b/packages/agents/src/tools/unobserve-pg-sync.ts @@ -1,6 +1,6 @@ import { Type } from '@sinclair/typebox' import type { HandlerContext } from '@electric-ax/agents-runtime' -import type { AgentTool } from '@mariozechner/pi-agent-core' +import type { AgentTool } from '@earendil-works/pi-agent-core' function asToolResult(value: unknown) { return { diff --git a/packages/agents/test/builtin-pull-wake-registration.test.ts b/packages/agents/test/builtin-pull-wake-registration.test.ts index 524728ccd9..9d3821a3cd 100644 --- a/packages/agents/test/builtin-pull-wake-registration.test.ts +++ b/packages/agents/test/builtin-pull-wake-registration.test.ts @@ -1,15 +1,20 @@ import { createServer, type IncomingMessage, type Server } from 'node:http' -import { afterEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { BuiltinAgentsServer } from '../src/server' +const pullWakeRunnerMocks = vi.hoisted(() => ({ + start: vi.fn(), + reconnect: vi.fn(), + stop: vi.fn(async () => {}), + waitForStopped: vi.fn(async () => {}), +})) + vi.mock(`@electric-ax/agents-runtime`, async (importOriginal) => { const actual = await importOriginal() return { ...(actual as object), createPullWakeRunner: vi.fn(() => ({ - start: vi.fn(), - stop: vi.fn(async () => {}), - waitForStopped: vi.fn(async () => {}), + ...pullWakeRunnerMocks, get running() { return false }, @@ -79,6 +84,13 @@ describe(`BuiltinAgentsServer pull-wake registration`, () => { ReturnType > | null + beforeEach(() => { + pullWakeRunnerMocks.start.mockClear() + pullWakeRunnerMocks.reconnect.mockClear() + pullWakeRunnerMocks.stop.mockClear() + pullWakeRunnerMocks.waitForStopped.mockClear() + }) + afterEach(async () => { await builtinServer?.stop() builtinServer = null @@ -91,6 +103,7 @@ describe(`BuiltinAgentsServer pull-wake registration`, () => { builtinServer = new BuiltinAgentsServer({ agentServerUrl: agentsServer.url, mockStreamFn, + durableStreamsFetchCache: false, pullWake: { runnerId: `test-runner` }, }) @@ -109,6 +122,7 @@ describe(`BuiltinAgentsServer pull-wake registration`, () => { builtinServer = new BuiltinAgentsServer({ agentServerUrl: agentsServer.url, mockStreamFn, + durableStreamsFetchCache: false, pullWake: { runnerId: `test-runner` }, }) @@ -147,6 +161,7 @@ describe(`BuiltinAgentsServer pull-wake registration`, () => { builtinServer = new BuiltinAgentsServer({ agentServerUrl: `${agentsServer.url}/t/svc-agent-1/v1`, mockStreamFn, + durableStreamsFetchCache: false, pullWake: { runnerId: `test-runner`, registerRunner: true }, }) @@ -159,4 +174,40 @@ describe(`BuiltinAgentsServer pull-wake registration`, () => { `/t/svc-agent-1/v1/_electric/runners` ) }) + + it(`reconnects the pull-wake runner without restarting the built-in runtime`, async () => { + agentsServer = await startRecordingAgentsServer() + builtinServer = new BuiltinAgentsServer({ + agentServerUrl: agentsServer.url, + mockStreamFn, + durableStreamsFetchCache: false, + pullWake: { runnerId: `test-runner` }, + }) + await builtinServer.start() + + builtinServer.reconnectPullWake() + + expect(pullWakeRunnerMocks.reconnect).toHaveBeenCalledTimes(1) + expect(pullWakeRunnerMocks.stop).not.toHaveBeenCalled() + }) + + it(`treats pull-wake reconnect as a no-op outside the running lifecycle`, async () => { + agentsServer = await startRecordingAgentsServer() + builtinServer = new BuiltinAgentsServer({ + agentServerUrl: agentsServer.url, + mockStreamFn, + durableStreamsFetchCache: false, + pullWake: { runnerId: `test-runner` }, + }) + + builtinServer.reconnectPullWake() + expect(pullWakeRunnerMocks.reconnect).not.toHaveBeenCalled() + + await builtinServer.start() + await builtinServer.stop() + pullWakeRunnerMocks.reconnect.mockClear() + builtinServer.reconnectPullWake() + + expect(pullWakeRunnerMocks.reconnect).not.toHaveBeenCalled() + }) }) diff --git a/packages/electric-ax/package.json b/packages/electric-ax/package.json index e9e3e39255..aa4d490ea3 100644 --- a/packages/electric-ax/package.json +++ b/packages/electric-ax/package.json @@ -46,7 +46,7 @@ "stylecheck": "eslint . --quiet" }, "dependencies": { - "@durable-streams/client": "^0.2.6", + "@durable-streams/client": "https://pkg.pr.new/@durable-streams/client@293", "@durable-streams/state": "^0.3.1", "@electric-ax/agents": "workspace:*", "@electric-ax/agents-runtime": "workspace:*", @@ -59,10 +59,10 @@ "react": "^19.2.0" }, "devDependencies": { - "@vitest/coverage-v8": "^4.1.0", "@types/node": "^22.19.15", "@types/omelette": "^0.4.5", "@types/react": "^19.2.14", + "@vitest/coverage-v8": "^4.1.0", "ink-testing-library": "^4.0.0", "tsdown": "^0.9.0", "tsx": "^4.19.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 122fcc8ca3..4c2ff827b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,9 +89,6 @@ importers: '@electric-ax/agents-runtime': specifier: workspace:* version: link:../../packages/agents-runtime - '@mariozechner/pi-agent-core': - specifier: ^0.57.1 - version: 0.57.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) '@radix-ui/react-icons': specifier: ^1.3.0 version: 1.3.2(react@19.2.5) @@ -200,9 +197,6 @@ importers: '@electric-ax/agents-runtime': specifier: workspace:* version: link:../../packages/agents-runtime - '@mariozechner/pi-agent-core': - specifier: ^0.70.2 - version: 0.70.2(zod@4.3.6) '@radix-ui/themes': specifier: ^3.3.0 version: 3.3.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -793,7 +787,7 @@ importers: version: link:../../packages/react-hooks next: specifier: ^14.2.5 - version: 14.2.17(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.17(@opentelemetry/api@1.9.1)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) pg: specifier: ^8.12.0 version: 8.13.1 @@ -1536,18 +1530,18 @@ importers: '@durable-streams/state': specifier: ^0.3.1 version: 0.3.1(@tanstack/db@0.6.7(typescript@5.9.3)) + '@earendil-works/pi-agent-core': + specifier: ^0.80.5 + version: 0.80.5(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) + '@earendil-works/pi-ai': + specifier: ^0.80.5 + version: 0.80.5(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) '@electric-ax/agents-mcp': specifier: workspace:* version: link:../agents-mcp '@electric-ax/agents-runtime': specifier: workspace:* version: link:../agents-runtime - '@mariozechner/pi-agent-core': - specifier: ^0.70.2 - version: 0.70.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) - '@mariozechner/pi-ai': - specifier: ^0.70.2 - version: 0.70.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) '@sinclair/typebox': specifier: ^0.34.48 version: 0.34.49 @@ -1673,6 +1667,9 @@ importers: vite-plugin-electron: specifier: ^0.29.1 version: 0.29.1 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.17)(jsdom@28.1.0(@noble/hashes@2.0.1))(lightningcss@1.30.1)(terser@5.48.0) wait-on: specifier: ^9.0.1 version: 9.0.5 @@ -1706,13 +1703,13 @@ importers: version: link:../agents-server-ui '@expo/metro-runtime': specifier: ~6.1.2 - version: 6.1.2(expo@54.0.35)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 6.1.2(expo@54.0.36)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@react-native-async-storage/async-storage': specifier: ^2.2.0 - version: 2.2.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + version: 2.2.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)) '@sentry/react-native': specifier: ~7.2.0 - version: 7.2.0(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 7.2.0(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@tanstack/db': specifier: ^0.6.5 version: 0.6.7(typescript@5.9.3) @@ -1723,41 +1720,41 @@ importers: specifier: ^0.1.85 version: 0.1.85(react@19.1.0)(typescript@5.9.3) expo: - specifier: 54.0.35 - version: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + specifier: 54.0.36 + version: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) expo-build-properties: specifier: ~1.0.10 - version: 1.0.10(expo@54.0.35) + version: 1.0.10(expo@54.0.36) expo-clipboard: specifier: ~8.0.8 - version: 8.0.8(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 8.0.8(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-constants: specifier: ~18.0.13 - version: 18.0.13(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + version: 18.0.13(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)) expo-image-manipulator: specifier: ~14.0.8 - version: 14.0.8(expo@54.0.35) + version: 14.0.8(expo@54.0.36) expo-image-picker: specifier: ~17.0.11 - version: 17.0.11(expo@54.0.35) + version: 17.0.11(expo@54.0.36) expo-linking: specifier: ~8.0.12 - version: 8.0.12(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 8.0.12(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-router: specifier: ~6.0.24 - version: 6.0.24(@expo/metro-runtime@6.1.2)(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.12)(expo@54.0.35)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 6.0.24(@expo/metro-runtime@6.1.2)(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.12)(expo@54.0.36)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-splash-screen: specifier: ~31.0.13 - version: 31.0.13(expo@54.0.35)(typescript@5.9.3) + version: 31.0.13(expo@54.0.36)(typescript@5.9.3) expo-status-bar: specifier: ~3.0.9 - version: 3.0.9(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 3.0.9(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-system-ui: specifier: ~6.0.9 - version: 6.0.9(expo@54.0.35)(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + version: 6.0.9(expo@54.0.36)(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)) expo-web-browser: specifier: ~15.0.11 - version: 15.0.11(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + version: 15.0.11(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)) react: specifier: 19.1.0 version: 19.1.0 @@ -1766,28 +1763,28 @@ importers: version: 19.1.0(react@19.1.0) react-native: specifier: 0.81.5 - version: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + version: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) react-native-gesture-handler: specifier: 2.28.0 - version: 2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 2.28.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-random-uuid: specifier: ^0.1.4 - version: 0.1.4(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + version: 0.1.4(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)) react-native-safe-area-context: specifier: ~5.6.2 - version: 5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-screens: specifier: 4.16.0 - version: 4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-svg: specifier: 15.12.1 - version: 15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 15.12.1(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-web: specifier: ^0.21.2 version: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-native-webview: specifier: 13.15.0 - version: 13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) zod: specifier: ^4.4.3 version: 4.4.3 @@ -1806,7 +1803,7 @@ importers: version: 4.1.5(vitest@4.1.5) babel-preset-expo: specifier: ~54.0.10 - version: 54.0.10(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@54.0.35)(react-refresh@0.14.2) + version: 54.0.10(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@54.0.36)(react-refresh@0.14.2) expo-doctor: specifier: ^1.19.8 version: 1.19.8 @@ -1823,20 +1820,20 @@ importers: specifier: ^0.78.0 version: 0.78.0(zod@4.3.6) '@durable-streams/client': - specifier: ^0.2.6 - version: 0.2.6 + specifier: https://pkg.pr.new/@durable-streams/client@293 + version: https://pkg.pr.new/@durable-streams/client@293 '@durable-streams/state': specifier: ^0.3.1 version: 0.3.1(@tanstack/db@0.6.7(typescript@5.9.3)) + '@earendil-works/pi-agent-core': + specifier: ^0.80.5 + version: 0.80.5(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) + '@earendil-works/pi-ai': + specifier: ^0.80.5 + version: 0.80.5(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) '@electric-ax/agents-mcp': specifier: workspace:* version: link:../agents-mcp - '@mariozechner/pi-agent-core': - specifier: ^0.70.2 - version: 0.70.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) - '@mariozechner/pi-ai': - specifier: ^0.70.2 - version: 0.70.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) '@mozilla/readability': specifier: ^0.6.0 version: 0.6.0 @@ -1929,29 +1926,35 @@ importers: specifier: ^0.78.0 version: 0.78.0(zod@4.4.3) '@durable-streams/client': - specifier: ^0.2.6 - version: 0.2.6 + specifier: https://pkg.pr.new/@durable-streams/client@293 + version: https://pkg.pr.new/@durable-streams/client@293 '@durable-streams/server': specifier: ^0.3.7 version: 0.3.7(@tanstack/db@0.6.7(typescript@5.8.3)) '@durable-streams/state': specifier: ^0.3.1 version: 0.3.1(@tanstack/db@0.6.7(typescript@5.8.3)) + '@earendil-works/pi-agent-core': + specifier: ^0.80.5 + version: 0.80.5(zod@4.4.3) '@electric-ax/agents-runtime': specifier: workspace:* version: link:../agents-runtime '@electric-sql/client': specifier: ^1.5.22 version: link:../typescript-client - '@mariozechner/pi-agent-core': - specifier: ^0.70.2 - version: 0.70.2(zod@4.4.3) '@opentelemetry/api': specifier: ^1.9.1 version: 1.9.1 '@sinclair/typebox': specifier: ^0.34.48 version: 0.34.49 + '@tanstack/db': + specifier: ^0.6.7 + version: 0.6.7(typescript@5.8.3) + '@tanstack/electric-db-collection': + specifier: ^0.3.5 + version: 0.3.5(typescript@5.8.3) '@whatwg-node/server': specifier: ^0.10.18 version: 0.10.18 @@ -2023,10 +2026,10 @@ importers: packages/agents-server-conformance-tests: dependencies: '@durable-streams/client': - specifier: ^0.2.6 - version: 0.2.6 + specifier: https://pkg.pr.new/@durable-streams/client@293 + version: https://pkg.pr.new/@durable-streams/client@293 '@electric-sql/client': - specifier: ^1.5.22 + specifier: ^1.5.21 version: link:../typescript-client fast-check: specifier: ^4.6.0 @@ -2035,12 +2038,12 @@ importers: specifier: ^4.1.0 version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.0(@noble/hashes@2.0.1))(vite@7.3.2(@types/node@25.9.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0)) devDependencies: + '@earendil-works/pi-agent-core': + specifier: ^0.80.5 + version: 0.80.5 '@electric-ax/agents-server': specifier: workspace:* version: link:../agents-server - '@mariozechner/pi-agent-core': - specifier: ^0.70.2 - version: 0.70.2 tsdown: specifier: ^0.9.0 version: 0.9.9(typescript@5.8.3) @@ -2054,8 +2057,8 @@ importers: specifier: ^1.4.1 version: 1.4.1(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@durable-streams/client': - specifier: ^0.2.6 - version: 0.2.6 + specifier: https://pkg.pr.new/@durable-streams/client@293 + version: https://pkg.pr.new/@durable-streams/client@293 '@durable-streams/state': specifier: ^0.3.1 version: 0.3.1(@tanstack/db@0.6.7(typescript@5.9.3)) @@ -2172,8 +2175,8 @@ importers: packages/electric-ax: dependencies: '@durable-streams/client': - specifier: ^0.2.6 - version: 0.2.6 + specifier: https://pkg.pr.new/@durable-streams/client@293 + version: https://pkg.pr.new/@durable-streams/client@293 '@durable-streams/state': specifier: ^0.3.1 version: 0.3.1(@tanstack/db@0.6.7(typescript@5.9.3)) @@ -2735,15 +2738,6 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@anthropic-ai/sdk@0.73.0': - resolution: {integrity: sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==} - hasBin: true - peerDependencies: - zod: ^3.25.0 || ^4.0.0 - peerDependenciesMeta: - zod: - optional: true - '@anthropic-ai/sdk@0.78.0': resolution: {integrity: sha512-PzQhR715td/m1UaaN5hHXjYB8Gl2lF9UVhrrGrZeysiF6Rb74Wc9GCB8hzLdzmQtBd1qe89F9OptgB9Za1Ib5w==} hasBin: true @@ -2753,8 +2747,8 @@ packages: zod: optional: true - '@anthropic-ai/sdk@0.90.0': - resolution: {integrity: sha512-MzZtPabJF1b0FTDl6Z6H5ljphPwACLGP13lu8MTiB8jXaW/YXlpOp+Po2cVou3MPM5+f5toyLnul9whKCy7fBg==} + '@anthropic-ai/sdk@0.91.1': + resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} hasBin: true peerDependencies: zod: ^3.25.0 || ^4.0.0 @@ -2795,10 +2789,6 @@ packages: '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} - '@aws-crypto/crc32@5.2.0': - resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} - engines: {node: '>=16.0.0'} - '@aws-crypto/sha256-browser@5.2.0': resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} @@ -2812,136 +2802,92 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-bedrock-runtime@3.1037.0': - resolution: {integrity: sha512-Evla4DUdBf1pQpQa7pbfquj7jRaRktkI0qGoWBJBXWB9wQISzJ8OEI4sHugk/W6SF47C7hMP/o3Z/XBrfnejCw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/core@3.974.5': - resolution: {integrity: sha512-lMPlYlYfQdNZhlkJgnkmESwrY+hNh3PljmZ+37oAqLNdJ6rnILAwFSyc6B3bJeDOtMORNnMQIej0aTRuOlDyhQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-env@3.972.31': - resolution: {integrity: sha512-X/yGB73LmDW/6MdDJGCDzZBUXnM3ys4vs9l+5ZTJmiEswDdP1OjeoAFlFjVGS9o4KB2wZWQ9KOfdVNSSK6Ep3w==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-http@3.972.33': - resolution: {integrity: sha512-c0ZF+lwoWVvX5iCaGKL5T/4DnIw88CGqxA0BcBs3U86mIp5EZYPVg+KSPkMXOyokmADvNewiMUfSG2uFwjRp0g==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-ini@3.972.35': - resolution: {integrity: sha512-jsU4u/cRkKFLKQS0k918FQ27fzXLG5ENiLWQMYE6581zLeI2hWh04ptlrvZMB3wJT/5d+vSzJk74X1CMFr4y8Q==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-login@3.972.35': - resolution: {integrity: sha512-5oa3j0cA50jPqgNhZ9XdJVopuzUf1klRb28/2MfLYWWiPi9DRVvbrBWT+DidbHTT36520VuXZJahQwR+YgSjrg==} + '@aws-sdk/client-bedrock-runtime@3.1048.0': + resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.36': - resolution: {integrity: sha512-4nT2T8Z7vH8KE9EdjEsuIlHpZSlcaK2PrKbQBjuUGU46BCCzF3WvP0u0Uiosni3Ykmmn4rWLVawoOCLotUtCbg==} + '@aws-sdk/core@3.975.1': + resolution: {integrity: sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.31': - resolution: {integrity: sha512-eKeT4MXumpBJsrDLCYcSzIkFPVTFn/es7It2oogp2OhU/ic7P/+xzFpQx9ZhwtXS57Mc5S42BPWi7lHmvs/nYg==} + '@aws-sdk/credential-provider-env@3.972.57': + resolution: {integrity: sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.972.35': - resolution: {integrity: sha512-bCuBdfnj0KGDMdLp6utMTLiJcFN2ek9EgZinxQZZSc3FxjJ/HSqeqab2cjbnoNfy8RM6suDCsRkmVY1izp9I+A==} + '@aws-sdk/credential-provider-http@3.972.59': + resolution: {integrity: sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.35': - resolution: {integrity: sha512-swW6Bwvl8lanyEMtZOWE/oR6yqcRQH4HTQZUVsnDVgoXvRjRywpYpLv2BWwjUFyjPrqsdX6FeTkf4tMSe/qFTQ==} + '@aws-sdk/credential-provider-ini@3.973.1': + resolution: {integrity: sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/eventstream-handler-node@3.972.14': - resolution: {integrity: sha512-m4X56gxG76/CKfxNVbOFuYwnAZcHgS6HOH8lgp15HoGHIAVTcZfZrXvcYzJFOMLEJgVn+JHBu6EiNV+xSNXXFg==} + '@aws-sdk/credential-provider-login@3.972.63': + resolution: {integrity: sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-eventstream@3.972.10': - resolution: {integrity: sha512-QUqLs7Af1II9X4fCRAu+EGHG3KHyOp4RkuLhRKoA3NuFlh6TL8i+zXBl8w2LUxqm44B/Kom45hgSlwA1SpTsXQ==} + '@aws-sdk/credential-provider-node@3.972.66': + resolution: {integrity: sha512-f+qjRXZpz7sgzbc4QB+6nLKfyKFgRRXzWdXbsKPv/VhVRyHsDyq4yBWC/B75BAJpFIcUeI2XR/3gdWJ677zB4A==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-host-header@3.972.10': - resolution: {integrity: sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==} + '@aws-sdk/credential-provider-process@3.972.57': + resolution: {integrity: sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-logger@3.972.10': - resolution: {integrity: sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==} + '@aws-sdk/credential-provider-sso@3.973.1': + resolution: {integrity: sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-recursion-detection@3.972.11': - resolution: {integrity: sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==} + '@aws-sdk/credential-provider-web-identity@3.972.63': + resolution: {integrity: sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.34': - resolution: {integrity: sha512-/UL96JKjsjdodcRRMKl99tLQvK6Oi9ptLC9iU1yiTF/ruaDX0mtBBtnLNZDxIZRJOCVOtB49ed1YaTadqygk8Q==} + '@aws-sdk/eventstream-handler-node@3.972.26': + resolution: {integrity: sha512-RE1fu7Nn05vG0EUJM+8Sde2GFecC658WGaC/asPzLF6K4x3H5ZaDBcQtHRE67Gdgb1VZpyUUliYejHFK1qt0Uw==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-user-agent@3.972.35': - resolution: {integrity: sha512-hOFWNOjVmOocpRlrU04nYxjMOeoe0Obu5AXEuhB8zblMCPl3cG1hdluQCZERRKFyhMQjwZnDbhSHjoMUjetFGw==} + '@aws-sdk/middleware-eventstream@3.972.22': + resolution: {integrity: sha512-jtkgmhevnpzC1WeS+Y/sgymYbaQ6qg7pVOUl5cUT/8MiLptqrtnXQlNV80m+j2WIx5MIL7kVHIZNxxcK2tfUEQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-websocket@3.972.16': - resolution: {integrity: sha512-86+S9oCyRVGzoMRpQhxkArp7kD2K75GPmaNevd9B6EyNhWoNvnCZZ3WbgN4j7ZT+jvtvBCGZvI2XHsWZJ+BRIg==} + '@aws-sdk/middleware-websocket@3.972.39': + resolution: {integrity: sha512-CS1spxRSezmTmI3PD+3Xrnp6KryTSEz0EefA8u6uGd0s2I0uXseWHALDI/03Wi0IUczXNWo2QrZEaHDuJNby/Q==} engines: {node: '>= 14.0.0'} - '@aws-sdk/nested-clients@3.997.3': - resolution: {integrity: sha512-SivE6GP228IVgfsrr2c/vqTg95X0Qj39Yw4uIrcddpkUzIltNMoNOR62leHOLhODfjv9K8X2mPTwS69A5kT0nQ==} + '@aws-sdk/nested-clients@3.997.31': + resolution: {integrity: sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==} engines: {node: '>=20.0.0'} - '@aws-sdk/region-config-resolver@3.972.13': - resolution: {integrity: sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==} + '@aws-sdk/signature-v4-multi-region@3.996.39': + resolution: {integrity: sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.22': - resolution: {integrity: sha512-/rXhMXteD+BqhFd0nYprAgcZ/KtU+963uftPqd3tiFcFfooHZINXUGtOmo2SQjRVauCTNqIEzkwuSETdZFqTTA==} + '@aws-sdk/token-providers@3.1048.0': + resolution: {integrity: sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1036.0': - resolution: {integrity: sha512-aNSJ6jjDYayxN9ZA1JpycVScX93Lx03kKZ1EXt3DGOTahcWVLJj3oLAlop0xKP+vP2Ga2t49p1tEaMkTbCCaZA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/token-providers@3.1037.0': - resolution: {integrity: sha512-csxa484KboWLs3f8jFQ5v9RwH8FVf0fQ+SO3GSXyu4Jtinhh4qXmOWLSVX30RBpB933dZaKGHGEXzEEY88NqRw==} + '@aws-sdk/token-providers@3.1083.0': + resolution: {integrity: sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==} engines: {node: '>=20.0.0'} '@aws-sdk/types@3.973.8': resolution: {integrity: sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-arn-parser@3.972.3': - resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-endpoints@3.996.8': - resolution: {integrity: sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-format-url@3.972.10': - resolution: {integrity: sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==} + '@aws-sdk/types@3.974.0': + resolution: {integrity: sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==} engines: {node: '>=20.0.0'} '@aws-sdk/util-locate-window@3.965.5': resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-user-agent-browser@3.972.10': - resolution: {integrity: sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==} - - '@aws-sdk/util-user-agent-node@3.973.21': - resolution: {integrity: sha512-Av4UHTcAWgdvbN0IP9pbtf4Qa1+6LtJqQdZWj5pLn5J67w0pnJJAZZ+7JPPcj2KN3378zD2JDM9DwJKEyvyMTQ==} - engines: {node: '>=20.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true - - '@aws-sdk/xml-builder@3.972.19': - resolution: {integrity: sha512-Cw8IOMdBUEIl8ZlhRC3Dc/E64D5B5/8JhV6vhPLiPfJwcRC84S6F8aBOIi/N4vR9ZyA4I5Cc0Ateb/9EHaJXeQ==} + '@aws-sdk/xml-builder@3.972.34': + resolution: {integrity: sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==} engines: {node: '>=20.0.0'} - '@aws/lambda-invoke-store@0.2.4': - resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + '@aws/lambda-invoke-store@0.3.0': + resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} engines: {node: '>=18.0.0'} '@babel/code-frame@7.10.4': @@ -4772,6 +4718,12 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + '@durable-streams/client@https://pkg.pr.new/@durable-streams/client@293': + resolution: {tarball: https://pkg.pr.new/@durable-streams/client@293} + version: 0.2.6 + engines: {node: '>=18.0.0'} + hasBin: true + '@durable-streams/server-conformance-tests@0.3.5': resolution: {integrity: sha512-sXl2S562yVa+gO9ypZMJ3rC9lMpsUoc7XIBw24iY/k8g0RczgKdK3jYpgc10bttZLQLLGQMo3QxZ+pbGgIO5dw==} engines: {node: '>=18.0.0'} @@ -4791,6 +4743,15 @@ packages: '@tanstack/db': optional: true + '@earendil-works/pi-agent-core@0.80.5': + resolution: {integrity: sha512-413NZ5BEwSdL+BAtoBlk7hcA3UGvltcdAPtFoMZ1uqLZRNIoFU0WhNXGwv5Sp1SPBmMR6MAc3neoA+MdOlWJ2w==} + engines: {node: '>=22.19.0'} + + '@earendil-works/pi-ai@0.80.5': + resolution: {integrity: sha512-+7UODYKu4o/X8sxTHG8HEV3+O+dOYKg5MeO3MJIFQ+wu65pIWDhjZGc759uWsRr79AhV7MSJEe4GWnFn4l68gw==} + engines: {node: '>=22.19.0'} + hasBin: true + '@ecies/ciphers@0.2.5': resolution: {integrity: sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} @@ -6080,8 +6041,8 @@ packages: resolution: {integrity: sha512-uF1pOVcd+xizNtVTuZqNGzy7I6IJon5YMmQidsURds1Ww96AFDxrR/NEACqeATNAmY60m8wy1VZZpSg5zLNkpw==} hasBin: true - '@expo/cli@54.0.25': - resolution: {integrity: sha512-WnUqIb8oMBhtwSfIqdCHCzcaDIpLNXItRVd5miuvWi4GO0SGo89PSsAkbVJ+LJgcaY+v5rbgMELJS9I/CqOulA==} + '@expo/cli@54.0.26': + resolution: {integrity: sha512-BjsAoKINLEo3LRE+sDC6FCgjxuOWsyfOFOKz0txrbEcxSatzIjJDVuX8XaTdmeicZdcoN524yl1sfwCWfxhYMw==} hasBin: true peerDependencies: expo: '*' @@ -6105,6 +6066,9 @@ packages: '@expo/config-plugins@54.0.4': resolution: {integrity: sha512-g2yXGICdoOw5i3LkQSDxl2Q5AlQCrG7oniu0pCPPO+UxGb7He4AFqSvPSy8HpRUj55io17hT62FTjYRD+d6j3Q==} + '@expo/config-plugins@54.0.5': + resolution: {integrity: sha512-aWQ3sViNRoQWw6So4A2qhWCt24CuGBK6MrRHI1AG+V6/NQAjIZCHaSvcXK2gXKpmisRhTSUWaKPIgLJQFB+AeQ==} + '@expo/config-types@53.0.5': resolution: {integrity: sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g==} @@ -6117,6 +6081,9 @@ packages: '@expo/config@12.0.13': resolution: {integrity: sha512-Cu52arBa4vSaupIWsF0h7F/Cg//N374nYb7HAxV0I4KceKA7x2UXpYaHOL7EEYYvp7tZdThBjvGpVmr8ScIvaQ==} + '@expo/config@12.0.14': + resolution: {integrity: sha512-3dfbBd9LnPDgyylhCgkOsaG8Adg52uOVOTQYH5lf23a/t8M5eQpXKCvzUarrf62B78057n2NnkiofK9TfPgvzw==} + '@expo/devcert@1.2.0': resolution: {integrity: sha512-Uilcv3xGELD5t/b0eM4cxBFEKQRIivB3v7i+VhWLV/gL98aw810unLKKJbGAxAIhY6Ipyz8ChWibFsKFXYwstA==} @@ -6140,6 +6107,9 @@ packages: '@expo/env@2.0.11': resolution: {integrity: sha512-xV+ps6YCW7XIPVUwFVCRN2nox09dnRwy8uIjwHWTODu0zFw4kp4omnVkl0OOjuu2XOe7tdgAHxikrkJt9xB/7Q==} + '@expo/env@2.0.12': + resolution: {integrity: sha512-wVfzeBGlUohZG5kS8QCqXurpuWZFJEkBB1wXCifai3EZ/Llcg/VMTiUCpAgHImD3lI7GIU3V1uI64c04XIo98Q==} + '@expo/fingerprint@0.13.4': resolution: {integrity: sha512-MYfPYBTMfrrNr07DALuLhG6EaLVNVrY/PXjEzsjWdWE4ZFn0yqI0IdHNkJG7t1gePT8iztHc7qnsx+oo/rDo6w==} hasBin: true @@ -6169,8 +6139,8 @@ packages: '@expo/metro-config@0.20.17': resolution: {integrity: sha512-lpntF2UZn5bTwrPK6guUv00Xv3X9mkN3YYla+IhEHiYXWyG7WKOtDU0U4KR8h3ubkZ6SPH3snDyRyAzMsWtZFA==} - '@expo/metro-config@54.0.16': - resolution: {integrity: sha512-3LLb9ZQl0VlqSlsalJ7+CYjfz60PBoSDHvpE1UF71aTM1Nx0Vb4LhXo7bCCC+PYP9q/GPB58LLbIROQ8PjKX2w==} + '@expo/metro-config@54.0.17': + resolution: {integrity: sha512-PQFgQCZGY0DffZUvBzJttDPreZfHrQakaBlKjnvOUMNXbDna+TYmg1IFZuIDUYJezLcdp+TvVFTLjNi1+mqaVw==} peerDependencies: expo: '*' peerDependenciesMeta: @@ -6224,6 +6194,11 @@ packages: peerDependencies: expo: '*' + '@expo/prebuild-config@54.0.9': + resolution: {integrity: sha512-3/Rmyzt8vduPjnSVHbnc0wYFrlhwLWn2g596rDyKcLGeqN2WTLJbzVeznsrUwyzhBNXgnTomZWO5HDzbZ/4E7g==} + peerDependencies: + expo: '*' + '@expo/prebuild-config@9.0.11': resolution: {integrity: sha512-0DsxhhixRbCCvmYskBTq8czsU0YOBsntYURhWPNpkl0IPVpeP9haE5W4OwtHGzXEbmHdzaoDwNmVcWjS/mqbDw==} @@ -6238,6 +6213,9 @@ packages: '@expo/schema-utils@0.1.8': resolution: {integrity: sha512-9I6ZqvnAvKKDiO+ZF8BpQQFYWXOJvTAL5L/227RUbWG1OVZDInFifzCBiqAZ3b67NRfeAgpgvbA7rejsqhY62A==} + '@expo/schema-utils@0.1.9': + resolution: {integrity: sha512-t9bYwG4Z0yCVzHYJoDMci1OFq2FkBkhStlfUGSkspKYTwB/84+x6sY+CXCgdhkQNQtvWaugW5KUs9YZfAXq9Sg==} + '@expo/sdk-runtime-versions@1.0.0': resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} @@ -6319,8 +6297,8 @@ packages: '@fontsource/alegreya-sans@5.1.1': resolution: {integrity: sha512-vQAwr25Pk5N5Y924AxaGpipZQY9IdulIRS4+WXbNiHCVwDS/i6k7c46UdyBlhvnPM33JII7ndv/gJ9BP8i11bA==} - '@google/genai@1.50.1': - resolution: {integrity: sha512-YbkX7H9+1Pt8wOt7DDREy8XSoiL6fRDzZQRyaVBarFf8MR3zHGqVdvM4cLbDXqPhxqvegZShgfxb8kw9C7YhAQ==} + '@google/genai@1.52.0': + resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==} engines: {node: '>=20.0.0'} peerDependencies: '@modelcontextprotocol/sdk': ^1.25.2 @@ -6917,39 +6895,19 @@ packages: '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} - '@mariozechner/pi-agent-core@0.57.1': - resolution: {integrity: sha512-WXsBbkNWOObFGHkhixaT8GXJpHDd3+fn8QntYF+4R8Sa9WB90ENXWidO6b7vcKX+JX0jjO5dIsQxmzosARJKlg==} - engines: {node: '>=20.0.0'} - deprecated: please use @earendil-works/pi-agent-core instead going forward - - '@mariozechner/pi-agent-core@0.70.2': - resolution: {integrity: sha512-g1hIdKyDwmQOoBGO0R4OhpemKeMENeK0vE5FJtuQKqEcsdCAkVBgZAK6aZUARYZVxMA718JS6WPLFWoddzjD7g==} - engines: {node: '>=20.0.0'} - deprecated: please use @earendil-works/pi-agent-core instead going forward - - '@mariozechner/pi-ai@0.57.1': - resolution: {integrity: sha512-Bd/J4a3YpdzJVyHLih0vDSdB0QPL4ti0XsAwtHOK/8eVhB0fHM1CpcgIrcBFJ23TMcKXMi0qamz18ERfp8tmgg==} - engines: {node: '>=20.0.0'} - deprecated: please use @earendil-works/pi-ai instead going forward - hasBin: true - - '@mariozechner/pi-ai@0.70.2': - resolution: {integrity: sha512-+30LRPjXsXF+oI96DvGWMbdPGeqoLJvadh6UPev7wx2DzhC9FEqXkQcoMZ0usbCm7E9pl8ua8a9s/pQ5ikaUbg==} - engines: {node: '>=20.0.0'} - deprecated: please use @earendil-works/pi-ai instead going forward - hasBin: true - '@mermaid-js/parser@1.1.0': resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==} '@microsoft/fetch-event-source@2.0.1': resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==} - '@mistralai/mistralai@1.14.1': - resolution: {integrity: sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ==} - - '@mistralai/mistralai@2.2.1': - resolution: {integrity: sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==} + '@mistralai/mistralai@2.2.6': + resolution: {integrity: sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true '@mixmark-io/domino@2.2.0': resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} @@ -7097,9 +7055,6 @@ packages: resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} - '@nodable/entities@2.1.0': - resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -7128,6 +7083,10 @@ packages: resolution: {integrity: sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==} engines: {node: '>=20.0'} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} @@ -9564,194 +9523,50 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - '@smithy/config-resolver@4.4.17': - resolution: {integrity: sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==} + '@smithy/core@3.29.2': + resolution: {integrity: sha512-DXUk6yU0C1Q1tYvJh1VCtl8QOBcSoZpKwjTPkxT6A4MUQYHvgeKGByL8mrEdxnvhdf9nq5GyzmRb5n/vPgu3Lw==} engines: {node: '>=18.0.0'} - '@smithy/core@3.23.17': - resolution: {integrity: sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==} + '@smithy/credential-provider-imds@4.4.7': + resolution: {integrity: sha512-UEMLOoA0Fl4uYBxh6l0uN0H6EJe/A89OGeDNTteQeXpJ20BcpfIr4wlCY9pel1jEAUHAxaYwuqrYlrKdXE1GKQ==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.2.14': - resolution: {integrity: sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-codec@4.2.14': - resolution: {integrity: sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-browser@4.2.14': - resolution: {integrity: sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-config-resolver@4.3.14': - resolution: {integrity: sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-node@4.2.14': - resolution: {integrity: sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-universal@4.2.14': - resolution: {integrity: sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==} - engines: {node: '>=18.0.0'} - - '@smithy/fetch-http-handler@5.3.17': - resolution: {integrity: sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==} - engines: {node: '>=18.0.0'} - - '@smithy/hash-node@4.2.14': - resolution: {integrity: sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==} - engines: {node: '>=18.0.0'} - - '@smithy/invalid-dependency@4.2.14': - resolution: {integrity: sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==} + '@smithy/fetch-http-handler@5.6.4': + resolution: {integrity: sha512-psnst7NZWdAEvJvyW8YZEE7xNVMyLrQFfHtyrVFrxNyy+dKWkQ+rqC6oI5ZhxThpUy9RSfEshgm34zqbOxzsRw==} engines: {node: '>=18.0.0'} '@smithy/is-array-buffer@2.2.0': resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} - '@smithy/is-array-buffer@4.2.2': - resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-content-length@4.2.14': - resolution: {integrity: sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-endpoint@4.4.32': - resolution: {integrity: sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-retry@4.5.5': - resolution: {integrity: sha512-wnYOpB5vATFKWrY2Z9Alb0KhjZI6AbzU6Fbz3Hq2GnURdRYWB4q+qWivQtSTwXcmWUA3MZ6krfwL6Cq5MAbxsA==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-serde@4.2.20': - resolution: {integrity: sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-stack@4.2.14': - resolution: {integrity: sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==} - engines: {node: '>=18.0.0'} - - '@smithy/node-config-provider@4.3.14': - resolution: {integrity: sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==} + '@smithy/node-http-handler@4.7.3': + resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.6.1': - resolution: {integrity: sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==} + '@smithy/node-http-handler@4.9.4': + resolution: {integrity: sha512-BNTop/fSOptmoVk8g+efwHCofFh37g70OWGAFES1TeAAJja1K5aAI8rTE26ETSc5k8IQuWY2kAIoPla01NgYrA==} engines: {node: '>=18.0.0'} - '@smithy/property-provider@4.2.14': - resolution: {integrity: sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==} - engines: {node: '>=18.0.0'} - - '@smithy/protocol-http@5.3.14': - resolution: {integrity: sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==} - engines: {node: '>=18.0.0'} - - '@smithy/querystring-builder@4.2.14': - resolution: {integrity: sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==} - engines: {node: '>=18.0.0'} - - '@smithy/querystring-parser@4.2.14': - resolution: {integrity: sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==} - engines: {node: '>=18.0.0'} - - '@smithy/service-error-classification@4.3.0': - resolution: {integrity: sha512-9jKsBYQRPR0xBLgc2415RsA5PIcP2sis4oBdN9s0D13cg1B1284mNTjx9Yc+BEERXzuPm5ObktI96OxsKh8E9A==} - engines: {node: '>=18.0.0'} - - '@smithy/shared-ini-file-loader@4.4.9': - resolution: {integrity: sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==} - engines: {node: '>=18.0.0'} - - '@smithy/signature-v4@5.3.14': - resolution: {integrity: sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==} - engines: {node: '>=18.0.0'} - - '@smithy/smithy-client@4.12.13': - resolution: {integrity: sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==} + '@smithy/signature-v4@5.6.3': + resolution: {integrity: sha512-8qVKKzqh7naF27ePmx0SkUfnGP/wBI9dyaeAmhHvopnbIlItUAmB/e6PkPCU3rRb2v9BY8D4EZXSoydSibatvw==} engines: {node: '>=18.0.0'} '@smithy/types@4.14.1': resolution: {integrity: sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==} engines: {node: '>=18.0.0'} - '@smithy/url-parser@4.2.14': - resolution: {integrity: sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-base64@4.3.2': - resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-body-length-browser@4.2.2': - resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-body-length-node@4.2.3': - resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + '@smithy/types@4.16.0': + resolution: {integrity: sha512-aVUabzlBBmY0PfvVgLKQSOGFIL5/7R54JE3uD9a5Ay/jSED61SkuAcCYENNXJzYUvJ1NPrWO0P+rAXHCkbBUKw==} engines: {node: '>=18.0.0'} '@smithy/util-buffer-from@2.2.0': resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} - '@smithy/util-buffer-from@4.2.2': - resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} - engines: {node: '>=18.0.0'} - - '@smithy/util-config-provider@4.2.2': - resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-browser@4.3.49': - resolution: {integrity: sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-node@4.2.54': - resolution: {integrity: sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-endpoints@3.4.2': - resolution: {integrity: sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-hex-encoding@4.2.2': - resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-middleware@4.2.14': - resolution: {integrity: sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-retry@4.3.4': - resolution: {integrity: sha512-FY1UQQ1VFmMwiYp1GVS4MeaGD5O0blLNYK0xCRHU+mJgeoH/hSY8Ld8sJWKQ6uznkh14HveRGQJncgPyNl9J+A==} - engines: {node: '>=18.0.0'} - - '@smithy/util-stream@4.5.25': - resolution: {integrity: sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==} - engines: {node: '>=18.0.0'} - - '@smithy/util-uri-escape@4.2.2': - resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} - engines: {node: '>=18.0.0'} - '@smithy/util-utf8@2.3.0': resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} - '@smithy/util-utf8@4.2.2': - resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} - engines: {node: '>=18.0.0'} - - '@smithy/uuid@1.1.2': - resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} - engines: {node: '>=18.0.0'} - '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -10506,9 +10321,6 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - '@tootallnate/quickjs-emscripten@0.23.0': - resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@trpc/client@11.7.2': resolution: {integrity: sha512-OQxqUMfpDvjcszo9dbnqWQXnW2L5IbrKSz2H7l8s+mVM3EvYw7ztQ/gjFIN3iy0NcamiQfd4eE6qjcb9Lm+63A==} peerDependencies: @@ -11830,10 +11642,6 @@ packages: resolution: {integrity: sha512-MdJqjpodkS5J149zN0Po+HPshkTdUyrvF7CKTafUgv69vBSPtncrj+3IiUgqdd7ElIEkbeXCsEouBUwLrw9Ilg==} engines: {node: '>=16.14.0'} - ast-types@0.13.4: - resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} - engines: {node: '>=4'} - ast-types@0.16.1: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} @@ -11991,8 +11799,8 @@ packages: expo: optional: true - babel-preset-expo@54.0.11: - resolution: {integrity: sha512-dEpeFDtYEFzmWtWVwvt7sUCZH0fxXPfbJlgXd7XNZSQDa/Ki/hTOj9exMTzqR2oyPHDNcE9VxYCJ4oS6xw4Pjg==} + babel-preset-expo@54.0.12: + resolution: {integrity: sha512-6xeSkdaixmQhWSYL7tfLu0pOS0BY+8ftwmdNSHtpEFSizrXYZkCjk/B6Dxr+6nwNRihixMcS0aBlWS1wlDl3pw==} peerDependencies: '@babel/runtime': ^7.20.0 expo: '*' @@ -12032,10 +11840,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - basic-ftp@5.3.0: - resolution: {integrity: sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==} - engines: {node: '>=10.0.0'} - bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} @@ -13078,10 +12882,6 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} - data-uri-to-buffer@6.0.2: - resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} - engines: {node: '>= 14'} - data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -13272,10 +13072,6 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - degenerator@5.0.1: - resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} - engines: {node: '>= 14'} - del@6.1.1: resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} engines: {node: '>=10'} @@ -14047,11 +13843,6 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - eslint-config-prettier@10.1.8: resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true @@ -14538,8 +14329,8 @@ packages: react-native-webview: optional: true - expo@54.0.35: - resolution: {integrity: sha512-E+tXpQwjGm5fK/uwa55p0Xx/kuo5dXDKfVJ95IargTNa5KiFt26lSTXXa9KnHbI4EDLwFD38/xTKZvzPTlGTdg==} + expo@54.0.36: + resolution: {integrity: sha512-HMHp1H+actmnX85NJE6lILKzSJV6pTDNkwghq9EMOP3zTynjvBYVqJGSLlm6sEVzJCC5Z2ZiKgLvtsHrqlY0dg==} hasBin: true peerDependencies: '@expo/dom-webview': '*' @@ -14655,13 +14446,6 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fast-xml-builder@1.1.5: - resolution: {integrity: sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==} - - fast-xml-parser@5.7.1: - resolution: {integrity: sha512-8Cc3f8GUGUULg34pBch/KGyPLglS+OFs05deyOlY7fL2MTagYPKrVQNmR1fLF/yJ9PH5ZSTd3YDF6pnmeZU+zA==} - hasBin: true - fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -14990,10 +14774,6 @@ packages: get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} - get-uri@6.0.5: - resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} - engines: {node: '>= 14'} - getenv@2.0.0: resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} engines: {node: '>=6'} @@ -16540,10 +16320,6 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - lucide-react@0.544.0: resolution: {integrity: sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==} peerDependencies: @@ -17250,10 +17026,6 @@ packages: nested-error-stacks@2.0.1: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} - netmask@2.1.1: - resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} - engines: {node: '>= 0.4.0'} - next@14.2.17: resolution: {integrity: sha512-hNo/Zy701DDO3nzKkPmsLRlDfNCtb1OJxFUvjGEl04u7SFa3zwC6hqsOUzMajcaEOEV8ey1GjvByvrg0Qr5AiQ==} engines: {node: '>=18.17.0'} @@ -17707,14 +17479,6 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - pac-proxy-agent@7.2.0: - resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} - engines: {node: '>= 14'} - - pac-resolver@7.0.1: - resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} - engines: {node: '>= 14'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -17808,10 +17572,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-expression-matcher@1.5.0: - resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} - engines: {node: '>=14.0.0'} - path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -18424,10 +18184,6 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-agent@6.5.0: - resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} - engines: {node: '>= 14'} - proxy-compare@3.0.1: resolution: {integrity: sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==} @@ -19625,14 +19381,6 @@ packages: snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - socks-proxy-agent@8.0.5: - resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} - engines: {node: '>= 14'} - - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - solid-js@1.9.7: resolution: {integrity: sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==} @@ -20042,9 +19790,6 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - strnum@2.2.3: - resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} - strtok3@6.3.0: resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} engines: {node: '>=10'} @@ -20246,11 +19991,6 @@ packages: resolution: {integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==} engines: {node: '>=18'} - terser@5.46.2: - resolution: {integrity: sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==} - engines: {node: '>=10'} - hasBin: true - terser@5.48.0: resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} engines: {node: '>=10'} @@ -20607,8 +20347,8 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typebox@1.1.34: - resolution: {integrity: sha512-V0fM5W5DTXlEMDxqtX1dQ25HR1RQ11DPUVrIup4sJi1yQtIyI30SHfxBy/HjXKL1CtUqc5or2igA/wa/v4hMKQ==} + typebox@1.1.38: + resolution: {integrity: sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==} typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} @@ -22101,12 +21841,6 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.2.4 - '@anthropic-ai/sdk@0.73.0(zod@4.3.6)': - dependencies: - json-schema-to-ts: 3.1.1 - optionalDependencies: - zod: 4.3.6 - '@anthropic-ai/sdk@0.78.0(zod@4.3.6)': dependencies: json-schema-to-ts: 3.1.1 @@ -22119,13 +21853,13 @@ snapshots: optionalDependencies: zod: 4.4.3 - '@anthropic-ai/sdk@0.90.0(zod@4.3.6)': + '@anthropic-ai/sdk@0.91.1(zod@4.3.6)': dependencies: json-schema-to-ts: 3.1.1 optionalDependencies: zod: 4.3.6 - '@anthropic-ai/sdk@0.90.0(zod@4.4.3)': + '@anthropic-ai/sdk@0.91.1(zod@4.4.3)': dependencies: json-schema-to-ts: 3.1.1 optionalDependencies: @@ -22190,12 +21924,6 @@ snapshots: '@asamuzakjp/nwsapi@2.3.9': {} - '@aws-crypto/crc32@5.2.0': - dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.8 - tslib: 2.8.1 - '@aws-crypto/sha256-browser@5.2.0': dependencies: '@aws-crypto/sha256-js': 5.2.0 @@ -22222,393 +21950,198 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-bedrock-runtime@3.1037.0': + '@aws-sdk/client-bedrock-runtime@3.1048.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.5 - '@aws-sdk/credential-provider-node': 3.972.36 - '@aws-sdk/eventstream-handler-node': 3.972.14 - '@aws-sdk/middleware-eventstream': 3.972.10 - '@aws-sdk/middleware-host-header': 3.972.10 - '@aws-sdk/middleware-logger': 3.972.10 - '@aws-sdk/middleware-recursion-detection': 3.972.11 - '@aws-sdk/middleware-user-agent': 3.972.35 - '@aws-sdk/middleware-websocket': 3.972.16 - '@aws-sdk/region-config-resolver': 3.972.13 - '@aws-sdk/token-providers': 3.1037.0 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-endpoints': 3.996.8 - '@aws-sdk/util-user-agent-browser': 3.972.10 - '@aws-sdk/util-user-agent-node': 3.973.21 - '@smithy/config-resolver': 4.4.17 - '@smithy/core': 3.23.17 - '@smithy/eventstream-serde-browser': 4.2.14 - '@smithy/eventstream-serde-config-resolver': 4.3.14 - '@smithy/eventstream-serde-node': 4.2.14 - '@smithy/fetch-http-handler': 5.3.17 - '@smithy/hash-node': 4.2.14 - '@smithy/invalid-dependency': 4.2.14 - '@smithy/middleware-content-length': 4.2.14 - '@smithy/middleware-endpoint': 4.4.32 - '@smithy/middleware-retry': 4.5.5 - '@smithy/middleware-serde': 4.2.20 - '@smithy/middleware-stack': 4.2.14 - '@smithy/node-config-provider': 4.3.14 - '@smithy/node-http-handler': 4.6.1 - '@smithy/protocol-http': 5.3.14 - '@smithy/smithy-client': 4.12.13 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 - '@smithy/util-base64': 4.3.2 - '@smithy/util-body-length-browser': 4.2.2 - '@smithy/util-body-length-node': 4.2.3 - '@smithy/util-defaults-mode-browser': 4.3.49 - '@smithy/util-defaults-mode-node': 4.2.54 - '@smithy/util-endpoints': 3.4.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.4 - '@smithy/util-stream': 4.5.25 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/core@3.974.5': - dependencies: - '@aws-sdk/types': 3.973.8 - '@aws-sdk/xml-builder': 3.972.19 - '@smithy/core': 3.23.17 - '@smithy/node-config-provider': 4.3.14 - '@smithy/property-provider': 4.2.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/signature-v4': 5.3.14 - '@smithy/smithy-client': 4.12.13 - '@smithy/types': 4.14.1 - '@smithy/util-base64': 4.3.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.4 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-env@3.972.31': - dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-http@3.972.33': - dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/types': 3.973.8 - '@smithy/fetch-http-handler': 5.3.17 - '@smithy/node-http-handler': 4.6.1 - '@smithy/property-provider': 4.2.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/smithy-client': 4.12.13 - '@smithy/types': 4.14.1 - '@smithy/util-stream': 4.5.25 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-ini@3.972.35': - dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/credential-provider-env': 3.972.31 - '@aws-sdk/credential-provider-http': 3.972.33 - '@aws-sdk/credential-provider-login': 3.972.35 - '@aws-sdk/credential-provider-process': 3.972.31 - '@aws-sdk/credential-provider-sso': 3.972.35 - '@aws-sdk/credential-provider-web-identity': 3.972.35 - '@aws-sdk/nested-clients': 3.997.3 - '@aws-sdk/types': 3.973.8 - '@smithy/credential-provider-imds': 4.2.14 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-login@3.972.35': - dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/nested-clients': 3.997.3 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/credential-provider-node': 3.972.66 + '@aws-sdk/eventstream-handler-node': 3.972.26 + '@aws-sdk/middleware-eventstream': 3.972.22 + '@aws-sdk/middleware-websocket': 3.972.39 + '@aws-sdk/token-providers': 3.1048.0 '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/core': 3.29.2 + '@smithy/fetch-http-handler': 5.6.4 + '@smithy/node-http-handler': 4.7.3 '@smithy/types': 4.14.1 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-node@3.972.36': + '@aws-sdk/core@3.975.1': dependencies: - '@aws-sdk/credential-provider-env': 3.972.31 - '@aws-sdk/credential-provider-http': 3.972.33 - '@aws-sdk/credential-provider-ini': 3.972.35 - '@aws-sdk/credential-provider-process': 3.972.31 - '@aws-sdk/credential-provider-sso': 3.972.35 - '@aws-sdk/credential-provider-web-identity': 3.972.35 - '@aws-sdk/types': 3.973.8 - '@smithy/credential-provider-imds': 4.2.14 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 + '@aws-sdk/types': 3.974.0 + '@aws-sdk/xml-builder': 3.972.34 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.29.2 + '@smithy/signature-v4': 5.6.3 + '@smithy/types': 4.16.0 + bowser: 2.14.1 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-process@3.972.31': + '@aws-sdk/credential-provider-env@3.972.57': dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.972.35': + '@aws-sdk/credential-provider-http@3.972.59': dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/nested-clients': 3.997.3 - '@aws-sdk/token-providers': 3.1036.0 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/fetch-http-handler': 5.6.4 + '@smithy/node-http-handler': 4.9.4 + '@smithy/types': 4.16.0 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-web-identity@3.972.35': - dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/nested-clients': 3.997.3 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 + '@aws-sdk/credential-provider-ini@3.973.1': + dependencies: + '@aws-sdk/core': 3.975.1 + '@aws-sdk/credential-provider-env': 3.972.57 + '@aws-sdk/credential-provider-http': 3.972.59 + '@aws-sdk/credential-provider-login': 3.972.63 + '@aws-sdk/credential-provider-process': 3.972.57 + '@aws-sdk/credential-provider-sso': 3.973.1 + '@aws-sdk/credential-provider-web-identity': 3.972.63 + '@aws-sdk/nested-clients': 3.997.31 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/credential-provider-imds': 4.4.7 + '@smithy/types': 4.16.0 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/eventstream-handler-node@3.972.14': + '@aws-sdk/credential-provider-login@3.972.63': dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/eventstream-codec': 4.2.14 - '@smithy/types': 4.14.1 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/nested-clients': 3.997.31 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws-sdk/middleware-eventstream@3.972.10': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 + '@aws-sdk/credential-provider-node@3.972.66': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.57 + '@aws-sdk/credential-provider-http': 3.972.59 + '@aws-sdk/credential-provider-ini': 3.973.1 + '@aws-sdk/credential-provider-process': 3.972.57 + '@aws-sdk/credential-provider-sso': 3.973.1 + '@aws-sdk/credential-provider-web-identity': 3.972.63 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/credential-provider-imds': 4.4.7 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws-sdk/middleware-host-header@3.972.10': + '@aws-sdk/credential-provider-process@3.972.57': dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.972.10': + '@aws-sdk/credential-provider-sso@3.973.1': dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/types': 4.14.1 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/nested-clients': 3.997.31 + '@aws-sdk/token-providers': 3.1083.0 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws-sdk/middleware-recursion-detection@3.972.11': + '@aws-sdk/credential-provider-web-identity@3.972.63': dependencies: - '@aws-sdk/types': 3.973.8 - '@aws/lambda-invoke-store': 0.2.4 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/nested-clients': 3.997.31 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.34': + '@aws-sdk/eventstream-handler-node@3.972.26': dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-arn-parser': 3.972.3 - '@smithy/core': 3.23.17 - '@smithy/node-config-provider': 4.3.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/signature-v4': 5.3.14 - '@smithy/smithy-client': 4.12.13 - '@smithy/types': 4.14.1 - '@smithy/util-config-provider': 4.2.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-stream': 4.5.25 - '@smithy/util-utf8': 4.2.2 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.972.35': + '@aws-sdk/middleware-eventstream@3.972.22': dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-endpoints': 3.996.8 - '@smithy/core': 3.23.17 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-retry': 4.3.4 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws-sdk/middleware-websocket@3.972.16': + '@aws-sdk/middleware-websocket@3.972.39': dependencies: - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-format-url': 3.972.10 - '@smithy/eventstream-codec': 4.2.14 - '@smithy/eventstream-serde-browser': 4.2.14 - '@smithy/fetch-http-handler': 5.3.17 - '@smithy/protocol-http': 5.3.14 - '@smithy/signature-v4': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-base64': 4.3.2 - '@smithy/util-hex-encoding': 4.2.2 - '@smithy/util-utf8': 4.2.2 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/fetch-http-handler': 5.6.4 + '@smithy/signature-v4': 5.6.3 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.997.3': + '@aws-sdk/nested-clients@3.997.31': dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.5 - '@aws-sdk/middleware-host-header': 3.972.10 - '@aws-sdk/middleware-logger': 3.972.10 - '@aws-sdk/middleware-recursion-detection': 3.972.11 - '@aws-sdk/middleware-user-agent': 3.972.35 - '@aws-sdk/region-config-resolver': 3.972.13 - '@aws-sdk/signature-v4-multi-region': 3.996.22 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-endpoints': 3.996.8 - '@aws-sdk/util-user-agent-browser': 3.972.10 - '@aws-sdk/util-user-agent-node': 3.973.21 - '@smithy/config-resolver': 4.4.17 - '@smithy/core': 3.23.17 - '@smithy/fetch-http-handler': 5.3.17 - '@smithy/hash-node': 4.2.14 - '@smithy/invalid-dependency': 4.2.14 - '@smithy/middleware-content-length': 4.2.14 - '@smithy/middleware-endpoint': 4.4.32 - '@smithy/middleware-retry': 4.5.5 - '@smithy/middleware-serde': 4.2.20 - '@smithy/middleware-stack': 4.2.14 - '@smithy/node-config-provider': 4.3.14 - '@smithy/node-http-handler': 4.6.1 - '@smithy/protocol-http': 5.3.14 - '@smithy/smithy-client': 4.12.13 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 - '@smithy/util-base64': 4.3.2 - '@smithy/util-body-length-browser': 4.2.2 - '@smithy/util-body-length-node': 4.2.3 - '@smithy/util-defaults-mode-browser': 4.3.49 - '@smithy/util-defaults-mode-node': 4.2.54 - '@smithy/util-endpoints': 3.4.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.4 - '@smithy/util-utf8': 4.2.2 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/signature-v4-multi-region': 3.996.39 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/fetch-http-handler': 5.6.4 + '@smithy/node-http-handler': 4.9.4 + '@smithy/types': 4.16.0 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/region-config-resolver@3.972.13': + '@aws-sdk/signature-v4-multi-region@3.996.39': dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/config-resolver': 4.4.17 - '@smithy/node-config-provider': 4.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/signature-v4-multi-region@3.996.22': - dependencies: - '@aws-sdk/middleware-sdk-s3': 3.972.34 - '@aws-sdk/types': 3.973.8 - '@smithy/protocol-http': 5.3.14 - '@smithy/signature-v4': 5.3.14 - '@smithy/types': 4.14.1 + '@aws-sdk/types': 3.974.0 + '@smithy/signature-v4': 5.6.3 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1036.0': + '@aws-sdk/token-providers@3.1048.0': dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/nested-clients': 3.997.3 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/nested-clients': 3.997.31 '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/core': 3.29.2 '@smithy/types': 4.14.1 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/token-providers@3.1037.0': + '@aws-sdk/token-providers@3.1083.0': dependencies: - '@aws-sdk/core': 3.974.5 - '@aws-sdk/nested-clients': 3.997.3 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 + '@aws-sdk/core': 3.975.1 + '@aws-sdk/nested-clients': 3.997.31 + '@aws-sdk/types': 3.974.0 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt '@aws-sdk/types@3.973.8': dependencies: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/util-arn-parser@3.972.3': + '@aws-sdk/types@3.974.0': dependencies: - tslib: 2.8.1 - - '@aws-sdk/util-endpoints@3.996.8': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 - '@smithy/util-endpoints': 3.4.2 - tslib: 2.8.1 - - '@aws-sdk/util-format-url@3.972.10': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/querystring-builder': 4.2.14 - '@smithy/types': 4.14.1 + '@smithy/types': 4.16.0 tslib: 2.8.1 '@aws-sdk/util-locate-window@3.965.5': dependencies: tslib: 2.8.1 - '@aws-sdk/util-user-agent-browser@3.972.10': + '@aws-sdk/xml-builder@3.972.34': dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/types': 4.14.1 - bowser: 2.14.1 - tslib: 2.8.1 - - '@aws-sdk/util-user-agent-node@3.973.21': - dependencies: - '@aws-sdk/middleware-user-agent': 3.972.35 - '@aws-sdk/types': 3.973.8 - '@smithy/node-config-provider': 4.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-config-provider': 4.2.2 - tslib: 2.8.1 - - '@aws-sdk/xml-builder@3.972.19': - dependencies: - '@smithy/types': 4.14.1 - fast-xml-parser: 5.7.1 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@aws/lambda-invoke-store@0.2.4': {} + '@aws/lambda-invoke-store@0.3.0': {} '@babel/code-frame@7.10.4': dependencies: @@ -22820,7 +22353,7 @@ snapshots: '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@babel/helper-annotate-as-pure@7.29.7': dependencies: @@ -22874,12 +22407,12 @@ snapshots: '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-annotate-as-pure': 7.29.7 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -22910,6 +22443,32 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.28.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -22926,13 +22485,13 @@ snapshots: '@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-annotate-as-pure': 7.29.7 regexpu-core: 6.2.0 semver: 6.3.1 - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 @@ -22955,17 +22514,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - debug: 4.4.3 - lodash.debounce: 4.0.8 - resolve: 1.22.12 - transitivePeerDependencies: - - supports-color - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -22990,7 +22538,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.27.1': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -23051,7 +22599,7 @@ snapshots: '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-module-imports': 7.28.6 + '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.29.0 transitivePeerDependencies: @@ -23060,7 +22608,7 @@ snapshots: '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.28.6 + '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.29.0 transitivePeerDependencies: @@ -23084,6 +22632,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -23116,15 +22682,15 @@ snapshots: '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-annotate-as-pure': 7.29.7 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.6 '@babel/traverse': 7.29.0 @@ -23154,7 +22720,7 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -23176,6 +22742,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -23227,7 +22811,7 @@ snapshots: '@babel/helper-wrap-function@7.25.9': dependencies: '@babel/template': 7.29.7 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -23235,7 +22819,7 @@ snapshots: '@babel/helper-wrap-function@7.28.6': dependencies: '@babel/template': 7.29.7 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -23356,12 +22940,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.29.0)': + '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.29.7) transitivePeerDependencies: - supports-color @@ -23370,9 +22954,9 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.26.0)': @@ -23390,51 +22974,51 @@ snapshots: '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.28.0)': @@ -23442,9 +23026,9 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-export-default-from@7.27.1(@babel/core@7.28.0)': @@ -23452,9 +23036,9 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-export-default-from@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-syntax-export-default-from@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.0)': @@ -23462,9 +23046,9 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.29.7)': @@ -23475,12 +23059,12 @@ snapshots: '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': dependencies: @@ -23490,22 +23074,22 @@ snapshots: '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)': dependencies: @@ -23522,85 +23106,90 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)': dependencies: @@ -23617,6 +23206,11 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -23626,11 +23220,11 @@ snapshots: '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.7)': @@ -23641,17 +23235,17 @@ snapshots: '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.28.0) - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.7) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color @@ -23668,18 +23262,18 @@ snapshots: '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.28.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.7) transitivePeerDependencies: - supports-color @@ -23700,11 +23294,11 @@ snapshots: '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7)': @@ -23715,15 +23309,15 @@ snapshots: '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -23736,10 +23330,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -23755,23 +23349,23 @@ snapshots: '@babel/plugin-transform-classes@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0) - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.7) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color @@ -23791,12 +23385,12 @@ snapshots: '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/template': 7.29.7 - '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/template': 7.29.7 @@ -23809,21 +23403,21 @@ snapshots: '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.0)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': + '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -23875,9 +23469,9 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7)': @@ -23891,23 +23485,23 @@ snapshots: '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.29.7) '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: @@ -23924,15 +23518,15 @@ snapshots: '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 @@ -23956,11 +23550,11 @@ snapshots: '@babel/plugin-transform-literals@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-literals@7.29.7(@babel/core@7.29.7)': @@ -23971,11 +23565,11 @@ snapshots: '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7)': @@ -24020,6 +23614,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -24050,12 +23660,12 @@ snapshots: dependencies: '@babel/core': 7.28.0 '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': @@ -24072,11 +23682,11 @@ snapshots: '@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7)': @@ -24087,11 +23697,11 @@ snapshots: '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-numeric-separator@7.29.7(@babel/core@7.29.7)': @@ -24104,27 +23714,27 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.0) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.28.0) - '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.28.0)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.29.7 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.7) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/traverse': 7.29.0 + '@babel/core': 7.28.0 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.28.0) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.28.0) + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -24150,11 +23760,11 @@ snapshots: '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7)': @@ -24165,14 +23775,14 @@ snapshots: '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: @@ -24191,15 +23801,15 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.0)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': + '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7)': dependencies: @@ -24214,19 +23824,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.28.0)': + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.0) + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.28.0 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color @@ -24247,21 +23857,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.28.0)': + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.0) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.28.0 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color @@ -24284,9 +23894,9 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.0)': @@ -24296,10 +23906,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.7) transitivePeerDependencies: - supports-color @@ -24318,6 +23928,11 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 @@ -24333,6 +23948,11 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 @@ -24340,18 +23960,18 @@ snapshots: '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0) - '@babel/types': 7.29.0 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.7) + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -24361,21 +23981,21 @@ snapshots: '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 regenerator-transform: 0.15.2 - '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-regenerator@7.29.7(@babel/core@7.29.7)': @@ -24406,14 +24026,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-runtime@7.28.0(@babel/core@7.29.0)': + '@babel/plugin-transform-runtime@7.28.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -24421,11 +24041,11 @@ snapshots: '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.7)': @@ -24436,14 +24056,14 @@ snapshots: '@babel/plugin-transform-spread@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: @@ -24460,11 +24080,11 @@ snapshots: '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-sticky-regex@7.29.7(@babel/core@7.29.7)': @@ -24504,6 +24124,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -24519,12 +24150,12 @@ snapshots: dependencies: '@babel/core': 7.28.0 '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7)': @@ -24635,15 +24266,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-react@7.27.1(@babel/core@7.29.0)': + '@babel/preset-react@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.7) transitivePeerDependencies: - supports-color @@ -24669,6 +24300,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/preset-typescript@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + '@babel/runtime@7.26.0': dependencies: regenerator-runtime: 0.14.1 @@ -24739,8 +24381,8 @@ snapshots: '@babel/traverse@7.28.5': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 '@babel/helper-globals': 7.28.0 '@babel/parser': 7.29.2 '@babel/template': 7.28.6 @@ -24815,7 +24457,7 @@ snapshots: '@base-ui/utils@0.2.8(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@floating-ui/utils': 0.2.11 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -25448,6 +25090,11 @@ snapshots: '@microsoft/fetch-event-source': 2.0.1(patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188) fastq: 1.20.1 + '@durable-streams/client@https://pkg.pr.new/@durable-streams/client@293': + dependencies: + '@microsoft/fetch-event-source': 2.0.1(patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188) + fastq: 1.20.1 + '@durable-streams/server-conformance-tests@0.3.5(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.0(@noble/hashes@2.0.1))(vite@7.3.2(@types/node@25.9.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0))': dependencies: '@durable-streams/client': 0.2.6 @@ -25500,6 +25147,111 @@ snapshots: optionalDependencies: '@tanstack/db': 0.6.7(typescript@5.9.3) + '@earendil-works/pi-agent-core@0.80.5': + dependencies: + '@earendil-works/pi-ai': 0.80.5 + ignore: 7.0.5 + typebox: 1.1.38 + yaml: 2.9.0 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-agent-core@0.80.5(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)': + dependencies: + '@earendil-works/pi-ai': 0.80.5(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) + ignore: 7.0.5 + typebox: 1.1.38 + yaml: 2.9.0 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-agent-core@0.80.5(zod@4.4.3)': + dependencies: + '@earendil-works/pi-ai': 0.80.5(zod@4.4.3) + ignore: 7.0.5 + typebox: 1.1.38 + yaml: 2.9.0 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-ai@0.80.5': + dependencies: + '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) + '@aws-sdk/client-bedrock-runtime': 3.1048.0 + '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)) + '@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.0 + '@smithy/node-http-handler': 4.7.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + openai: 6.26.0(ws@8.20.0)(zod@4.3.6) + partial-json: 0.1.7 + typebox: 1.1.38 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-ai@0.80.5(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)': + dependencies: + '@anthropic-ai/sdk': 0.91.1(zod@4.3.6) + '@aws-sdk/client-bedrock-runtime': 3.1048.0 + '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)) + '@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.0 + '@smithy/node-http-handler': 4.7.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + openai: 6.26.0(ws@8.20.0)(zod@4.3.6) + partial-json: 0.1.7 + typebox: 1.1.38 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-ai@0.80.5(zod@4.4.3)': + dependencies: + '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) + '@aws-sdk/client-bedrock-runtime': 3.1048.0 + '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)) + '@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.0 + '@smithy/node-http-handler': 4.7.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + openai: 6.26.0(zod@4.4.3) + partial-json: 0.1.7 + typebox: 1.1.38 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + '@ecies/ciphers@0.2.5(@noble/ciphers@1.3.0)': dependencies: '@noble/ciphers': 1.3.0 @@ -26475,7 +26227,7 @@ snapshots: '@expo/cli@0.24.20': dependencies: '@0no-co/graphql.web': 1.1.2 - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@expo/code-signing-certificates': 0.0.5 '@expo/config': 11.0.13 '@expo/config-plugins': 10.1.2 @@ -26534,30 +26286,30 @@ snapshots: terminal-link: 2.1.1 undici: 6.25.0 wrap-ansi: 7.0.0 - ws: 8.18.0 + ws: 8.20.0 transitivePeerDependencies: - bufferutil - graphql - supports-color - utf-8-validate - '@expo/cli@54.0.25(expo-router@6.0.24)(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(typescript@5.9.3)': + '@expo/cli@54.0.26(expo-router@6.0.24)(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(typescript@5.9.3)': dependencies: '@0no-co/graphql.web': 1.1.2 '@expo/code-signing-certificates': 0.0.6 - '@expo/config': 12.0.13 - '@expo/config-plugins': 54.0.4 + '@expo/config': 12.0.14 + '@expo/config-plugins': 54.0.5 '@expo/devcert': 1.2.1 - '@expo/env': 2.0.11 + '@expo/env': 2.0.12 '@expo/image-utils': 0.8.14(typescript@5.9.3) '@expo/json-file': 10.2.0 '@expo/metro': 54.2.0 - '@expo/metro-config': 54.0.16(expo@54.0.35) + '@expo/metro-config': 54.0.17(expo@54.0.36) '@expo/osascript': 2.4.4 '@expo/package-manager': 1.10.5 '@expo/plist': 0.4.9 - '@expo/prebuild-config': 54.0.8(expo@54.0.35)(typescript@5.9.3) - '@expo/schema-utils': 0.1.8 + '@expo/prebuild-config': 54.0.9(expo@54.0.36)(typescript@5.9.3) + '@expo/schema-utils': 0.1.9 '@expo/spawn-async': 1.7.2 '@expo/ws-tunnel': 1.0.6 '@expo/xcpretty': 4.3.2 @@ -26575,7 +26327,7 @@ snapshots: connect: 3.7.0 debug: 4.4.3 env-editor: 0.4.2 - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) expo-server: 1.0.7 freeport-async: 2.0.0 getenv: 2.0.0 @@ -26608,8 +26360,8 @@ snapshots: wrap-ansi: 7.0.0 ws: 8.20.0 optionalDependencies: - expo-router: 6.0.24(@expo/metro-runtime@6.1.2)(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.12)(expo@54.0.35)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + expo-router: 6.0.24(@expo/metro-runtime@6.1.2)(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.12)(expo@54.0.36)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) transitivePeerDependencies: - bufferutil - graphql @@ -26619,7 +26371,7 @@ snapshots: '@expo/code-signing-certificates@0.0.5': dependencies: - node-forge: 1.3.1 + node-forge: 1.4.0 nullthrows: 1.1.1 '@expo/code-signing-certificates@0.0.6': @@ -26664,6 +26416,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@expo/config-plugins@54.0.5': + dependencies: + '@expo/config-types': 54.0.10 + '@expo/json-file': 10.0.16 + '@expo/plist': 0.4.9 + '@expo/sdk-runtime-versions': 1.0.0 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + glob: 13.0.6 + resolve-from: 5.0.0 + semver: 7.7.4 + slash: 3.0.0 + slugify: 1.6.6 + xcode: 3.0.1 + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + '@expo/config-types@53.0.5': {} '@expo/config-types@54.0.10': {} @@ -26704,6 +26475,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@expo/config@12.0.14': + dependencies: + '@babel/code-frame': 7.10.4 + '@expo/config-plugins': 54.0.5 + '@expo/config-types': 54.0.10 + '@expo/json-file': 10.2.0 + deepmerge: 4.3.1 + getenv: 2.0.0 + glob: 13.0.6 + require-from-string: 2.0.2 + resolve-from: 5.0.0 + resolve-workspace-root: 2.0.0 + semver: 7.7.4 + slugify: 1.6.6 + sucrase: 3.35.1 + transitivePeerDependencies: + - supports-color + '@expo/devcert@1.2.0': dependencies: '@expo/sudo-prompt': 9.3.2 @@ -26719,12 +26508,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/devtools@0.1.8(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@expo/devtools@0.1.8(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: chalk: 4.1.2 optionalDependencies: react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) '@expo/env@1.0.7': dependencies: @@ -26746,6 +26535,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@expo/env@2.0.12': + dependencies: + chalk: 4.1.2 + debug: 4.4.3 + dotenv: 16.4.5 + dotenv-expand: 11.0.7 + getenv: 2.0.0 + transitivePeerDependencies: + - supports-color + '@expo/fingerprint@0.13.4': dependencies: '@expo/spawn-async': 1.7.2 @@ -26848,17 +26647,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/metro-config@54.0.16(expo@54.0.35)': + '@expo/metro-config@54.0.17(expo@54.0.36)': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/core': 7.29.0 - '@babel/generator': 7.29.1 - '@expo/config': 12.0.13 - '@expo/env': 2.0.11 + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@expo/config': 12.0.14 + '@expo/env': 2.0.12 '@expo/json-file': 10.0.16 '@expo/metro': 54.2.0 '@expo/spawn-async': 1.7.2 - browserslist: 4.28.2 + browserslist: 4.28.4 chalk: 4.1.2 debug: 4.4.3 dotenv: 16.4.5 @@ -26872,7 +26671,7 @@ snapshots: postcss: 8.4.47 resolve-from: 5.0.0 optionalDependencies: - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) transitivePeerDependencies: - bufferutil - supports-color @@ -26882,13 +26681,13 @@ snapshots: dependencies: react-native: 0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0) - '@expo/metro-runtime@6.1.2(expo@54.0.35)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@expo/metro-runtime@6.1.2(expo@54.0.36)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: anser: 1.4.10 - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) pretty-format: 29.7.0 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) stacktrace-parser: 0.1.11 whatwg-fetch: 3.6.20 optionalDependencies: @@ -26960,7 +26759,7 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 - '@expo/prebuild-config@54.0.8(expo@54.0.35)(typescript@5.9.3)': + '@expo/prebuild-config@54.0.8(expo@54.0.36)(typescript@5.9.3)': dependencies: '@expo/config': 12.0.13 '@expo/config-plugins': 54.0.4 @@ -26969,7 +26768,24 @@ snapshots: '@expo/json-file': 10.2.0 '@react-native/normalize-colors': 0.81.5 debug: 4.4.3 - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + resolve-from: 5.0.0 + semver: 7.7.4 + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/prebuild-config@54.0.9(expo@54.0.36)(typescript@5.9.3)': + dependencies: + '@expo/config': 12.0.14 + '@expo/config-plugins': 54.0.5 + '@expo/config-types': 54.0.10 + '@expo/image-utils': 0.8.14(typescript@5.9.3) + '@expo/json-file': 10.2.0 + '@react-native/normalize-colors': 0.81.5 + debug: 4.4.3 + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) resolve-from: 5.0.0 semver: 7.7.4 xml2js: 0.6.0 @@ -26995,8 +26811,8 @@ snapshots: '@expo/require-utils@55.0.5(typescript@5.9.3)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.0 - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -27004,6 +26820,8 @@ snapshots: '@expo/schema-utils@0.1.8': {} + '@expo/schema-utils@0.1.9': {} + '@expo/sdk-runtime-versions@1.0.0': {} '@expo/server@0.6.3': @@ -27027,11 +26845,11 @@ snapshots: react: 19.1.0 react-native: 0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0) - '@expo/vector-icons@15.1.1(expo-font@14.0.12(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@expo/vector-icons@15.1.1(expo-font@14.0.12(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: - expo-font: 14.0.12(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-font: 14.0.12(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) '@expo/ws-tunnel@1.0.6': {} @@ -27096,7 +26914,7 @@ snapshots: '@fontsource/alegreya-sans@5.1.1': {} - '@google/genai@1.50.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))': + '@google/genai@1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))': dependencies: google-auth-library: 10.6.2 p-retry: 4.6.2 @@ -27398,14 +27216,14 @@ snapshots: '@jimp/bmp@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 bmp-js: 0.1.0 '@jimp/core@0.16.13': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/utils': 0.16.13 any-base: 1.1.0 buffer: 5.7.1 @@ -27421,14 +27239,14 @@ snapshots: '@jimp/custom@0.16.13': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/core': 0.16.13 transitivePeerDependencies: - debug '@jimp/gif@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 gifwrap: 0.9.4 @@ -27436,39 +27254,39 @@ snapshots: '@jimp/jpeg@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 jpeg-js: 0.4.4 '@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-blur@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-circle@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-color@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 tinycolor2: 1.6.0 '@jimp/plugin-contain@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-scale@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)))': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) @@ -27477,7 +27295,7 @@ snapshots: '@jimp/plugin-cover@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-scale@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)))': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/plugin-crop': 0.16.13(@jimp/custom@0.16.13) '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) @@ -27486,62 +27304,62 @@ snapshots: '@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-displace@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-dither@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-fisheye@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-flip@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-rotate@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)))': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/plugin-rotate': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)) '@jimp/utils': 0.16.13 '@jimp/plugin-gaussian@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-invert@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-mask@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-normalize@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-print@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) '@jimp/utils': 0.16.13 @@ -27551,13 +27369,13 @@ snapshots: '@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 '@jimp/plugin-rotate@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) '@jimp/plugin-crop': 0.16.13(@jimp/custom@0.16.13) @@ -27566,14 +27384,14 @@ snapshots: '@jimp/plugin-scale@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) '@jimp/utils': 0.16.13 '@jimp/plugin-shadow@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blur@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/plugin-blur': 0.16.13(@jimp/custom@0.16.13) '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) @@ -27581,7 +27399,7 @@ snapshots: '@jimp/plugin-threshold@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-color@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/plugin-color': 0.16.13(@jimp/custom@0.16.13) '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) @@ -27589,7 +27407,7 @@ snapshots: '@jimp/plugins@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) '@jimp/plugin-blur': 0.16.13(@jimp/custom@0.16.13) @@ -27618,20 +27436,20 @@ snapshots: '@jimp/png@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/utils': 0.16.13 pngjs: 3.4.0 '@jimp/tiff@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 utif: 2.0.1 '@jimp/types@0.16.13(@jimp/custom@0.16.13)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/bmp': 0.16.13(@jimp/custom@0.16.13) '@jimp/custom': 0.16.13 '@jimp/gif': 0.16.13(@jimp/custom@0.16.13) @@ -27642,7 +27460,7 @@ snapshots: '@jimp/utils@0.16.13': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 regenerator-runtime: 0.13.11 '@jridgewell/gen-mapping@0.3.12': @@ -27753,14 +27571,14 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -27771,202 +27589,20 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} - '@mariozechner/pi-agent-core@0.57.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)': - dependencies: - '@mariozechner/pi-ai': 0.57.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-agent-core@0.70.2': - dependencies: - '@mariozechner/pi-ai': 0.70.2 - typebox: 1.1.34 - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-agent-core@0.70.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)': - dependencies: - '@mariozechner/pi-ai': 0.70.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) - typebox: 1.1.34 - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-agent-core@0.70.2(zod@4.3.6)': - dependencies: - '@mariozechner/pi-ai': 0.70.2(zod@4.3.6) - typebox: 1.1.34 - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-agent-core@0.70.2(zod@4.4.3)': - dependencies: - '@mariozechner/pi-ai': 0.70.2(zod@4.4.3) - typebox: 1.1.34 - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-ai@0.57.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)': - dependencies: - '@anthropic-ai/sdk': 0.73.0(zod@4.3.6) - '@aws-sdk/client-bedrock-runtime': 3.1037.0 - '@google/genai': 1.50.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)) - '@mistralai/mistralai': 1.14.1 - '@sinclair/typebox': 0.34.49 - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - chalk: 5.6.2 - openai: 6.26.0(ws@8.20.0)(zod@4.3.6) - partial-json: 0.1.7 - proxy-agent: 6.5.0 - undici: 7.25.0 - zod-to-json-schema: 3.25.2(zod@4.3.6) - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-ai@0.70.2': - dependencies: - '@anthropic-ai/sdk': 0.90.0(zod@4.4.3) - '@aws-sdk/client-bedrock-runtime': 3.1037.0 - '@google/genai': 1.50.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)) - '@mistralai/mistralai': 2.2.1 - chalk: 5.6.2 - openai: 6.26.0(ws@8.20.0)(zod@4.3.6) - partial-json: 0.1.7 - proxy-agent: 6.5.0 - typebox: 1.1.34 - undici: 7.25.0 - zod-to-json-schema: 3.25.2(zod@3.25.76) - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-ai@0.70.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)': - dependencies: - '@anthropic-ai/sdk': 0.90.0(zod@4.3.6) - '@aws-sdk/client-bedrock-runtime': 3.1037.0 - '@google/genai': 1.50.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)) - '@mistralai/mistralai': 2.2.1 - chalk: 5.6.2 - openai: 6.26.0(ws@8.20.0)(zod@4.3.6) - partial-json: 0.1.7 - proxy-agent: 6.5.0 - typebox: 1.1.34 - undici: 7.25.0 - zod-to-json-schema: 3.25.2(zod@4.3.6) - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-ai@0.70.2(zod@4.3.6)': - dependencies: - '@anthropic-ai/sdk': 0.90.0(zod@4.3.6) - '@aws-sdk/client-bedrock-runtime': 3.1037.0 - '@google/genai': 1.50.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)) - '@mistralai/mistralai': 2.2.1 - chalk: 5.6.2 - openai: 6.26.0(ws@8.20.0)(zod@4.3.6) - partial-json: 0.1.7 - proxy-agent: 6.5.0 - typebox: 1.1.34 - undici: 7.25.0 - zod-to-json-schema: 3.25.2(zod@4.3.6) - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-ai@0.70.2(zod@4.4.3)': - dependencies: - '@anthropic-ai/sdk': 0.90.0(zod@4.4.3) - '@aws-sdk/client-bedrock-runtime': 3.1037.0 - '@google/genai': 1.50.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)) - '@mistralai/mistralai': 2.2.1 - chalk: 5.6.2 - openai: 6.26.0(zod@4.4.3) - partial-json: 0.1.7 - proxy-agent: 6.5.0 - typebox: 1.1.34 - undici: 7.25.0 - zod-to-json-schema: 3.25.2(zod@4.4.3) - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - '@mermaid-js/parser@1.1.0': dependencies: langium: 4.2.2 '@microsoft/fetch-event-source@2.0.1(patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188)': {} - '@mistralai/mistralai@1.14.1': - dependencies: - ws: 8.20.0 - zod: 4.4.3 - zod-to-json-schema: 3.25.2(zod@4.4.3) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@mistralai/mistralai@2.2.1': + '@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)': dependencies: + '@opentelemetry/semantic-conventions': 1.41.1 ws: 8.20.0 zod: 4.4.3 zod-to-json-schema: 3.25.2(zod@4.4.3) + optionalDependencies: + '@opentelemetry/api': 1.9.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -28115,8 +27751,6 @@ snapshots: '@noble/hashes@2.0.1': {} - '@nodable/entities@2.1.0': {} - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -28146,6 +27780,8 @@ snapshots: '@oozcitak/util@10.0.0': {} + '@opentelemetry/api@1.9.0': {} + '@opentelemetry/api@1.9.1': {} '@opentelemetry/semantic-conventions@1.41.1': {} @@ -29988,10 +29624,10 @@ snapshots: prompts: 2.4.2 tinyexec: 1.2.4 - '@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))': + '@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))': dependencies: merge-options: 3.0.4 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) '@react-native/assets-registry@0.80.1': {} @@ -29999,16 +29635,16 @@ snapshots: '@react-native/babel-plugin-codegen@0.79.5(@babel/core@7.28.0)': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 '@react-native/codegen': 0.79.5(@babel/core@7.28.0) transitivePeerDependencies: - '@babel/core' - supports-color - '@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.29.0)': + '@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.29.7)': dependencies: '@babel/traverse': 7.29.0 - '@react-native/codegen': 0.81.5(@babel/core@7.29.0) + '@react-native/codegen': 0.81.5(@babel/core@7.29.7) transitivePeerDependencies: - '@babel/core' - supports-color @@ -30034,16 +29670,16 @@ snapshots: '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.28.0) '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.28.0) '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.28.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.0) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.28.0) '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.28.0) '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.28.0) '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.28.0) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.28.0) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.28.0) '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.28.0) '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.28.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.0) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.0) - '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.28.0) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.28.0) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.28.0) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.28.0) '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.0) '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.0) '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) @@ -30063,52 +29699,52 @@ snapshots: transitivePeerDependencies: - supports-color - '@react-native/babel-preset@0.81.5(@babel/core@7.29.0)': + '@react-native/babel-preset@0.81.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-runtime': 7.28.0(@babel/core@7.29.0) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-runtime': 7.28.0(@babel/core@7.29.7) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.7) '@babel/template': 7.28.6 - '@react-native/babel-plugin-codegen': 0.81.5(@babel/core@7.29.0) + '@react-native/babel-plugin-codegen': 0.81.5(@babel/core@7.29.7) babel-plugin-syntax-hermes-parser: 0.29.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.0) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) react-refresh: 0.14.2 transitivePeerDependencies: - supports-color @@ -30131,9 +29767,9 @@ snapshots: nullthrows: 1.1.1 yargs: 17.7.2 - '@react-native/codegen@0.81.5(@babel/core@7.29.0)': + '@react-native/codegen@0.81.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/parser': 7.29.2 glob: 7.2.3 hermes-parser: 0.29.1 @@ -30255,12 +29891,12 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@react-native/virtualized-lists@0.81.5(@types/react@19.1.17)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-native/virtualized-lists@0.81.5(@types/react@19.1.17)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) optionalDependencies: '@types/react': 19.1.17 @@ -30276,15 +29912,15 @@ snapshots: transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/bottom-tabs@7.4.4(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-navigation/bottom-tabs@7.4.4(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: - '@react-navigation/elements': 2.6.1(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@react-navigation/native': 7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/elements': 2.6.1(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) color: 4.2.3 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) + react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -30309,13 +29945,13 @@ snapshots: use-latest-callback: 0.2.4(react@19.1.0) use-sync-external-store: 1.6.0(react@19.1.0) - '@react-navigation/elements@2.6.1(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-navigation/elements@2.6.1(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: - '@react-navigation/native': 7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) color: 4.2.3 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) + react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) use-latest-callback: 0.2.4(react@19.1.0) use-sync-external-store: 1.6.0(react@19.1.0) @@ -30331,14 +29967,14 @@ snapshots: transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/native-stack@7.3.23(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-navigation/native-stack@7.3.23(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: - '@react-navigation/elements': 2.6.1(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@react-navigation/native': 7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/elements': 2.6.1(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) + react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) warn-once: 0.1.1 transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -30353,14 +29989,14 @@ snapshots: react-native: 0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0) use-latest-callback: 0.2.4(react@19.1.0) - '@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: '@react-navigation/core': 7.12.3(react@19.1.0) escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 nanoid: 3.3.11 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) use-latest-callback: 0.2.4(react@19.1.0) '@react-navigation/routers@7.5.1': @@ -30750,7 +30386,7 @@ snapshots: '@sentry/core@10.12.0': {} - '@sentry/react-native@7.2.0(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@sentry/react-native@7.2.0(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: '@sentry/babel-plugin-component-annotate': 4.3.0 '@sentry/browser': 10.12.0 @@ -30759,9 +30395,9 @@ snapshots: '@sentry/react': 10.12.0(react@19.1.0) '@sentry/types': 10.12.0 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) optionalDependencies: - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) transitivePeerDependencies: - encoding - supports-color @@ -30893,222 +30529,50 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@smithy/config-resolver@4.4.17': - dependencies: - '@smithy/node-config-provider': 4.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-config-provider': 4.2.2 - '@smithy/util-endpoints': 3.4.2 - '@smithy/util-middleware': 4.2.14 - tslib: 2.8.1 - - '@smithy/core@3.23.17': - dependencies: - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 - '@smithy/util-base64': 4.3.2 - '@smithy/util-body-length-browser': 4.2.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-stream': 4.5.25 - '@smithy/util-utf8': 4.2.2 - '@smithy/uuid': 1.1.2 - tslib: 2.8.1 - - '@smithy/credential-provider-imds@4.2.14': + '@smithy/core@3.29.2': dependencies: - '@smithy/node-config-provider': 4.3.14 - '@smithy/property-provider': 4.2.14 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@smithy/eventstream-codec@4.2.14': + '@smithy/credential-provider-imds@4.4.7': dependencies: - '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 4.14.1 - '@smithy/util-hex-encoding': 4.2.2 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@smithy/eventstream-serde-browser@4.2.14': + '@smithy/fetch-http-handler@5.6.4': dependencies: - '@smithy/eventstream-serde-universal': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/eventstream-serde-config-resolver@4.3.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/eventstream-serde-node@4.2.14': - dependencies: - '@smithy/eventstream-serde-universal': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/eventstream-serde-universal@4.2.14': - dependencies: - '@smithy/eventstream-codec': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/fetch-http-handler@5.3.17': - dependencies: - '@smithy/protocol-http': 5.3.14 - '@smithy/querystring-builder': 4.2.14 - '@smithy/types': 4.14.1 - '@smithy/util-base64': 4.3.2 - tslib: 2.8.1 - - '@smithy/hash-node@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - '@smithy/util-buffer-from': 4.2.2 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/invalid-dependency@4.2.14': - dependencies: - '@smithy/types': 4.14.1 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 '@smithy/is-array-buffer@2.2.0': dependencies: tslib: 2.8.1 - '@smithy/is-array-buffer@4.2.2': - dependencies: - tslib: 2.8.1 - - '@smithy/middleware-content-length@4.2.14': - dependencies: - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/middleware-endpoint@4.4.32': - dependencies: - '@smithy/core': 3.23.17 - '@smithy/middleware-serde': 4.2.20 - '@smithy/node-config-provider': 4.3.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 - '@smithy/util-middleware': 4.2.14 - tslib: 2.8.1 - - '@smithy/middleware-retry@4.5.5': + '@smithy/node-http-handler@4.7.3': dependencies: - '@smithy/core': 3.23.17 - '@smithy/node-config-provider': 4.3.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/service-error-classification': 4.3.0 - '@smithy/smithy-client': 4.12.13 - '@smithy/types': 4.14.1 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.4 - '@smithy/uuid': 1.1.2 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@smithy/middleware-serde@4.2.20': + '@smithy/node-http-handler@4.9.4': dependencies: - '@smithy/core': 3.23.17 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 - '@smithy/middleware-stack@4.2.14': + '@smithy/signature-v4@5.6.3': dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/node-config-provider@4.3.14': - dependencies: - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/node-http-handler@4.6.1': - dependencies: - '@smithy/protocol-http': 5.3.14 - '@smithy/querystring-builder': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/property-provider@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/protocol-http@5.3.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/querystring-builder@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - '@smithy/util-uri-escape': 4.2.2 - tslib: 2.8.1 - - '@smithy/querystring-parser@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/service-error-classification@4.3.0': - dependencies: - '@smithy/types': 4.14.1 - - '@smithy/shared-ini-file-loader@4.4.9': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/signature-v4@5.3.14': - dependencies: - '@smithy/is-array-buffer': 4.2.2 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-hex-encoding': 4.2.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-uri-escape': 4.2.2 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/smithy-client@4.12.13': - dependencies: - '@smithy/core': 3.23.17 - '@smithy/middleware-endpoint': 4.4.32 - '@smithy/middleware-stack': 4.2.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-stream': 4.5.25 + '@smithy/core': 3.29.2 + '@smithy/types': 4.16.0 tslib: 2.8.1 '@smithy/types@4.14.1': dependencies: tslib: 2.8.1 - '@smithy/url-parser@4.2.14': - dependencies: - '@smithy/querystring-parser': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-base64@4.3.2': - dependencies: - '@smithy/util-buffer-from': 4.2.2 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/util-body-length-browser@4.2.2': - dependencies: - tslib: 2.8.1 - - '@smithy/util-body-length-node@4.2.3': + '@smithy/types@4.16.0': dependencies: tslib: 2.8.1 @@ -31117,82 +30581,11 @@ snapshots: '@smithy/is-array-buffer': 2.2.0 tslib: 2.8.1 - '@smithy/util-buffer-from@4.2.2': - dependencies: - '@smithy/is-array-buffer': 4.2.2 - tslib: 2.8.1 - - '@smithy/util-config-provider@4.2.2': - dependencies: - tslib: 2.8.1 - - '@smithy/util-defaults-mode-browser@4.3.49': - dependencies: - '@smithy/property-provider': 4.2.14 - '@smithy/smithy-client': 4.12.13 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-defaults-mode-node@4.2.54': - dependencies: - '@smithy/config-resolver': 4.4.17 - '@smithy/credential-provider-imds': 4.2.14 - '@smithy/node-config-provider': 4.3.14 - '@smithy/property-provider': 4.2.14 - '@smithy/smithy-client': 4.12.13 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-endpoints@3.4.2': - dependencies: - '@smithy/node-config-provider': 4.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-hex-encoding@4.2.2': - dependencies: - tslib: 2.8.1 - - '@smithy/util-middleware@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-retry@4.3.4': - dependencies: - '@smithy/service-error-classification': 4.3.0 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-stream@4.5.25': - dependencies: - '@smithy/fetch-http-handler': 5.3.17 - '@smithy/node-http-handler': 4.6.1 - '@smithy/types': 4.14.1 - '@smithy/util-base64': 4.3.2 - '@smithy/util-buffer-from': 4.2.2 - '@smithy/util-hex-encoding': 4.2.2 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/util-uri-escape@4.2.2': - dependencies: - tslib: 2.8.1 - '@smithy/util-utf8@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 tslib: 2.8.1 - '@smithy/util-utf8@4.2.2': - dependencies: - '@smithy/util-buffer-from': 4.2.2 - tslib: 2.8.1 - - '@smithy/uuid@1.1.2': - dependencies: - tslib: 2.8.1 - '@standard-schema/spec@1.1.0': {} '@streamdown/math@1.0.2(react@19.1.0)': @@ -31555,7 +30948,7 @@ snapshots: '@tanstack/devtools-event-bus@0.3.3': dependencies: - ws: 8.18.3 + ws: 8.20.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -31583,8 +30976,8 @@ snapshots: '@tanstack/directive-functions-plugin@1.139.0(vite@7.1.7(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.27.1 - '@babel/core': 7.29.0 - '@babel/traverse': 7.29.0 + '@babel/core': 7.29.7 + '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 '@tanstack/router-utils': 1.139.0 babel-dead-code-elimination: 1.0.10 @@ -31616,6 +31009,17 @@ snapshots: - supports-color - typescript + '@tanstack/electric-db-collection@0.3.5(typescript@5.8.3)': + dependencies: + '@electric-sql/client': 1.5.20 + '@standard-schema/spec': 1.1.0 + '@tanstack/db': 0.6.7(typescript@5.8.3) + '@tanstack/store': 0.9.3 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + - typescript + '@tanstack/electric-db-collection@0.3.5(typescript@5.9.3)': dependencies: '@electric-sql/client': 1.5.20 @@ -31910,12 +31314,12 @@ snapshots: '@tanstack/server-functions-plugin@1.139.0(vite@7.1.7(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.27.1 - '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.29.0) - '@babel/template': 7.28.6 + '@babel/core': 7.29.7 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.29.7) + '@babel/template': 7.29.7 '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@tanstack/directive-functions-plugin': 1.139.0(vite@7.1.7(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0)) babel-dead-code-elimination: 1.0.10 tiny-invariant: 1.3.3 @@ -32202,8 +31606,6 @@ snapshots: '@tokenizer/token@0.3.0': {} - '@tootallnate/quickjs-emscripten@0.23.0': {} - '@trpc/client@11.7.2(@trpc/server@11.7.2(typescript@5.7.2))(typescript@5.7.2)': dependencies: '@trpc/server': 11.7.2(typescript@5.7.2) @@ -33621,7 +33023,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@22.19.17)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.0(@noble/hashes@2.0.1))(vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.0(@noble/hashes@2.0.1))(vite@7.3.2(@types/node@25.9.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0)) '@vitest/expect@3.2.4': dependencies: @@ -34306,13 +33708,9 @@ snapshots: ast-kit@1.4.3: dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.7 pathe: 2.0.3 - ast-types@0.13.4: - dependencies: - tslib: 2.8.1 - ast-types@0.16.1: dependencies: tslib: 2.8.1 @@ -34413,13 +33811,13 @@ snapshots: transitivePeerDependencies: - supports-color - babel-jest@29.7.0(@babel/core@7.29.0): + babel-jest@29.7.0(@babel/core@7.29.7): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.29.0) + babel-preset-jest: 29.6.3(@babel/core@7.29.7) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -34452,15 +33850,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): dependencies: '@babel/compat-data': 7.29.0 @@ -34478,10 +33867,10 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7): dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color @@ -34501,13 +33890,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7): dependencies: '@babel/core': 7.29.7 @@ -34541,9 +33923,9 @@ snapshots: transitivePeerDependencies: - '@babel/core' - babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.0): + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.7): dependencies: - '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.29.7) transitivePeerDependencies: - '@babel/core' @@ -34566,24 +33948,24 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.0) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.0) - babel-preset-current-node-syntax@1.1.1(@babel/core@7.29.0): + babel-preset-current-node-syntax@1.1.1(@babel/core@7.29.7): dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.29.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) babel-preset-expo@13.2.3(@babel/core@7.28.0): dependencies: @@ -34612,66 +33994,66 @@ snapshots: - '@babel/core' - supports-color - babel-preset-expo@54.0.10(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@54.0.35)(react-refresh@0.14.2): + babel-preset-expo@54.0.10(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@54.0.36)(react-refresh@0.14.2): dependencies: '@babel/helper-module-imports': 7.28.6 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.29.0) - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-runtime': 7.28.0(@babel/core@7.29.0) - '@babel/preset-react': 7.27.1(@babel/core@7.29.0) - '@babel/preset-typescript': 7.27.1(@babel/core@7.29.0) - '@react-native/babel-preset': 0.81.5(@babel/core@7.29.0) + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.29.7) + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-runtime': 7.28.0(@babel/core@7.29.7) + '@babel/preset-react': 7.27.1(@babel/core@7.29.7) + '@babel/preset-typescript': 7.27.1(@babel/core@7.29.7) + '@react-native/babel-preset': 0.81.5(@babel/core@7.29.7) babel-plugin-react-compiler: 1.0.0 babel-plugin-react-native-web: 0.21.2 babel-plugin-syntax-hermes-parser: 0.29.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.0) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) debug: 4.4.3 react-refresh: 0.14.2 resolve-from: 5.0.0 optionalDependencies: - '@babel/runtime': 7.29.2 - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + '@babel/runtime': 7.29.7 + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) transitivePeerDependencies: - '@babel/core' - supports-color - babel-preset-expo@54.0.11(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@54.0.35)(react-refresh@0.14.2): + babel-preset-expo@54.0.12(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@54.0.36)(react-refresh@0.14.2): dependencies: - '@babel/helper-module-imports': 7.28.6 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.29.0) - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-runtime': 7.28.0(@babel/core@7.29.0) - '@babel/preset-react': 7.27.1(@babel/core@7.29.0) - '@babel/preset-typescript': 7.27.1(@babel/core@7.29.0) - '@react-native/babel-preset': 0.81.5(@babel/core@7.29.0) + '@babel/helper-module-imports': 7.29.7 + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.29.7) + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-runtime': 7.28.0(@babel/core@7.29.7) + '@babel/preset-react': 7.27.1(@babel/core@7.29.7) + '@babel/preset-typescript': 7.27.1(@babel/core@7.29.7) + '@react-native/babel-preset': 0.81.5(@babel/core@7.29.7) babel-plugin-react-compiler: 1.0.0 babel-plugin-react-native-web: 0.21.2 babel-plugin-syntax-hermes-parser: 0.29.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.0) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) debug: 4.4.3 react-refresh: 0.14.2 resolve-from: 5.0.0 optionalDependencies: - '@babel/runtime': 7.29.2 - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + '@babel/runtime': 7.29.7 + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) transitivePeerDependencies: - '@babel/core' - supports-color @@ -34682,11 +34064,11 @@ snapshots: babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.1.1(@babel/core@7.28.0) - babel-preset-jest@29.6.3(@babel/core@7.29.0): + babel-preset-jest@29.6.3(@babel/core@7.29.7): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.1(@babel/core@7.29.0) + babel-preset-current-node-syntax: 1.1.1(@babel/core@7.29.7) bail@2.0.2: {} @@ -34700,8 +34082,6 @@ snapshots: baseline-browser-mapping@2.10.38: {} - basic-ftp@5.3.0: {} - bcrypt-pbkdf@1.0.2: dependencies: tweetnacl: 0.14.5 @@ -35473,7 +34853,7 @@ snapshots: core-js-compat@3.49.0: dependencies: - browserslist: 4.28.2 + browserslist: 4.28.4 core-js@3.39.0: {} @@ -35843,8 +35223,6 @@ snapshots: data-uri-to-buffer@4.0.1: {} - data-uri-to-buffer@6.0.2: {} - data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -35882,7 +35260,7 @@ snapshots: date-fns@2.30.0: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 date-fns@4.1.0: {} @@ -36002,12 +35380,6 @@ snapshots: defu@6.1.4: {} - degenerator@5.0.1: - dependencies: - ast-types: 0.13.4 - escodegen: 2.1.0 - esprima: 4.0.1 - del@6.1.1: dependencies: globby: 11.1.0 @@ -36960,14 +36332,6 @@ snapshots: escape-string-regexp@5.0.0: {} - escodegen@2.1.0: - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - eslint-config-prettier@10.1.8(eslint@9.32.0(jiti@2.6.1)): dependencies: eslint: 9.32.0(jiti@2.6.1) @@ -37398,28 +36762,28 @@ snapshots: transitivePeerDependencies: - supports-color - expo-asset@12.0.13(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3): + expo-asset@12.0.13(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3): dependencies: '@expo/image-utils': 0.8.14(typescript@5.9.3) - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - expo-constants: 18.0.13(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo-constants: 18.0.13(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)) react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) transitivePeerDependencies: - supports-color - typescript - expo-build-properties@1.0.10(expo@54.0.35): + expo-build-properties@1.0.10(expo@54.0.36): dependencies: ajv: 8.20.0 - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) semver: 7.7.4 - expo-clipboard@8.0.8(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-clipboard@8.0.8(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) expo-constants@17.1.7(expo@53.0.20(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0)))(react-native-webview@13.16.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0)): dependencies: @@ -37448,12 +36812,12 @@ snapshots: transitivePeerDependencies: - supports-color - expo-constants@18.0.13(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + expo-constants@18.0.13(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)): dependencies: '@expo/config': 12.0.13 '@expo/env': 2.0.11 - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) transitivePeerDependencies: - supports-color @@ -37464,10 +36828,10 @@ snapshots: expo: 53.0.20(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0)))(react-native-webview@13.16.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native: 0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0) - expo-file-system@19.0.23(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + expo-file-system@19.0.23(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)): dependencies: - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) expo-font@13.3.2(expo@53.0.20(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0)))(react-native-webview@13.16.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react@19.1.0): dependencies: @@ -37475,35 +36839,35 @@ snapshots: fontfaceobserver: 2.3.0 react: 19.1.0 - expo-font@14.0.12(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-font@14.0.12(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) fontfaceobserver: 2.3.0 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) - expo-image-loader@6.0.0(expo@54.0.35): + expo-image-loader@6.0.0(expo@54.0.36): dependencies: - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - expo-image-manipulator@14.0.8(expo@54.0.35): + expo-image-manipulator@14.0.8(expo@54.0.36): dependencies: - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - expo-image-loader: 6.0.0(expo@54.0.35) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo-image-loader: 6.0.0(expo@54.0.36) - expo-image-picker@17.0.11(expo@54.0.35): + expo-image-picker@17.0.11(expo@54.0.36): dependencies: - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - expo-image-loader: 6.0.0(expo@54.0.35) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo-image-loader: 6.0.0(expo@54.0.36) expo-keep-awake@14.1.4(expo@53.0.20(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0)))(react-native-webview@13.16.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react@19.1.0): dependencies: expo: 53.0.20(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0)))(react-native-webview@13.16.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react: 19.1.0 - expo-keep-awake@15.0.8(expo@54.0.35)(react@19.1.0): + expo-keep-awake@15.0.8(expo@54.0.36)(react@19.1.0): dependencies: - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react: 19.1.0 expo-linking@8.0.12(expo@53.0.20(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0)))(react-native-webview@13.16.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): @@ -37516,12 +36880,12 @@ snapshots: - expo - supports-color - expo-linking@8.0.12(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-linking@8.0.12(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: - expo-constants: 18.0.13(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + expo-constants: 18.0.13(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)) invariant: 2.2.4 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) transitivePeerDependencies: - expo - supports-color @@ -37548,11 +36912,11 @@ snapshots: dependencies: invariant: 2.2.4 - expo-modules-core@3.0.30(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-modules-core@3.0.30(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: invariant: 2.2.4 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) expo-router@5.1.4(716a7a11045ffabd7fab4726238d0981): dependencies: @@ -37582,21 +36946,21 @@ snapshots: - react-native - supports-color - expo-router@6.0.24(@expo/metro-runtime@6.1.2)(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.12)(expo@54.0.35)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-router@6.0.24(@expo/metro-runtime@6.1.2)(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.12)(expo@54.0.36)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: - '@expo/metro-runtime': 6.1.2(expo@54.0.35)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@expo/metro-runtime': 6.1.2(expo@54.0.36)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@expo/schema-utils': 0.1.8 '@radix-ui/react-slot': 1.2.0(@types/react@19.1.17)(react@19.1.0) '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-navigation/bottom-tabs': 7.4.4(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@react-navigation/native': 7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@react-navigation/native-stack': 7.3.23(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/bottom-tabs': 7.4.4(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/native-stack': 7.3.23(@react-navigation/native@7.1.16(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) client-only: 0.0.1 debug: 4.4.3 escape-string-regexp: 4.0.0 - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - expo-constants: 18.0.13(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) - expo-linking: 8.0.12(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo-constants: 18.0.13(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)) + expo-linking: 8.0.12(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-server: 1.0.7 fast-deep-equal: 3.1.3 invariant: 2.2.4 @@ -37604,10 +36968,10 @@ snapshots: query-string: 7.1.3 react: 19.1.0 react-fast-compare: 3.2.2 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) + react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) semver: 7.6.3 server-only: 0.0.1 sf-symbols-typescript: 2.2.0 @@ -37616,7 +36980,7 @@ snapshots: vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) optionalDependencies: react-dom: 19.1.0(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -37626,10 +36990,10 @@ snapshots: expo-server@1.0.7: {} - expo-splash-screen@31.0.13(expo@54.0.35)(typescript@5.9.3): + expo-splash-screen@31.0.13(expo@54.0.36)(typescript@5.9.3): dependencies: - '@expo/prebuild-config': 54.0.8(expo@54.0.35)(typescript@5.9.3) - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + '@expo/prebuild-config': 54.0.8(expo@54.0.36)(typescript@5.9.3) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) transitivePeerDependencies: - supports-color - typescript @@ -37641,27 +37005,27 @@ snapshots: react-native-edge-to-edge: 1.6.0(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-is-edge-to-edge: 1.2.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - expo-status-bar@3.0.9(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-status-bar@3.0.9(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) + react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - expo-system-ui@6.0.9(expo@54.0.35)(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + expo-system-ui@6.0.9(expo@54.0.36)(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)): dependencies: '@react-native/normalize-colors': 0.81.5 debug: 4.4.3 - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) optionalDependencies: react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) transitivePeerDependencies: - supports-color - expo-web-browser@15.0.11(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + expo-web-browser@15.0.11(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)): dependencies: - expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + expo: 54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) expo@53.0.20(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0)))(react-native-webview@13.16.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: @@ -37695,34 +37059,34 @@ snapshots: - supports-color - utf-8-validate - expo@54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3): + expo@54.0.36(@babel/core@7.29.7)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3): dependencies: - '@babel/runtime': 7.29.2 - '@expo/cli': 54.0.25(expo-router@6.0.24)(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(typescript@5.9.3) - '@expo/config': 12.0.13 - '@expo/config-plugins': 54.0.4 - '@expo/devtools': 0.1.8(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.29.7 + '@expo/cli': 54.0.26(expo-router@6.0.24)(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(typescript@5.9.3) + '@expo/config': 12.0.14 + '@expo/config-plugins': 54.0.5 + '@expo/devtools': 0.1.8(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@expo/fingerprint': 0.15.5 '@expo/metro': 54.2.0 - '@expo/metro-config': 54.0.16(expo@54.0.35) - '@expo/vector-icons': 15.1.1(expo-font@14.0.12(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@expo/metro-config': 54.0.17(expo@54.0.36) + '@expo/vector-icons': 15.1.1(expo-font@14.0.12(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@ungap/structured-clone': 1.3.1 - babel-preset-expo: 54.0.11(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@54.0.35)(react-refresh@0.14.2) - expo-asset: 12.0.13(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - expo-constants: 18.0.13(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) - expo-file-system: 19.0.23(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) - expo-font: 14.0.12(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - expo-keep-awake: 15.0.8(expo@54.0.35)(react@19.1.0) + babel-preset-expo: 54.0.12(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@54.0.36)(react-refresh@0.14.2) + expo-asset: 12.0.13(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo-constants: 18.0.13(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)) + expo-file-system: 19.0.23(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)) + expo-font: 14.0.12(expo@54.0.36)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-keep-awake: 15.0.8(expo@54.0.36)(react@19.1.0) expo-modules-autolinking: 3.0.26 - expo-modules-core: 3.0.30(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-modules-core: 3.0.30(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) pretty-format: 29.7.0 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) react-refresh: 0.14.2 whatwg-url-without-unicode: 8.0.0-3 optionalDependencies: - '@expo/metro-runtime': 6.1.2(expo@54.0.35)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-webview: 13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@expo/metro-runtime': 6.1.2(expo@54.0.36)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-webview: 13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) transitivePeerDependencies: - '@babel/core' - bufferutil @@ -37921,17 +37285,6 @@ snapshots: fast-uri@3.1.0: {} - fast-xml-builder@1.1.5: - dependencies: - path-expression-matcher: 1.5.0 - - fast-xml-parser@5.7.1: - dependencies: - '@nodable/entities': 2.1.0 - fast-xml-builder: 1.1.5 - path-expression-matcher: 1.5.0 - strnum: 2.2.3 - fastq@1.20.1: dependencies: reusify: 1.0.4 @@ -38316,14 +37669,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - get-uri@6.0.5: - dependencies: - basic-ftp: 5.3.0 - data-uri-to-buffer: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - getenv@2.0.0: {} getpass@0.1.7: @@ -38964,7 +38309,7 @@ snapshots: internal-slot@1.0.7: dependencies: es-errors: 1.3.0 - hasown: 2.0.3 + hasown: 2.0.4 side-channel: 1.1.0 internal-slot@1.1.0: @@ -39058,7 +38403,7 @@ snapshots: is-core-module@2.16.1: dependencies: - hasown: 2.0.3 + hasown: 2.0.4 is-data-view@1.0.2: dependencies: @@ -39282,7 +38627,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/parser': 7.29.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -39436,7 +38781,7 @@ snapshots: jimp@0.16.13: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@jimp/custom': 0.16.13 '@jimp/plugins': 0.16.13(@jimp/custom@0.16.13) '@jimp/types': 0.16.13(@jimp/custom@0.16.13) @@ -39653,7 +38998,7 @@ snapshots: json-schema-to-ts@3.1.1: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 ts-algebra: 2.0.0 json-schema-traverse@0.4.1: {} @@ -40086,8 +39431,6 @@ snapshots: dependencies: yallist: 4.0.0 - lru-cache@7.18.3: {} - lucide-react@0.544.0(react@19.2.0): dependencies: react: 19.2.0 @@ -40406,7 +39749,7 @@ snapshots: metro-babel-transformer@0.82.5: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 flow-enums-runtime: 0.0.6 hermes-parser: 0.29.1 nullthrows: 1.1.1 @@ -40415,7 +39758,7 @@ snapshots: metro-babel-transformer@0.83.3: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 flow-enums-runtime: 0.0.6 hermes-parser: 0.32.0 nullthrows: 1.1.1 @@ -40424,7 +39767,7 @@ snapshots: metro-babel-transformer@0.83.7: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 flow-enums-runtime: 0.0.6 hermes-parser: 0.35.0 metro-cache-key: 0.83.7 @@ -40579,17 +39922,17 @@ snapshots: metro-minify-terser@0.82.5: dependencies: flow-enums-runtime: 0.0.6 - terser: 5.46.2 + terser: 5.48.0 metro-minify-terser@0.83.3: dependencies: flow-enums-runtime: 0.0.6 - terser: 5.46.2 + terser: 5.48.0 metro-minify-terser@0.83.7: dependencies: flow-enums-runtime: 0.0.6 - terser: 5.46.2 + terser: 5.48.0 metro-resolver@0.82.5: dependencies: @@ -40605,17 +39948,17 @@ snapshots: metro-runtime@0.82.5: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 flow-enums-runtime: 0.0.6 metro-runtime@0.83.3: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 flow-enums-runtime: 0.0.6 metro-runtime@0.83.7: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 flow-enums-runtime: 0.0.6 metro-source-map@0.82.5: @@ -40635,7 +39978,7 @@ snapshots: metro-source-map@0.83.3: dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 '@babel/traverse--for-generate-function-map': '@babel/traverse@7.29.7' '@babel/types': 7.29.7 flow-enums-runtime: 0.0.6 @@ -40697,10 +40040,10 @@ snapshots: metro-transform-plugins@0.82.5: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 flow-enums-runtime: 0.0.6 nullthrows: 1.1.1 transitivePeerDependencies: @@ -40708,10 +40051,10 @@ snapshots: metro-transform-plugins@0.83.3: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 flow-enums-runtime: 0.0.6 nullthrows: 1.1.1 transitivePeerDependencies: @@ -40719,7 +40062,7 @@ snapshots: metro-transform-plugins@0.83.7: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 '@babel/template': 7.29.7 '@babel/traverse': 7.29.0 @@ -40730,7 +40073,7 @@ snapshots: metro-transform-worker@0.82.5: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 '@babel/parser': 7.29.7 '@babel/types': 7.29.7 @@ -40750,7 +40093,7 @@ snapshots: metro-transform-worker@0.83.3: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 '@babel/parser': 7.29.7 '@babel/types': 7.29.7 @@ -40770,7 +40113,7 @@ snapshots: metro-transform-worker@0.83.7: dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 '@babel/parser': 7.29.7 '@babel/types': 7.29.7 @@ -40790,9 +40133,9 @@ snapshots: metro@0.82.5: dependencies: - '@babel/code-frame': 7.29.0 - '@babel/core': 7.29.0 - '@babel/generator': 7.29.1 + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 '@babel/parser': 7.29.2 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 @@ -40838,11 +40181,11 @@ snapshots: metro@0.83.3: dependencies: '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 '@babel/parser': 7.29.7 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 accepts: 1.3.8 chalk: 4.1.2 @@ -41325,8 +40668,6 @@ snapshots: nested-error-stacks@2.0.1: {} - netmask@2.1.1: {} - next@14.2.17(@babel/core@7.28.4)(@opentelemetry/api@1.9.1)(@playwright/test@1.52.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@next/env': 14.2.17 @@ -41355,7 +40696,7 @@ snapshots: - babel-plugin-macros optional: true - next@14.2.17(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.17(@opentelemetry/api@1.9.1)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.2.17 '@swc/helpers': 0.5.5 @@ -41365,7 +40706,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.29.7)(react@18.3.1) + styled-jsx: 5.1.1(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.17 '@next/swc-darwin-x64': 14.2.17 @@ -41936,24 +41277,6 @@ snapshots: p-try@2.2.0: {} - pac-proxy-agent@7.2.0: - dependencies: - '@tootallnate/quickjs-emscripten': 0.23.0 - agent-base: 7.1.4 - debug: 4.4.3 - get-uri: 6.0.5 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - pac-resolver@7.0.1: - dependencies: - degenerator: 5.0.1 - netmask: 2.1.1 - package-json-from-dist@1.0.1: {} package-manager-detector@0.2.2: {} @@ -42051,8 +41374,6 @@ snapshots: path-exists@4.0.0: {} - path-expression-matcher@1.5.0: {} - path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -42312,7 +41633,7 @@ snapshots: polished@4.3.1: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 possible-typed-array-names@1.0.0: {} @@ -42333,7 +41654,7 @@ snapshots: postcss-load-config@4.0.2(postcss@8.4.47): dependencies: lilconfig: 3.1.2 - yaml: 2.8.1 + yaml: 2.9.0 optionalDependencies: postcss: 8.4.47 @@ -42683,19 +42004,6 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-agent@6.5.0: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - lru-cache: 7.18.3 - pac-proxy-agent: 7.2.0 - proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - proxy-compare@3.0.1: {} proxy-from-env@1.1.0: {} @@ -42966,13 +42274,13 @@ snapshots: react: 19.1.0 react-native: 0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0) - react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: '@egjs/hammerjs': 2.0.17 hoist-non-react-statics: 3.3.2 invariant: 2.2.4 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) react-native-is-edge-to-edge@1.2.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: @@ -42984,30 +42292,30 @@ snapshots: react: 19.1.0 react-native: 0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0) - react-native-is-edge-to-edge@1.3.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-is-edge-to-edge@1.3.1(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) react-native-random-uuid@0.1.4(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0)): dependencies: fast-base64-decode: 1.0.0 react-native: 0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0) - react-native-random-uuid@0.1.4(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + react-native-random-uuid@0.1.4(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0)): dependencies: fast-base64-decode: 1.0.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) react-native-safe-area-context@5.6.2(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 react-native: 0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0) - react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) react-native-screens@4.16.0(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: @@ -43017,20 +42325,20 @@ snapshots: react-native-is-edge-to-edge: 1.3.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) warn-once: 0.1.1 - react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 react-freeze: 1.0.4(react@19.1.0) - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) + react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) warn-once: 0.1.1 - react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: css-select: 5.1.0 css-tree: 1.1.3 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) warn-once: 0.1.1 react-native-web@0.20.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): @@ -43063,12 +42371,12 @@ snapshots: transitivePeerDependencies: - encoding - react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: escape-string-regexp: 4.0.0 invariant: 2.2.4 react: 19.1.0 - react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native: 0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0) react-native-webview@13.16.1(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: @@ -43125,20 +42433,20 @@ snapshots: - supports-color - utf-8-validate - react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0): + react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native/assets-registry': 0.81.5 - '@react-native/codegen': 0.81.5(@babel/core@7.29.0) + '@react-native/codegen': 0.81.5(@babel/core@7.29.7) '@react-native/community-cli-plugin': 0.81.5 '@react-native/gradle-plugin': 0.81.5 '@react-native/js-polyfills': 0.81.5 '@react-native/normalize-colors': 0.81.5 - '@react-native/virtualized-lists': 0.81.5(@types/react@19.1.17)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-native/virtualized-lists': 0.81.5(@types/react@19.1.17)(react-native@0.81.5(@babel/core@7.29.7)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 - babel-jest: 29.7.0(@babel/core@7.29.0) + babel-jest: 29.7.0(@babel/core@7.29.7) babel-plugin-syntax-hermes-parser: 0.29.1 base64-js: 1.5.1 commander: 12.1.0 @@ -43184,7 +42492,7 @@ snapshots: react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@types/react-redux': 7.1.34 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 @@ -43479,7 +42787,7 @@ snapshots: redux@4.2.1: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 reflect.getprototypeof@1.0.10: dependencies: @@ -43510,7 +42818,7 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 regex-recursion@6.0.2: dependencies: @@ -44406,7 +43714,8 @@ snapshots: slugify@1.6.6: {} - smart-buffer@4.2.0: {} + smart-buffer@4.2.0: + optional: true smob@1.6.2: {} @@ -44415,19 +43724,6 @@ snapshots: dot-case: 3.0.4 tslib: 2.8.1 - socks-proxy-agent@8.0.5: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - socks: 2.8.7 - transitivePeerDependencies: - - supports-color - - socks@2.8.7: - dependencies: - ip-address: 10.1.0 - smart-buffer: 4.2.0 - solid-js@1.9.7: dependencies: csstype: 3.2.3 @@ -44854,8 +44150,6 @@ snapshots: dependencies: js-tokens: 9.0.1 - strnum@2.2.3: {} - strtok3@6.3.0: dependencies: '@tokenizer/token': 0.3.0 @@ -44902,12 +44196,10 @@ snapshots: '@babel/core': 7.28.4 optional: true - styled-jsx@5.1.1(@babel/core@7.29.7)(react@18.3.1): + styled-jsx@5.1.1(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 - optionalDependencies: - '@babel/core': 7.29.7 styleq@0.1.3: {} @@ -45136,13 +44428,6 @@ snapshots: terminal-size@4.0.1: {} - terser@5.46.2: - dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.16.0 - commander: 2.20.3 - source-map-support: 0.5.21 - terser@5.48.0: dependencies: '@jridgewell/source-map': 0.3.11 @@ -45558,7 +44843,7 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.1 - typebox@1.1.34: {} + typebox@1.1.38: {} typed-array-buffer@1.0.3: dependencies: