Compare commits

..

8 Commits

Author SHA1 Message Date
Mohamed Boudra
bb6a4db6e0 chore(release): cut 0.1.87 2026-05-30 09:49:45 +07:00
Mohamed Boudra
2a13a082b7 Changelog for 0.1.87 2026-05-30 09:48:43 +07:00
Mohamed Boudra
eea5932a21 Fix mobile bottom sheets reopening after dismiss 2026-05-30 09:43:49 +07:00
paseo-ai[bot]
e04bb942e2 fix: update lockfile signatures and Nix hash [skip ci] 2026-05-29 16:05:39 +00:00
Mohamed Boudra
3c3574d670 Fix intermittent Android crash by upgrading Reanimated to 4.3.1
Reanimated 4.1.x crashes on Android with a NullPointerException in
ViewGroup.dispatchDraw (ReactViewGroup.dispatchDraw) — a child view is
null mid-draw because Reanimated commits a ShadowTree change during the
Android draw pass, mutating the view hierarchy while it's being painted.
Fixed upstream in 4.3.0 (software-mansion/react-native-reanimated#9072).

4.3.x requires react-native-worklets 0.8.x, so worklets moves 0.5.1 ->
0.8.3 in lockstep. react-native is pinned to exact 0.81.5 so the
re-resolve can't pull 0.81.6 (which needs react ^19.1.4, conflicting with
the react 19.1.0 override), and reanimated/worklets are added to root
overrides to force a single version across the tree — two copies of the
worklets runtime throw a version-mismatch at startup.
2026-05-29 23:01:07 +07:00
Mohamed Boudra
44863ec1dd Forward subagent permission requests 2026-05-29 22:02:18 +07:00
Mohamed Boudra
47414abc5e website tweaks 2026-05-29 19:33:57 +07:00
paseo-ai[bot]
9860dd36ef fix: update lockfile signatures and Nix hash [skip ci] 2026-05-29 11:50:20 +00:00
23 changed files with 1090 additions and 92 deletions

View File

@@ -1,5 +1,16 @@
# Changelog
## 0.1.87 - 2026-05-30
### Added
- Permission prompts from OpenCode subagents now surface in Paseo so you can approve or deny them
### Fixed
- Fixed an intermittent Android crash while animated views were drawing
- Fixed mobile bottom sheets not reopening after being dismissed
## 0.1.86 - 2026-05-29
### Added

View File

@@ -155,6 +155,20 @@ The "render invisible to measure, then reveal" pattern is the canonical
solution to chicken-and-egg positioning in this codebase. Reach for it before
anything fancier.
## Gotcha 6 — Bottom sheet refs are not lifecycle truth
`@gorhom/bottom-sheet` modals churn their imperative ref while presenting and
dismissing. Do not treat `ref != null` as permission to call `present()`, and do
not treat `ref == null` as the sheet being closed. The user-visible lifecycle is
the desired `visible` prop plus the sheet callbacks (`onChange(-1)`,
`onDismiss`).
If a user closes a sheet with the backdrop or a pan gesture, the sheet may detach
and reattach before React state has acknowledged `visible=false`. Re-presenting
on that attach races Gorhom's dismiss path and leaves the modal unable to reopen.
Track an explicit phase (`closed` / `presenting` / `presented` / `dismissing`) and
ignore ref churn while dismissing.
## Recipe for a new anchored panel
Before you write a new one, ask:

View File

@@ -0,0 +1,149 @@
# Session God-File Decomposition Plan
`packages/server/src/server/session.ts` — 9116 lines, one `Session` class (declared line 724), 128 handlers, ~60 instance fields, 7 entangled domains. Goal: a **strictly behavior-preserving, incremental** decomposition into per-domain controllers, mirroring the existing `TerminalSessionController`.
## Chosen strategy: controller-context (per-domain option-bag controllers)
Each domain becomes a controller class in its own file with the **exact** contract the repo already proved twice (`TerminalSessionController` at `packages/server/src/terminal/terminal-session-controller.ts`, and `CreateAgentLifecycleDispatch`):
- An **options-bag constructor** injecting only what that domain reads.
- An **owned-type `ReadonlySet`** of message types.
- A **NON-async `dispatch(msg): Promise<void> | undefined`** that checks the owned-type set FIRST and returns `undefined` synchronously on a miss (verified at terminal-session-controller.ts:140-143).
- `start()` wired from `subscribeToOptionalManagers`, `dispose()` called by the shell's ordered `cleanup()`.
Session shrinks to a connection/dispatch shell: it keeps `handleMessage`, the `??` chain (1739-1751), `emit`/`emitBinary`, `sessionLogger`, connection identity, inflight metrics, lifecycle intents, and the **ordered** `cleanup()`. Each `dispatchXMessage` collapses to `return this.xController.dispatch(msg)`.
### Why this is safe at the dispatch seam (verified)
`dispatchInboundMessage` builds `a() ?? b() ?? ... ?? dispatchMiscMessage()` and short-circuits on the first non-`undefined` **Promise object** (not its resolved value). Message-type spaces are **disjoint** (no duplicate `case` labels across switches), so at most one dispatcher matches any message — collapsing to delegation cannot change which handler runs. `dispatchTerminalMessage` (2150-2153) already proves this. Two quirks preserved verbatim: schedule/\* is reached via the chat dispatcher's OWN `default` arm (2183), not the top-level `??`; and `start_workspace_script_request` (a workspace type) is special-cased before terminal delegation (2150).
Rejected alternatives: **feature-module** (free functions + wide context bag) cannot own the live state machines (workspaceUpdatesSubscription, agentUpdatesSubscription, ~25 voice fields) and adds a competing idiom; **mixin-composition** preserves the shared-`this` god object verbatim and requires widening ~325 private fields to protected.
## Slice ordering (least-coupled first)
The task recommended **git/checkout as the first slice — OVERRIDDEN.** Verification: `emitCheckoutStatusUpdate` is called from exactly ONE site (session.ts:4915), inside the workspace-owned `syncWorkspaceGitObserver` callback that ALSO fires workspace effects over shared watch-target maps. Extracting checkout first forces splitting the hardest workspace/git seam before workspace is touched. The strictly safer first cuts are **chat-schedule-loop** (only knot: `handleChatPostRequest`; touches no shared observer/git/voice state) and **provider-catalog** (one shared collaborator + injected predicates).
| # | Slice | Effort | Risk |
| --- | ----------------------------------------------------------------------------- | ------ | ------ |
| 0 | Test net + disjointness tripwire (no extraction) | M | low |
| 1 | ChatScheduleLoopController — **STOP FOR REVIEW after green** | M | low |
| 2 | ProviderCatalogController | M | medium |
| 3 | Split shared workspace-git observer + agent-subscribe fan-out (no controller) | M | high |
| 4 | GitCheckoutController | L | medium |
| 5 | WorkspaceController | XL | high |
| 6 | Voice prereqs: emit() purity + abortController ownership | M | high |
| 7 | VoiceSessionController | XL | high |
| 8a | Agent-lifecycle config setters | M | medium |
| 8b | AgentLifecycleController | XL | high |
---
## Slice 0 — Test net + disjointness tripwire (prerequisite)
No production code moves. Add `session.dispatch-seam.test.ts`. This is the gate the whole plan rests on, because chat/schedule/loop have **zero** handleMessage coverage today (verified).
Write RED-then-GREEN against the **current in-place** Session:
- `chat/post` happy path (asserts `chat/post` response emitted) + fanout-limit error path (asserts the `chat/post` error envelope, NOT a bubbled `rpc_error`).
- one `schedule/*` and one `loop/*` round-trip.
- a handler that throws **synchronously** emits `rpc_error{code:"handler_error"}` + an `activity_log` error frame.
- a handler that **rejects async** emits the SAME pair.
- a table-driven assertion that the union of all controllers' owned-type `ReadonlySet`s is pairwise disjoint and covers the dispatched `SessionInboundMessage` union (grows as controllers land).
**Tests:** `session.dispatch-seam.test.ts`, `session.test.ts`.
---
## Slice 1 — ChatScheduleLoopController ← STOP FOR HUMAN REVIEW after this ships green
**Move:** all 21 handlers (`handleChat*` ×7, `handleSchedule*` ×9, `handleLoop*` ×5), the three rpc-error emitters (`emitChatRpcError`/`emitScheduleRpcError`/`emitLoopRpcError`**kept separate, not merged**), `toScheduleSummary``packages/server/src/server/chat/chat-schedule-loop-controller.ts`. Collapse `dispatchChatScheduleLoopMessage` + `dispatchScheduleMessage` to `return this.chatScheduleLoopController.dispatch(msg)`.
**SessionContext surface:** `emit`, `sessionLogger`, `clientId` (authorAgentId fallback), `chatService`, `scheduleService`, `loopService`, and a narrow agent-control port `{ listAgents, resolveAgentIdentifier, agentStorage.list }` for `handleChatPostRequest` mention fanout.
**Owned-type set MUST include all 7 `chat/*` + 5 `loop/*` + 9 `schedule/*` types** — schedule/\* is currently routed via the chat dispatcher's own `default` arm, so it must stay inside this one controller, or schedule requests silently no-op.
**Behavior note:** least-coupled domain. Move the three rpc-error emitters verbatim (they differ in default code + the `ChatServiceError` branch). **Tests:** `session.dispatch-seam.test.ts`, `loop-service.test.ts`, `session.test.ts`.
---
## Slice 2 — ProviderCatalogController
**Move:** 7 provider handlers + `emitProviderDisabledResponse` + `getProviderSnapshotEntryForRead``packages/server/src/server/provider/provider-catalog-controller.ts`. Move the `providers_snapshot_update` PUSH wiring (1235-1254) into the controller's `start()`/`dispose()`. Collapse `dispatchProviderMessage`.
**SessionContext surface:** `emit`, `sessionLogger`, `providerSnapshotManager` (**shared by reference** — stays a daemon singleton read by checkout/lifecycle/workspace), `isProviderVisibleToClient` (predicate closing over `this`, reads `appVersion` live), `downgradeModeIconsForClient`, `downgradeEntryModesForClient`, agent-control reads `{ listProviderAvailability, listDraftFeatures }`.
**Behavior note:** COMPAT correctness — PUSH and PULL paths MUST call the SAME injected visibility/downgrade closures, reading `appVersion` LIVE (mutated post-construction via `updateAppVersion`). Keep `COMPAT(providersSnapshot)` and `COMPAT(customModeIcons)` comments verbatim. Do NOT pull `resolveStructuredGenerationProviders`/`getFocusedAgentSelectionForCwd` in. **Tests:** `session.dispatch-seam.test.ts`, `daemon-e2e/models.e2e.test.ts`, `session.test.ts`.
---
## Slice 3 — Split the shared observer seams (prerequisite, no controller)
In-place refactor on the shell, two named fan-outs:
1. **workspace-git observer** (4910-4917): make `emitCheckoutStatusUpdate` and `onBranchChanged` injectable callbacks; keep `workspaceGitWatchTargets`/`workspaceGitSubscriptions` shared by reference.
2. **agentManager.subscribe callback** (~1298): refactor into `{ onAgentUpdate, shouldAutoAllowVoicePermission(event), onStreamEvent }`.
**Behavior note:** the single hardest seam, split exactly once before the two domains that co-own it. The observer fires BOTH workspace (`handleWorkspaceGitBranchSnapshot`, `emitWorkspaceUpdateForCwd`) and checkout (`emitCheckoutStatusUpdate`) effects; the agent-subscribe callback is invoked by agent EVENTS (not the `??` chain) and does lifecycle + voice work. Add a test asserting BOTH a `workspace_update` and a `checkout_status_update` fire from one simulated git snapshot change, and a voice-permission test for the auto-allow path. **Tests:** `session.workspace-git-watch.test.ts`, `session.workspaces.test.ts`, `voice-permission-policy.test.ts`, `session.test.ts`.
---
## Slice 4 — GitCheckoutController
**Move:** ~22 `checkout_*`/`stash_*`/PR/github handlers + `handleSubscribeCheckoutDiffRequest`/`handleUnsubscribeCheckoutDiffRequest` + `emitCheckoutStatusUpdate` + `checkoutDiffSubscriptions``packages/server/src/server/checkout/git-checkout-controller.ts`. Collapse `dispatchCheckoutMessage`.
**SessionContext surface:** `emit`, `sessionLogger`, `checkoutDiffManager` (move in + dispose teardown), `github` (shared), `workspaceGitService` (**shared spine**), `workspaceGitWatchTargets`/`workspaceGitSubscriptions` (**shared**), `providerSnapshotManager.listRegisteredProviderIds`. `emitCheckoutStatusUpdate` is now owned here and injected back into the workspace observer seam from Slice 3.
**Behavior note:** safe now that Slice 3 split the observer. `checkoutDiffSubscriptions` teardown moves to `dispose()`, called by `cleanup()` at its current ordinal (8530). **Tests:** `session.dispatch-seam.test.ts`, `checkout-diff-manager.test.ts`, `daemon-e2e/checkout-diff-subscription.e2e.test.ts`, `session.test.ts`.
---
## Slice 5 — WorkspaceController (XL)
**Move:** all workspace handlers (incl. re-homed `handleProjectRenameRequest` and `start_workspace_script_request`) + ~25 private workspace helpers + the whole `workspaceUpdatesSubscription` state machine → `packages/server/src/server/workspace/workspace-controller.ts`.
**SessionContext surface:** `emit`, `sessionLogger`, `projectRegistry`/`workspaceRegistry`/`downloadTokenStore`/script stores/editor cache (**owned**), `workspaceGitService` + watch maps (**shared with checkout**), injected `emitCheckoutStatusUpdate`/`onBranchChanged`, `terminalManager`/`killTerminalsUnderPath`, an `agentUpdatesSubscription` write via a narrow `bufferAgentUpdate` command, `providerSnapshotManager.listRegisteredProviderIds`.
**Behavior note:** the workspaceUpdatesSubscription machine moves WHOLE. The eight already-public workspace methods stay a public surface re-exposed via the shell. Re-homes are atomic remove-from-old-dispatcher + add-to-new-owned-set. **Tests:** `session.workspaces.test.ts`, `session.workspace-git-watch.test.ts`, `session.workspace-resolution-invariants.test.ts`, `session.test.ts`.
---
## Slice 6 — Voice prerequisites (emit purity + abort ownership)
In-place, separately reviewable. Split the `audio_output` TTS-debug branch out of `emit()` (8421-8468, bypasses to `onMessage` at 8454) so `emit` is a pure trace+onMessage sink. Move `convertPCMToWavBuffer` (674-701) to `speech/audio.ts`. Decide abortController ownership.
**Behavior note:** TTS-debug split and abortController ownership are the SAME decision (`ttsDebugStreams.clear()` is tied to `createAbortController` reassignment at 8359). Keep `emit` (with the universal trace) on the shell and inject it everywhere — no trace-less emit. Do NOT inject the AbortController by value. Add: a TTS-debug persistence test (with the debug env flag) before the move, and a barge-in→cleanup regression test asserting the NEW run's signal is aborted. **Tests:** `voice-roundtrip.e2e.test.ts`, `voice-permission-policy.test.ts`, `session.test.ts`.
---
## Slice 7 — VoiceSessionController (XL)
**Move:** voice handlers + ~25 voice fields + the TTS-debug hook (Slice 6) + `voiceModeAgentId`/`isVoiceMode` + the `shouldAutoAllowVoicePermission` predicate (Slice 3) → `packages/server/src/server/voice/voice-session-controller.ts`. Carve voice types out of `dispatchVoiceAndControlMessage`, leaving infra (restart/shutdown/heartbeat/ping/abort) on the shell.
**SessionContext surface:** pure `emit`, `emitBinary`, `hasBinaryChannel`, `sessionLogger`/`sessionId`/`paseoHome`, `getSpeechReadiness`, agent-control port `{ loadAgent, reloadWithSystemPrompt, interruptIfRunning, isRunning, sendSpokenText, buildAgentPrompt }`, `getSignal`/`abortCurrent` (Slice 6).
**Behavior note:** depends on Slices 3 + 6. `cleanup()` stays the ordered orchestrator and calls `voiceController.dispose()` at the position the inlined voice teardown occupies today (8505-8525). **Tests:** `voice-roundtrip.e2e.test.ts`, `voice-local-agent.e2e.test.ts`, `session.voice-mcp-config.test.ts`, `session.test.ts`.
---
## Slice 8a — Agent-lifecycle config setters
Parameterize the 4 setter envelopes `handleSetAgentMode/Model/Feature/Thinking` (4209-4390) into one helper; re-home `handleListCommandsRequest` (misfiled in `dispatchMiscMessage`). Add a handleMessage-driven **failure** test per setter (force the command to reject, assert both the `*_response{accepted:false}` AND the `activity_log` error frame in order) BEFORE collapsing. **Tests:** `session.test.ts`, `session.lifecycle-boundary.test.ts`.
## Slice 8b — AgentLifecycleController (XL, LAST)
**Move:** remaining lifecycle handlers + the `agentUpdatesSubscription` fan-out (`bufferOrEmitAgentUpdate`, `flushBootstrappedAgentUpdates`, `matchesAgentFilter`, `forwardAgentUpdate`) → `packages/server/src/server/agent/agent-lifecycle-controller.ts`. Collapse the three lifecycle dispatchers.
**SessionContext surface:** `emit`, `sessionLogger`, `agentManager`/`agentStorage` (**owned**), injected `forwardAgentUpdate``buildProjectPlacementForCwd` (backed by WorkspaceController), `agentUpdatesSubscription` accessor (owned; workspace writes via `bufferAgentUpdate`), `isProviderVisibleToClient`, `resolveCreateAgentWorkspace`, `supports`, `mcpBaseUrl`, `terminalController.killTerminalForClose`.
**Behavior note:** done LAST — the shared-projection hub. `handleCloseItemsRequest` splits its terminal-kill half from its agent-archive half. **Tests:** `session.test.ts`, `session.wait-for-finish.test.ts`, `session.create-agent-title.test.ts`, `session.lifecycle-boundary.test.ts`, `daemon-client.e2e.test.ts`.
---
## Cross-cutting invariants (every slice)
- **Always** run `npm run typecheck` and `npm run lint` after each slice; run `npm run build:server` before diagnosing cross-package type errors.
- Controller `dispatch` is **NON-async**, guarded by an owned-type `ReadonlySet` check returning `undefined` synchronously on miss. Never `async dispatch`.
- Controllers add **no** try/catch inside `dispatch` — error handling stays in `handleMessage`.
- `cleanup()` stays the single ordered teardown orchestrator on the shell.
- Move domain error emitters **verbatim**; treat any cross-domain emitter merge as a separate, test-guarded change.
- Per-slice typecheck/lint/format via `npm run` scripts; never re-run the full suite locally (run only the listed files with `--bail=1`).

View File

@@ -1 +1 @@
sha256-PYbY3Lk9+y2byl1mN9dVO4YGXLEZrmnuPYxDw59LE5g=
sha256-B6xcv2QfqpmvZCwYZ34pF2HWu3Icjl3bylHcQr8lo8g=

676
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "paseo",
"version": "0.1.86",
"version": "0.1.87",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "paseo",
"version": "0.1.86",
"version": "0.1.87",
"hasInstallScript": true,
"license": "AGPL-3.0-or-later",
"workspaces": [
@@ -10120,6 +10120,588 @@
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/metro-babel-transformer": {
"version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.85.3.tgz",
"integrity": "sha512-omuKq+r7jM4XvCMIlNMPP7Up3SyB8o5EAdZtF7YXniKyq7UOMBqhYHFqgsdOXr0lT+3ADf7VCJG3sb82jlBrrQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.25.2",
"@react-native/babel-preset": "0.85.3",
"hermes-parser": "0.33.3",
"nullthrows": "^1.1.1"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
},
"peerDependencies": {
"@babel/core": "*"
}
},
"node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-plugin-codegen": {
"version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.85.3.tgz",
"integrity": "sha512-Wc94zGfeFG8Njf9SHMPfYZP04kjigkOps6F1TYTvd7ZVXuGxqseCDgxc50LWcOhOCLypI9n3oVVqz81C3p44ZA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/traverse": "^7.29.0",
"@react-native/codegen": "0.85.3"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-preset": {
"version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.85.3.tgz",
"integrity": "sha512-fD7fxEhkJB/aF57tWoXjaAWpklfrExYZS3k6aXPP3BQ77DZY7gvf/b7dbirwjID6NVnP1JDRJyTuPBGr0K/vlw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/plugin-proposal-export-default-from": "^7.24.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-export-default-from": "^7.24.7",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
"@babel/plugin-transform-async-generator-functions": "^7.25.4",
"@babel/plugin-transform-async-to-generator": "^7.24.7",
"@babel/plugin-transform-block-scoping": "^7.25.0",
"@babel/plugin-transform-class-properties": "^7.25.4",
"@babel/plugin-transform-classes": "^7.25.4",
"@babel/plugin-transform-destructuring": "^7.24.8",
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
"@babel/plugin-transform-for-of": "^7.24.7",
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7",
"@babel/plugin-transform-optional-catch-binding": "^7.24.7",
"@babel/plugin-transform-optional-chaining": "^7.24.8",
"@babel/plugin-transform-private-methods": "^7.24.7",
"@babel/plugin-transform-private-property-in-object": "^7.24.7",
"@babel/plugin-transform-react-display-name": "^7.24.7",
"@babel/plugin-transform-react-jsx": "^7.25.2",
"@babel/plugin-transform-react-jsx-self": "^7.24.7",
"@babel/plugin-transform-react-jsx-source": "^7.24.7",
"@babel/plugin-transform-regenerator": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/plugin-transform-typescript": "^7.25.2",
"@babel/plugin-transform-unicode-regex": "^7.24.7",
"@react-native/babel-plugin-codegen": "0.85.3",
"babel-plugin-syntax-hermes-parser": "0.33.3",
"babel-plugin-transform-flow-enums": "^0.0.2",
"react-refresh": "^0.14.0"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
},
"peerDependencies": {
"@babel/core": "*"
}
},
"node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/codegen": {
"version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.3.tgz",
"integrity": "sha512-/JkS1lGLyzBWP1FbgDwaqEf7qShIC6pUC1M0a/YMAd/v4iqR24MRkQWe7jkYvcBQ2LpEhs5NGE9InhxSv21zCA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/parser": "^7.29.0",
"hermes-parser": "0.33.3",
"invariant": "^2.2.4",
"nullthrows": "^1.1.1",
"tinyglobby": "^0.2.15",
"yargs": "^17.6.2"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
},
"peerDependencies": {
"@babel/core": "*"
}
},
"node_modules/@react-native/metro-babel-transformer/node_modules/babel-plugin-syntax-hermes-parser": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.33.3.tgz",
"integrity": "sha512-/Z9xYdaJ1lC0pT9do6TqCqhOSLfZ5Ot8D5za1p+feEfWYupCOfGbhhEXN9r2ZgJtDNUNRw/Z+T2CvAGKBqtqWA==",
"license": "MIT",
"peer": true,
"dependencies": {
"hermes-parser": "0.33.3"
}
},
"node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz",
"integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==",
"license": "MIT",
"peer": true
},
"node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz",
"integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==",
"license": "MIT",
"peer": true,
"dependencies": {
"hermes-estree": "0.33.3"
}
},
"node_modules/@react-native/metro-babel-transformer/node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@react-native/metro-config": {
"version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.85.3.tgz",
"integrity": "sha512-sVo6HepUmCcpdfozEf91lA0FjpLNNZYu/Zi9FiYiAQTK8pzATXDVTqhvdxpFrQn435p5eUTSbllvbH/KN+bnyA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@react-native/js-polyfills": "0.85.3",
"@react-native/metro-babel-transformer": "0.85.3",
"metro-config": "^0.84.3",
"metro-runtime": "^0.84.3"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/@react-native/js-polyfills": {
"version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.85.3.tgz",
"integrity": "sha512-U2+aMshIXf1uFn77tpBb/xhHWB9vkVrMpt7kkucAugF8hJKYTDGB587X7WwelHduK2KBfhl4giSv0rzZGoef9A==",
"license": "MIT",
"peer": true,
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"peer": true,
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/@react-native/metro-config/node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 14"
}
},
"node_modules/@react-native/metro-config/node_modules/ci-info": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"license": "MIT",
"peer": true
},
"node_modules/@react-native/metro-config/node_modules/hermes-estree": {
"version": "0.35.0",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz",
"integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==",
"license": "MIT",
"peer": true
},
"node_modules/@react-native/metro-config/node_modules/hermes-parser": {
"version": "0.35.0",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz",
"integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==",
"license": "MIT",
"peer": true,
"dependencies": {
"hermes-estree": "0.35.0"
}
},
"node_modules/@react-native/metro-config/node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"peer": true,
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@react-native/metro-config/node_modules/metro": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro/-/metro-0.84.4.tgz",
"integrity": "sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/core": "^7.25.2",
"@babel/generator": "^7.29.1",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/traverse": "^7.29.0",
"@babel/types": "^7.29.0",
"accepts": "^2.0.0",
"ci-info": "^2.0.0",
"connect": "^3.6.5",
"debug": "^4.4.0",
"error-stack-parser": "^2.0.6",
"flow-enums-runtime": "^0.0.6",
"graceful-fs": "^4.2.4",
"hermes-parser": "0.35.0",
"image-size": "^1.0.2",
"invariant": "^2.2.4",
"jest-worker": "^29.7.0",
"jsc-safe-url": "^0.2.2",
"lodash.throttle": "^4.1.1",
"metro-babel-transformer": "0.84.4",
"metro-cache": "0.84.4",
"metro-cache-key": "0.84.4",
"metro-config": "0.84.4",
"metro-core": "0.84.4",
"metro-file-map": "0.84.4",
"metro-resolver": "0.84.4",
"metro-runtime": "0.84.4",
"metro-source-map": "0.84.4",
"metro-symbolicate": "0.84.4",
"metro-transform-plugins": "0.84.4",
"metro-transform-worker": "0.84.4",
"mime-types": "^3.0.1",
"nullthrows": "^1.1.1",
"serialize-error": "^2.1.0",
"source-map": "^0.5.6",
"throat": "^5.0.0",
"ws": "^7.5.10",
"yargs": "^17.6.2"
},
"bin": {
"metro": "src/cli.js"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-babel-transformer": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.4.tgz",
"integrity": "sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.25.2",
"flow-enums-runtime": "^0.0.6",
"hermes-parser": "0.35.0",
"metro-cache-key": "0.84.4",
"nullthrows": "^1.1.1"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-cache": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.4.tgz",
"integrity": "sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg==",
"license": "MIT",
"peer": true,
"dependencies": {
"exponential-backoff": "^3.1.1",
"flow-enums-runtime": "^0.0.6",
"https-proxy-agent": "^7.0.5",
"metro-core": "0.84.4"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-cache-key": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.4.tgz",
"integrity": "sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw==",
"license": "MIT",
"peer": true,
"dependencies": {
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-config": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.4.tgz",
"integrity": "sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"connect": "^3.6.5",
"flow-enums-runtime": "^0.0.6",
"jest-validate": "^29.7.0",
"metro": "0.84.4",
"metro-cache": "0.84.4",
"metro-core": "0.84.4",
"metro-runtime": "0.84.4",
"yaml": "^2.6.1"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-core": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.4.tgz",
"integrity": "sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"lodash.throttle": "^4.1.1",
"metro-resolver": "0.84.4"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-file-map": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.4.tgz",
"integrity": "sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "^4.4.0",
"fb-watchman": "^2.0.0",
"flow-enums-runtime": "^0.0.6",
"graceful-fs": "^4.2.4",
"invariant": "^2.2.4",
"jest-worker": "^29.7.0",
"micromatch": "^4.0.4",
"nullthrows": "^1.1.1",
"walker": "^1.0.7"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-minify-terser": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.4.tgz",
"integrity": "sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"terser": "^5.15.0"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-resolver": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.4.tgz",
"integrity": "sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A==",
"license": "MIT",
"peer": true,
"dependencies": {
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-runtime": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.4.tgz",
"integrity": "sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.25.0",
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-source-map": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.4.tgz",
"integrity": "sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/traverse": "^7.29.0",
"@babel/types": "^7.29.0",
"flow-enums-runtime": "^0.0.6",
"invariant": "^2.2.4",
"metro-symbolicate": "0.84.4",
"nullthrows": "^1.1.1",
"ob1": "0.84.4",
"source-map": "^0.5.6",
"vlq": "^1.0.0"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-symbolicate": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.4.tgz",
"integrity": "sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA==",
"license": "MIT",
"peer": true,
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"invariant": "^2.2.4",
"metro-source-map": "0.84.4",
"nullthrows": "^1.1.1",
"source-map": "^0.5.6",
"vlq": "^1.0.0"
},
"bin": {
"metro-symbolicate": "src/index.js"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-transform-plugins": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.4.tgz",
"integrity": "sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/generator": "^7.29.1",
"@babel/template": "^7.28.6",
"@babel/traverse": "^7.29.0",
"flow-enums-runtime": "^0.0.6",
"nullthrows": "^1.1.1"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/metro-transform-worker": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.4.tgz",
"integrity": "sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/generator": "^7.29.1",
"@babel/parser": "^7.29.0",
"@babel/types": "^7.29.0",
"flow-enums-runtime": "^0.0.6",
"metro": "0.84.4",
"metro-babel-transformer": "0.84.4",
"metro-cache": "0.84.4",
"metro-cache-key": "0.84.4",
"metro-minify-terser": "0.84.4",
"metro-source-map": "0.84.4",
"metro-transform-plugins": "0.84.4",
"nullthrows": "^1.1.1"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/mime-types": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
"peer": true,
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/@react-native/metro-config/node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/@react-native/metro-config/node_modules/ob1": {
"version": "0.84.4",
"resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.4.tgz",
"integrity": "sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA==",
"license": "MIT",
"peer": true,
"dependencies": {
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/metro-config/node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@react-native/metro-config/node_modules/ws": {
"version": "7.5.11",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz",
"integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@react-native/normalize-colors": {
"version": "0.81.6",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.81.6.tgz",
@@ -30808,19 +31390,18 @@
}
},
"node_modules/react-native-reanimated": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.6.tgz",
"integrity": "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.3.1.tgz",
"integrity": "sha512-KhGsS0YkCA+gusgyzlf9hnqzVPIR398KTpqXyqq/+yYJJPAvyEEPKcxlB0xtOOXSMrR2A9uRKVARVQhZwrOh+Q==",
"license": "MIT",
"dependencies": {
"react-native-is-edge-to-edge": "^1.2.1",
"semver": "7.7.2"
"react-native-is-edge-to-edge": "^1.3.1",
"semver": "^7.7.3"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0",
"react": "*",
"react-native": "*",
"react-native-worklets": ">=0.5.0"
"react-native": "0.81 - 0.85",
"react-native-worklets": "0.8.x"
}
},
"node_modules/react-native-reanimated/node_modules/semver": {
@@ -30962,27 +31543,28 @@
}
},
"node_modules/react-native-worklets": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz",
"integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==",
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.3.tgz",
"integrity": "sha512-oCBJROyLU7yG/1R8s0INMflygTH71bx+5XcYkH0CM938TlhSoVbiunE1WVW5FZa51vwYqfLie/IXMX2s1Kh3eg==",
"license": "MIT",
"dependencies": {
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
"@babel/plugin-transform-class-properties": "^7.0.0-0",
"@babel/plugin-transform-classes": "^7.0.0-0",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0",
"@babel/plugin-transform-optional-chaining": "^7.0.0-0",
"@babel/plugin-transform-shorthand-properties": "^7.0.0-0",
"@babel/plugin-transform-template-literals": "^7.0.0-0",
"@babel/plugin-transform-unicode-regex": "^7.0.0-0",
"@babel/preset-typescript": "^7.16.7",
"@babel/plugin-transform-arrow-functions": "^7.27.1",
"@babel/plugin-transform-class-properties": "^7.27.1",
"@babel/plugin-transform-classes": "^7.28.4",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
"@babel/plugin-transform-optional-chaining": "^7.27.1",
"@babel/plugin-transform-shorthand-properties": "^7.27.1",
"@babel/plugin-transform-template-literals": "^7.27.1",
"@babel/plugin-transform-unicode-regex": "^7.27.1",
"@babel/preset-typescript": "^7.27.1",
"convert-source-map": "^2.0.0",
"semver": "7.7.2"
"semver": "^7.7.3"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0",
"@babel/core": "*",
"@react-native/metro-config": "*",
"react": "*",
"react-native": "*"
"react-native": "0.81 - 0.85"
}
},
"node_modules/react-native-worklets/node_modules/semver": {
@@ -36303,7 +36885,7 @@
},
"packages/app": {
"name": "@getpaseo/app",
"version": "0.1.86",
"version": "0.1.87",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
@@ -36360,14 +36942,14 @@
"qrcode": "^1.5.4",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "^0.81.5",
"react-native": "0.81.5",
"react-native-draggable-flatlist": "^4.0.3",
"react-native-edge-to-edge": "^1.7.0",
"react-native-gesture-handler": "~2.28.0",
"react-native-keyboard-controller": "^1.19.2",
"react-native-markdown-display": "^7.0.2",
"react-native-nitro-modules": "0.35.5",
"react-native-reanimated": "~4.1.1",
"react-native-reanimated": "~4.3.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-svg": "^15.14.0",
@@ -36375,7 +36957,7 @@
"react-native-unistyles": "^3.2.4",
"react-native-web": "~0.21.0",
"react-native-webview": "^13.16.0",
"react-native-worklets": "0.5.1",
"react-native-worklets": "~0.8.3",
"tiny-invariant": "^1.3.3",
"use-sync-external-store": "^1.6.0",
"zod": "^3.23.8",
@@ -36528,12 +37110,12 @@
},
"packages/cli": {
"name": "@getpaseo/cli",
"version": "0.1.86",
"version": "0.1.87",
"dependencies": {
"@clack/prompts": "^1.0.0",
"@getpaseo/client": "0.1.86",
"@getpaseo/protocol": "0.1.86",
"@getpaseo/server": "0.1.86",
"@getpaseo/client": "0.1.87",
"@getpaseo/protocol": "0.1.87",
"@getpaseo/server": "0.1.87",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"mime-types": "^2.1.35",
@@ -36779,10 +37361,10 @@
},
"packages/client": {
"name": "@getpaseo/client",
"version": "0.1.86",
"version": "0.1.87",
"dependencies": {
"@getpaseo/protocol": "0.1.86",
"@getpaseo/relay": "0.1.86",
"@getpaseo/protocol": "0.1.87",
"@getpaseo/relay": "0.1.87",
"zod": "^3.23.8"
},
"devDependencies": {
@@ -36802,7 +37384,7 @@
},
"packages/desktop": {
"name": "@getpaseo/desktop",
"version": "0.1.86",
"version": "0.1.87",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@getpaseo/cli": "*",
@@ -37054,7 +37636,7 @@
},
"packages/expo-two-way-audio": {
"name": "@getpaseo/expo-two-way-audio",
"version": "0.1.86",
"version": "0.1.87",
"license": "MIT",
"devDependencies": {
"@types/react": "^18.0.25",
@@ -37090,7 +37672,7 @@
},
"packages/highlight": {
"name": "@getpaseo/highlight",
"version": "0.1.86",
"version": "0.1.87",
"dependencies": {
"@lezer/common": "^1.5.0",
"@lezer/cpp": "^1.1.5",
@@ -37319,7 +37901,7 @@
},
"packages/protocol": {
"name": "@getpaseo/protocol",
"version": "0.1.86",
"version": "0.1.87",
"dependencies": {
"zod": "^3.23.8"
},
@@ -37340,7 +37922,7 @@
},
"packages/relay": {
"name": "@getpaseo/relay",
"version": "0.1.86",
"version": "0.1.87",
"dependencies": {
"base64-js": "^1.5.1",
"tweetnacl": "^1.0.3",
@@ -37558,14 +38140,14 @@
},
"packages/server": {
"name": "@getpaseo/server",
"version": "0.1.86",
"version": "0.1.87",
"dependencies": {
"@agentclientprotocol/sdk": "^0.17.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.133",
"@getpaseo/client": "0.1.86",
"@getpaseo/highlight": "0.1.86",
"@getpaseo/protocol": "0.1.86",
"@getpaseo/relay": "0.1.86",
"@getpaseo/client": "0.1.87",
"@getpaseo/highlight": "0.1.87",
"@getpaseo/protocol": "0.1.87",
"@getpaseo/relay": "0.1.87",
"@isaacs/ttlcache": "^2.1.4",
"@modelcontextprotocol/sdk": "^1.20.1",
"@opencode-ai/sdk": "1.14.46",
@@ -38337,7 +38919,7 @@
},
"packages/website": {
"name": "@getpaseo/website",
"version": "0.1.86",
"version": "0.1.87",
"dependencies": {
"@cloudflare/vite-plugin": "^1.29.1",
"@cloudflare/workers-types": "^4.20260317.1",

View File

@@ -1,6 +1,6 @@
{
"name": "paseo",
"version": "0.1.86",
"version": "0.1.87",
"private": true,
"description": "Paseo: voice-controlled development environment with OpenAI Realtime API",
"keywords": [
@@ -121,6 +121,8 @@
"overrides": {
"lightningcss": "1.30.1",
"react": "19.1.0",
"react-dom": "19.1.0"
"react-dom": "19.1.0",
"react-native-reanimated": "4.3.1",
"react-native-worklets": "0.8.3"
}
}

View File

@@ -0,0 +1,95 @@
import { expect, test, type Page } from "./fixtures";
import { expectComposerVisible } from "./helpers/composer";
import { openAgentRoute, seedMockAgentWorkspace } from "./helpers/mock-agent";
const MOBILE_VIEWPORT = { width: 390, height: 844 };
async function openMockAgentAtMobileBreakpoint(page: Page) {
await page.setViewportSize(MOBILE_VIEWPORT);
const session = await seedMockAgentWorkspace({
repoPrefix: "bottom-sheet-reopen-",
title: "Bottom sheet reopen e2e",
initialPrompt: "Prepare a bottom sheet reopen test agent.",
});
await openAgentRoute(page, session);
await expect(page.getByTestId("workspace-tab-switcher-trigger")).toBeVisible({
timeout: 30_000,
});
await expectComposerVisible(page);
await expect(page.getByRole("button", { name: /Select model/ })).toBeVisible({
timeout: 30_000,
});
return session;
}
async function withMobileMockAgent(page: Page, run: () => Promise<void>) {
const session = await openMockAgentAtMobileBreakpoint(page);
try {
await run();
} finally {
await session.cleanup();
}
}
function bottomSheetBackdrop(page: Page) {
return page.getByRole("button", { name: "Bottom sheet backdrop" }).first();
}
async function expectBottomSheetOpen(page: Page) {
await expect(bottomSheetBackdrop(page)).toBeVisible({ timeout: 10_000 });
}
async function closeBottomSheetWithBackdrop(page: Page) {
const box = await bottomSheetBackdrop(page).boundingBox();
expect(box).not.toBeNull();
await page.mouse.click(box!.x + box!.width / 2, box!.y + 24);
await expect(bottomSheetBackdrop(page)).not.toBeVisible({ timeout: 10_000 });
// Guard against the regression where the sheet starts dismissing, then re-presents.
await page.waitForTimeout(500);
await expect(bottomSheetBackdrop(page)).not.toBeVisible({ timeout: 1_000 });
}
async function openTabSwitcher(page: Page) {
const trigger = page.getByRole("button", { name: /Switch tabs/ });
await trigger.click();
await expectBottomSheetOpen(page);
}
async function openModelSelector(page: Page) {
await page.getByRole("button", { name: /Select model/ }).click();
await expectBottomSheetOpen(page);
await expect(
page.getByLabel("Bottom Sheet", { exact: true }).getByText("Ten second stream", {
exact: true,
}),
).toBeVisible({ timeout: 10_000 });
}
async function openAndCloseTabSwitcherTwice(page: Page) {
await openTabSwitcher(page);
await closeBottomSheetWithBackdrop(page);
await openTabSwitcher(page);
await closeBottomSheetWithBackdrop(page);
}
async function openAndCloseModelSelectorTwice(page: Page) {
await openModelSelector(page);
await closeBottomSheetWithBackdrop(page);
await openModelSelector(page);
await closeBottomSheetWithBackdrop(page);
}
test.describe("mobile bottom sheet reopen", () => {
test("tab switcher can open, close, reopen, and close again", async ({ page }) => {
await withMobileMockAgent(page, async () => {
await openAndCloseTabSwitcherTwice(page);
});
});
test("model selector can open, close, reopen, and close again", async ({ page }) => {
await withMobileMockAgent(page, async () => {
await openAndCloseModelSelectorTwice(page);
});
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/app",
"version": "0.1.86",
"version": "0.1.87",
"private": true,
"main": "index.ts",
"scripts": {
@@ -85,14 +85,14 @@
"qrcode": "^1.5.4",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "^0.81.5",
"react-native": "0.81.5",
"react-native-draggable-flatlist": "^4.0.3",
"react-native-edge-to-edge": "^1.7.0",
"react-native-gesture-handler": "~2.28.0",
"react-native-keyboard-controller": "^1.19.2",
"react-native-markdown-display": "^7.0.2",
"react-native-nitro-modules": "0.35.5",
"react-native-reanimated": "~4.1.1",
"react-native-reanimated": "~4.3.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-svg": "^15.14.0",
@@ -100,7 +100,7 @@
"react-native-unistyles": "^3.2.4",
"react-native-web": "~0.21.0",
"react-native-webview": "^13.16.0",
"react-native-worklets": "0.5.1",
"react-native-worklets": "~0.8.3",
"tiny-invariant": "^1.3.3",
"use-sync-external-store": "^1.6.0",
"zod": "^3.23.8",

View File

@@ -118,4 +118,44 @@ describe("bottom sheet visibility tracker", () => {
tracker.handleSheetIndexChange(-1);
expect(closeCount()).toBe(2);
});
it("does not re-present when the controller reattaches before parent state acknowledges a user dismiss", () => {
const { sheet, tracker, closeCount } = setup();
tracker.attachController(sheet);
tracker.syncDesired({ visible: true });
tracker.handleSheetIndexChange(-1);
tracker.attachController(null);
tracker.attachController(sheet);
expect(closeCount()).toBe(1);
expect(sheet.events).toEqual([{ type: "present" }]);
});
it("does not re-present when dismiss fires before parent state acknowledges a user dismiss", () => {
const { sheet, tracker, closeCount } = setup();
tracker.attachController(sheet);
tracker.syncDesired({ visible: true });
tracker.handleSheetDismiss();
tracker.attachController(null);
tracker.attachController(sheet);
expect(closeCount()).toBe(1);
expect(sheet.events).toEqual([{ type: "present" }]);
});
it("allows a fresh open after parent state acknowledges a dismissed sheet", () => {
const { sheet, tracker } = setup();
tracker.attachController(sheet);
tracker.syncDesired({ visible: true });
tracker.handleSheetIndexChange(-1);
tracker.attachController(null);
tracker.attachController(sheet);
tracker.syncDesired({ visible: false });
tracker.syncDesired({ visible: true });
expect(sheet.events).toEqual([{ type: "present" }, { type: "present" }]);
});
});

View File

@@ -15,25 +15,27 @@ export interface BottomSheetVisibilityTracker {
handleSheetDismiss(): void;
}
type BottomSheetPhase = "closed" | "presenting" | "presented" | "dismissing";
export function createBottomSheetVisibilityTracker(opts: {
onClose: () => void;
}): BottomSheetVisibilityTracker {
let controller: BottomSheetController | null = null;
let visible = false;
let isEnabled: boolean | undefined;
let isPresented = false;
let phase: BottomSheetPhase = "closed";
let hasNotifiedClose = false;
function present(): void {
if (!controller || isPresented) return;
isPresented = true;
if (!controller || phase !== "closed") return;
phase = "presenting";
hasNotifiedClose = false;
controller.present();
}
function dismiss(): void {
if (!controller || !isPresented) return;
isPresented = false;
if (!controller || phase === "closed" || phase === "dismissing") return;
phase = "dismissing";
controller.dismiss();
}
@@ -58,19 +60,34 @@ export function createBottomSheetVisibilityTracker(opts: {
present();
return;
}
if (phase === "dismissing") {
phase = "closed";
hasNotifiedClose = false;
return;
}
dismiss();
},
handleSheetIndexChange(index) {
if (index === -1 && visible) {
if (index !== -1) {
if (phase === "presenting") {
phase = "presented";
}
return;
}
if (phase === "presenting" || phase === "presented") {
phase = "dismissing";
}
if (visible) {
notifyClose();
}
},
handleSheetDismiss() {
isPresented = false;
if (visible) {
phase = "dismissing";
notifyClose();
return;
}
phase = "closed";
hasNotifiedClose = false;
},
};

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/cli",
"version": "0.1.86",
"version": "0.1.87",
"description": "Paseo CLI - control your AI coding agents from the command line",
"bin": {
"paseo": "bin/paseo"
@@ -25,9 +25,9 @@
},
"dependencies": {
"@clack/prompts": "^1.0.0",
"@getpaseo/client": "0.1.86",
"@getpaseo/protocol": "0.1.86",
"@getpaseo/server": "0.1.86",
"@getpaseo/client": "0.1.87",
"@getpaseo/protocol": "0.1.87",
"@getpaseo/server": "0.1.87",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"mime-types": "^2.1.35",

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/client",
"version": "0.1.86",
"version": "0.1.87",
"description": "Paseo client SDK package",
"files": [
"dist",
@@ -33,8 +33,8 @@
"test": "vitest run"
},
"dependencies": {
"@getpaseo/protocol": "0.1.86",
"@getpaseo/relay": "0.1.86",
"@getpaseo/protocol": "0.1.87",
"@getpaseo/relay": "0.1.87",
"zod": "^3.23.8"
},
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/desktop",
"version": "0.1.86",
"version": "0.1.87",
"private": true,
"description": "Paseo desktop app (Electron wrapper)",
"homepage": "https://paseo.sh",

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/expo-two-way-audio",
"version": "0.1.86",
"version": "0.1.87",
"description": "Native module for two way audio streaming",
"keywords": [
"ExpoTwoWayAudio",

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/highlight",
"version": "0.1.86",
"version": "0.1.87",
"files": [
"dist",
"!dist/**/*.map"

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/protocol",
"version": "0.1.86",
"version": "0.1.87",
"description": "Paseo shared protocol schemas and wire types",
"files": [
"dist",

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/relay",
"version": "0.1.86",
"version": "0.1.87",
"description": "Paseo relay for bridging daemon and client connections",
"files": [
"dist",

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/server",
"version": "0.1.86",
"version": "0.1.87",
"description": "Paseo backend server",
"files": [
"dist/server",
@@ -57,10 +57,10 @@
"dependencies": {
"@agentclientprotocol/sdk": "^0.17.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.133",
"@getpaseo/client": "0.1.86",
"@getpaseo/highlight": "0.1.86",
"@getpaseo/protocol": "0.1.86",
"@getpaseo/relay": "0.1.86",
"@getpaseo/client": "0.1.87",
"@getpaseo/highlight": "0.1.87",
"@getpaseo/protocol": "0.1.87",
"@getpaseo/relay": "0.1.87",
"@isaacs/ttlcache": "^2.1.4",
"@modelcontextprotocol/sdk": "^1.20.1",
"@opencode-ai/sdk": "1.14.46",

View File

@@ -1954,6 +1954,15 @@ function getOpenCodeSubAgentMaps(state: OpenCodeEventTranslationState): {
};
}
function isOpenCodeSessionTrackedByParent(
sessionId: string,
state: OpenCodeEventTranslationState,
): boolean {
return (
sessionId === state.sessionId || state.subAgentCallIdByChildSessionId?.has(sessionId) === true
);
}
function getOpenCodeSubAgentState(
callId: string,
state: OpenCodeEventTranslationState,
@@ -2438,7 +2447,7 @@ function appendOpenCodePermissionAsked(
state: OpenCodeEventTranslationState,
events: AgentStreamEvent[],
): void {
if (event.properties.sessionID !== state.sessionId) {
if (!isOpenCodeSessionTrackedByParent(event.properties.sessionID, state)) {
return;
}
const metadata = readOpenCodeRecord(event.properties.metadata);

View File

@@ -387,6 +387,97 @@ describe("translateOpenCodeEvent", () => {
]);
});
it("forwards permission requests from linked OpenCode subagent sessions", () => {
const state = createState();
translateOpenCodeEvent(
{
type: "message.part.updated",
properties: {
part: {
id: "part-subagent",
sessionID: "session-1",
messageID: "message-1",
type: "tool",
tool: "task",
callID: "call-subagent",
state: {
status: "running",
input: {
subagent_type: "explore",
description: "Explore external config",
},
},
},
},
},
state,
);
translateOpenCodeEvent(
{
type: "session.created",
properties: {
sessionID: "child-session-1",
info: {
id: "child-session-1",
parentID: "session-1",
},
},
},
state,
);
const result = translateOpenCodeEvent(
{
type: "permission.asked",
properties: {
id: "perm-child-1",
sessionID: "child-session-1",
permission: "external_directory",
patterns: ["/Users/example/.config/nvim"],
metadata: {
reason: "Need to inspect the requested config directory",
},
},
},
state,
);
expect(result).toEqual([
{
type: "permission_requested",
provider: "opencode",
request: {
id: "perm-child-1",
provider: "opencode",
name: "external_directory",
kind: "tool",
title: "Access external directory",
description:
"Need to inspect the requested config directory - Scope: /Users/example/.config/nvim",
input: {
patterns: ["/Users/example/.config/nvim"],
metadata: {
reason: "Need to inspect the requested config directory",
},
},
detail: {
type: "unknown",
input: {
permission: "external_directory",
patterns: ["/Users/example/.config/nvim"],
metadata: {
reason: "Need to inspect the requested config directory",
},
},
output: null,
},
actions: openCodePermissionActions,
},
},
]);
});
it("emits usage_updated after step-finish parts", () => {
const state = createState();
state.accumulatedUsage.contextWindowMaxTokens = 400_000;

View File

@@ -1,6 +1,6 @@
{
"name": "@getpaseo/website",
"version": "0.1.86",
"version": "0.1.87",
"private": true,
"type": "module",
"scripts": {

View File

@@ -31,12 +31,6 @@ export function SiteFooter({ width = "default" }: SiteFooterProps) {
>
Changelog
</a>
<a
href="/cloud"
className="block text-muted-foreground hover:text-foreground transition-colors"
>
Cloud
</a>
<a
href="/docs/cli"
className="block text-muted-foreground hover:text-foreground transition-colors"

View File

@@ -28,12 +28,6 @@ export function SiteHeader() {
>
Changelog
</a>
<a
href="/cloud"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Cloud
</a>
<a
href="/download"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"