@buoy-gg/agent-core 7.0.44 → 7.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/lib/commonjs/catalog/catalog.g.js +2 -2
  2. package/lib/commonjs/catalog/catalog.source.json +148 -15
  3. package/lib/commonjs/catalog/catalog.types.g.js +1 -1
  4. package/lib/commonjs/catalog/normalizeParams.js +2 -2
  5. package/lib/commonjs/catalog/snapshotReads.js +1 -1
  6. package/lib/commonjs/context/buildContextPack.js +1 -1
  7. package/lib/commonjs/effects/ledger.js +1 -1
  8. package/lib/commonjs/effects/storeDiff.js +1 -0
  9. package/lib/commonjs/engine/compactScreen.js +1 -0
  10. package/lib/commonjs/engine/effectFor.js +1 -1
  11. package/lib/commonjs/engine/planOnlyStop.js +1 -0
  12. package/lib/commonjs/engine/runAgentTurn.js +7 -7
  13. package/lib/commonjs/engine/systemPrompt.js +19 -6
  14. package/lib/commonjs/engine/verify.js +1 -1
  15. package/lib/commonjs/policy/redact.js +1 -1
  16. package/lib/commonjs/providers/openai.js +3 -5
  17. package/lib/commonjs/providers/types.js +1 -1
  18. package/lib/module/catalog/catalog.g.js +2 -2
  19. package/lib/module/catalog/catalog.source.json +148 -15
  20. package/lib/module/catalog/catalog.types.g.js +1 -1
  21. package/lib/module/catalog/normalizeParams.js +1 -1
  22. package/lib/module/catalog/snapshotReads.js +1 -1
  23. package/lib/module/context/buildContextPack.js +1 -1
  24. package/lib/module/effects/ledger.js +1 -1
  25. package/lib/module/effects/storeDiff.js +1 -0
  26. package/lib/module/engine/compactScreen.js +1 -0
  27. package/lib/module/engine/effectFor.js +1 -1
  28. package/lib/module/engine/planOnlyStop.js +1 -0
  29. package/lib/module/engine/runAgentTurn.js +7 -7
  30. package/lib/module/engine/systemPrompt.js +19 -6
  31. package/lib/module/engine/verify.js +1 -1
  32. package/lib/module/policy/redact.js +1 -1
  33. package/lib/module/providers/openai.js +3 -5
  34. package/lib/module/providers/types.js +1 -1
  35. package/lib/typescript/catalog/catalog.g.d.ts +2 -2
  36. package/lib/typescript/catalog/catalog.types.g.d.ts +6 -6
  37. package/lib/typescript/effects/ledger.d.ts +13 -0
  38. package/lib/typescript/effects/storeDiff.d.ts +23 -0
  39. package/lib/typescript/engine/compactScreen.d.ts +18 -0
  40. package/lib/typescript/engine/planOnlyStop.d.ts +22 -0
  41. package/lib/typescript/engine/runAgentTurn.d.ts +1 -1
  42. package/lib/typescript/engine/verify.d.ts +3 -1
  43. package/lib/typescript/providers/types.d.ts +8 -0
  44. package/lib/web/index.mjs +55 -44
  45. package/package.json +1 -1
@@ -7,7 +7,7 @@
7
7
  {
8
8
  "action": "getSnapshot",
9
9
  "summary": "Read this build's environment: every EXPO_PUBLIC_-style variable it was compiled with, plus the app's required-env-var checks.",
10
- "description": "Returns `{ env: Record<string,string>, requiredEnvVars: [...] }`. This is the ONLY read env has \u2014 it exposes no other action. Values are baked into the JS bundle at build time and never change at runtime, so one read is good for the life of the app, and real secrets are not present (only EXPO_PUBLIC_-style vars exist in the RN runtime at all).",
10
+ "description": "Returns `{ env: Record<string,string>, requiredEnvVars: [...] }`. This is the ONLY read env has \u2014 it exposes no other action. Values are baked into the JS bundle at build time and never change at runtime, so one read is good for the life of the app, and real secrets are not present (only EXPO_PUBLIC_-style vars exist in the RN runtime at all). It also returns `checks`: the tool's own verdict for each required variable (required_present, required_missing, required_wrong_type, required_wrong_value). Answer \"is anything missing or wrong?\" from `checks`: every env value is a string, so a number-typed variable like \"1856\" passes.",
11
11
  "params": {
12
12
  "type": "object",
13
13
  "properties": {},
@@ -107,7 +107,7 @@
107
107
  },
108
108
  "effect": "read",
109
109
  "release": "works",
110
- "description": "Returns `{status, totalCaptured, shown, envelopes:[{id, at, origin, eventId, totalBytes, items:[{type, summary, spanCount, bytes}]}]}`, newest first. Item payloads stay on the device \u2014 the summary line is the one-line human read (error headline, transaction name, log count). `status` is \"attached\" when capture is live; \"searching\" right after launch; \"sdk-not-found\"/\"no-client\" when the app has no Sentry client, in which case nothing can ever appear here. Capture starts when Buoy mounts, so envelopes sent before that are absent \u2014 \"nothing was captured\", never \"nothing was sent\"."
110
+ "description": "Returns `{status, totalCaptured, shown, envelopes:[{id, at, origin, eventId, totalBytes, items:[{type, summary, spanCount, bytes}]}]}`, newest first. Item payloads stay on the device \u2014 the summary line is the one-line human read (error headline, transaction name, log count). `status` is \"attached\" when capture is live; \"searching\" right after launch; \"sdk-not-found\"/\"no-client\" when the app has no Sentry client, in which case nothing can ever appear here. Capture starts when Buoy mounts, so envelopes sent before that are absent \u2014 \"nothing was captured\", never \"nothing was sent\". It also returns `drops`: events the SDK discarded before sending, each with its `reason` (before_send = the app's own beforeSend returned null, sample_rate, event_processor, client_report\u2026) and a `detail` naming the event. Read it to answer \"why isn't this in Sentry?\"."
111
111
  },
112
112
  {
113
113
  "action": "clearEnvelopes",
@@ -130,6 +130,27 @@
130
130
  "title": "Jotai",
131
131
  "summary": "Reads and writes the app's Jotai atoms: list registered atoms with change counts and writability, fetch one atom's current value, fetch the real prev/next values behind one recorded change, wipe the change timeline, and set a writable atom. Reach for it when on-screen data disagrees with the API or a value looks stale/wrong and the app uses Jotai. Only atoms the app explicitly passed to watchAtoms()/watchDefaultStoreAtoms() exist here \u2014 coverage is opt-in, so an empty list means nothing was registered, not that the app has no state. For the HISTORY of atom changes use get_events with sources:['jotai']; this tool's snapshot deliberately ships value-free markers and you fetch values on demand.",
132
132
  "actions": [
133
+ {
134
+ "action": "getSnapshot",
135
+ "summary": "Read the Jotai change log: the app's recent atom changes, newest first, each with the atom and a preview of its new value.",
136
+ "description": "Returns `{ changes: [{ id, at, atom, changed, summary, value }], total, returned }` newest first. `value` is a short preview; getChangeDetail(id) has the full prevValue and nextValue. Pass `limit` (default 20) and `atom` to list one atom's changes.",
137
+ "params": {
138
+ "type": "object",
139
+ "properties": {
140
+ "limit": {
141
+ "type": "number",
142
+ "description": "Most-recent N changes. Default 20."
143
+ },
144
+ "atom": {
145
+ "type": "string",
146
+ "description": "Only changes to atoms whose label contains this text."
147
+ }
148
+ },
149
+ "additionalProperties": false
150
+ },
151
+ "effect": "read",
152
+ "release": "works"
153
+ },
133
154
  {
134
155
  "action": "listAtoms",
135
156
  "summary": "Compact reader for a remote driver: the registered atoms with light metadata. Each atom's value stays on the device unless `includeValues` is set, and `limit` caps how many are returned. `writable` says which can be set via setAtom. Each atom also carries `shape` \u2014 the item type of any list it holds \u2014 which is the cheap way to learn what an addition must look like.",
@@ -479,6 +500,27 @@
479
500
  "title": "Zustand",
480
501
  "summary": "Reads and writes the app's live Zustand stores: list registered stores with their top-level keys or full state, fetch one store's current state, fetch the real before/after trees for one recorded state change, and setState a store for time-travel/reset. Reach for it when on-screen data disagrees with the API, or when a QA user needs the app put into a specific state. The change TIMELINE (history over time) is better read via get_events sources:['zustand']; this tool is for CURRENT state plus on-demand detail. Everything here works in release builds \u2014 but setState defaults to replace:true, which wipes the store's action functions.",
481
502
  "actions": [
503
+ {
504
+ "action": "getSnapshot",
505
+ "summary": "Read the Zustand change log: the app's recent store changes, newest first, each with the store, the keys it changed and what it set. Answers \"what changed in my cart?\" and finds the change to undo.",
506
+ "description": "Returns `{ changes: [{ id, at, store, changed, summary, partial }], total, returned }` newest first. `partial` is what that setState sent, cut to 400 characters. For an undo, getChangeDetail(id) has that change's full prevState: write the keys it changed back from prevState with setState. Changes are recorded from app start, whether or not the Events tool is on. Pass `limit` (default 20) and `store` to list one store's changes.",
507
+ "params": {
508
+ "type": "object",
509
+ "properties": {
510
+ "limit": {
511
+ "type": "number",
512
+ "description": "Most-recent N changes. Default 20."
513
+ },
514
+ "store": {
515
+ "type": "string",
516
+ "description": "Only changes to stores whose name contains this text."
517
+ }
518
+ },
519
+ "additionalProperties": false
520
+ },
521
+ "effect": "read",
522
+ "release": "works"
523
+ },
482
524
  {
483
525
  "action": "listStores",
484
526
  "summary": "List every registered Zustand store with its name, change count, persistence, and (by default) just its top-level keys.",
@@ -642,6 +684,27 @@
642
684
  "title": "Redux",
643
685
  "summary": "Reads and drives the app's LIVE Redux store, plus the captured action log. Use getState for what is in the store right now (Redux is action-based, so current state is NOT in the action-log snapshot \u2014 that history comes from get_events sources:['redux']), dispatch to push a plain action into the running app, getActionDetail to pull one action's real prevState/nextState trees (snapshots ship markers, not trees), and clearEvents to wipe the recorded log. All four need a Redux store bound to Buoy; when none is, getState/dispatch answer available:false with a specific reason instead of failing generically.",
644
686
  "actions": [
687
+ {
688
+ "action": "getSnapshot",
689
+ "summary": "Read the Redux action log: the app's recent actions, newest first, each with its real type, payload and meta. Shows the action shapes this app really uses before you dispatch one.",
690
+ "description": "Returns `{ actions: [{ id, at, type, payload, meta, error, changed }], total, returned }` newest first. `payload`, `meta` and `error` are cut to 400 characters; getActionDetail(id) has the full action and its before/after state. Redux Toolkit names actions `<slice>/<reducer>`, and async thunks add `/pending`, `/fulfilled` or `/rejected` with the thunk's argument in `meta.arg`. Copy a real action's shape when you dispatch one. Pass `limit` (default 20) and `type` to list only actions whose type contains that text.",
691
+ "params": {
692
+ "type": "object",
693
+ "properties": {
694
+ "limit": {
695
+ "type": "number",
696
+ "description": "Most-recent N actions. Default 20."
697
+ },
698
+ "type": {
699
+ "type": "string",
700
+ "description": "Only actions whose type contains this text."
701
+ }
702
+ },
703
+ "additionalProperties": false
704
+ },
705
+ "effect": "read",
706
+ "release": "works"
707
+ },
645
708
  {
646
709
  "action": "getState",
647
710
  "summary": "Read the app's CURRENT Redux state \u2014 top-level slice names by default, full state tree with includeValues:true.",
@@ -716,13 +779,40 @@
716
779
  },
717
780
  "effect": "destructive",
718
781
  "release": "works",
719
- "description": "Sends the given plain action straight to store.dispatch, e.g. {\"action\":{\"type\":\"counter/increment\",\"payload\":1}} or {\"action\":{\"type\":\"auth/logout\"}}. (The MCP redux_dispatch tool takes flat {type, payload} and wraps it into this shape for you.) `action` is REQUIRED and must be a plain object with a string `type` \u2014 Redux itself throws on a missing/undefined type, and thunk functions cannot be sent over the wire. Returns {dispatched:true, type} on success, or {dispatched:false, available:false, reason:'no-react-redux'|'no-provider'|'not-instrumented'} when no store is bound \u2014 never claim a dispatch landed unless dispatched is true. Treat as destructive: this mutates the real app the user is looking at, and a type like auth/logout, cart/clear or a rehydrate action is irreversible from here \u2014 the adapter exposes no time travel (jumpToState is NOT a sync action). Confirm the exact action type with the user before dispatching anything that resets, clears, or logs out.",
782
+ "description": "Sends the given plain action straight to store.dispatch, e.g. {\"action\":{\"type\":\"counter/increment\",\"payload\":1}} or {\"action\":{\"type\":\"auth/logout\"}}. (The MCP redux_dispatch tool takes flat {type, payload} and wraps it into this shape for you.) `action` is REQUIRED and must be a plain object with a string `type` \u2014 Redux itself throws on a missing/undefined type, and thunk functions cannot be sent over the wire. Returns {dispatched:true, type} on success, or {dispatched:false, available:false, reason:'no-react-redux'|'no-provider'|'not-instrumented'} when no store is bound \u2014 never claim a dispatch landed unless dispatched is true. Treat as destructive: this mutates the real app the user is looking at, and a type like auth/logout, cart/clear or a rehydrate action is irreversible from here \u2014 the adapter exposes no time travel (jumpToState is NOT a sync action). Confirm the exact action type with the user before dispatching anything that resets, clears, or logs out. Use dispatch when the app's own action does what you want (copy its shape from getSnapshot). When no action you can see does it, such as removing one entry, use redux.setState on the value instead of guessing action names.",
720
783
  "requires": [
721
784
  "react-redux installed in the app",
722
785
  "a Redux <Provider> above <FloatingDevTools /> \u2014 or an explicit registerReduxStore(store)",
723
786
  "the app must actually handle the action type; an unknown type dispatches successfully and changes nothing"
724
787
  ]
725
788
  },
789
+ {
790
+ "action": "setState",
791
+ "summary": "Set or remove ONE value in the live Redux store by path, without knowing the app's action names.",
792
+ "description": "Changes the state directly through Buoy's time-travel reducer: Buoy copies the current state, sets `value` at `path` (or deletes it with `remove:true`) and jumps the store to the result. Screens update at once. `path` is dot-separated from the root, slice first: \"offers.added.<id>\" or \"cart.items.0.qty\". Returns `{ ok, path, previous }`; `previous` is what was there, so you can put it back. Reach for it when the app has no action you can see for the change (getSnapshot lists the real ones), like removing one added item. It needs the store to be wrapped by Buoy; otherwise it refuses and you dispatch the app's own action instead.",
793
+ "params": {
794
+ "type": "object",
795
+ "properties": {
796
+ "path": {
797
+ "type": "string",
798
+ "description": "Dot path from the root, slice first, e.g. \"offers.added.ab14\"."
799
+ },
800
+ "value": {
801
+ "description": "The new value. null is a real value."
802
+ },
803
+ "remove": {
804
+ "type": "boolean",
805
+ "description": "Delete the key at path instead of setting it."
806
+ }
807
+ },
808
+ "required": [
809
+ "path"
810
+ ],
811
+ "additionalProperties": false
812
+ },
813
+ "effect": "destructive",
814
+ "release": "works"
815
+ },
726
816
  {
727
817
  "action": "clearEvents",
728
818
  "summary": "Wipe the recorded Redux action log on the device (state trees and all). Irreversible.",
@@ -967,7 +1057,7 @@
967
1057
  },
968
1058
  "effect": "read",
969
1059
  "release": "works",
970
- "description": "Projects each live query to light fields: queryHash, queryKey, status (one of fresh/stale/fetching/error/inactive/paused/disabled, from getQueryStatusLabel), fetchStatus, isStale, observers, updatedAt (state.dataUpdatedAt), and error.message when present. Returns {queries, total, returned, includedData}. Sorted most-recently-updated first. Safe to call repeatedly \u2014 unlike the full dehydrated snapshot, the heavy cached data stays on the device by default. TWO TRAPS: (1) the adapter has NO default limit \u2014 omit it and you get every query in the cache; (2) includeData:true returns q.state.data RAW and UNCAPPED (no 16KB wire marker, no 8MB cap like getQueryData), so it can blow the wire/token budget on a big cache \u2014 prefer getQueryData for one query's payload. The queryHash of each row is the handle every other action takes. What is NOT here has not been fetched yet \u2014 the cache fills as the user visits screens \u2014 so treat a missing key as a screen to go to (route-events.navigate, then highlight-updates.waitFor, then read again), not as an absence to report. Every read here also returns `shape`: a one-line sketch of the value's type plus, for each list in it, what its items look like. It is tiny and does not grow with the data, so it survives the 24,000-character cut when the data itself does not \u2014 read it before writing, and match it exactly. A list whose `item` is missing is EMPTY, which means nothing in the running app knows what belongs in it; anything you add there cannot be checked and will be accepted as-is, so say so rather than inventing fields. Send `staleOnly:true` to see only stale queries.",
1060
+ "description": "Projects each live query to light fields: queryHash, queryKey, status (one of fresh/stale/fetching/error/inactive/paused/disabled, from getQueryStatusLabel), fetchStatus, isStale, observers, updatedAt (state.dataUpdatedAt), and error.message when present. Returns {queries, total, returned, includedData}. Sorted most-recently-updated first. Safe to call repeatedly \u2014 unlike the full dehydrated snapshot, the heavy cached data stays on the device by default. TWO TRAPS: (1) the adapter has NO default limit \u2014 omit it and you get every query in the cache; (2) includeData:true returns q.state.data RAW and UNCAPPED (no 16KB wire marker, no 8MB cap like getQueryData), so it can blow the wire/token budget on a big cache \u2014 prefer getQueryData for one query's payload. The queryHash of each row is the handle every other action takes. What is NOT here has not been fetched yet \u2014 the cache fills as the user visits screens \u2014 so treat a missing key as a screen to go to (route-events.navigate, then highlight-updates.waitFor, then read again), not as an absence to report. Every read here also returns `shape`: a one-line sketch of the value's type plus, for each list in it, what its items look like. It is tiny and does not grow with the data, so it survives the 24,000-character cut when the data itself does not \u2014 read it before writing, and match it exactly. A list whose `item` is missing is EMPTY, which means nothing in the running app knows what belongs in it; anything you add there cannot be checked and will be accepted as-is, so say so rather than inventing fields. Send `staleOnly:true` to see only stale queries. `refetchEveryMs` appears on a query a mounted screen refetches on a timer: any cache edit, error or loading pin on it is replaced within that many ms, so use a network override for anything that must last \u2014 and it answers \"is the app calling this API over and over?\".",
971
1061
  "requires": [
972
1062
  "QueryClientProvider above <FloatingDevTools/>",
973
1063
  "@tanstack/react-query v5"
@@ -1441,7 +1531,7 @@
1441
1531
  },
1442
1532
  "effect": "read",
1443
1533
  "release": "works",
1444
- "description": "Runs the device's own \"Copy Settings\" formatter over the unified store and returns {output: string, returned: number, totalAvailable: number, includedData: boolean, format: string}. The store is a 200-event ring, newest-first. Defaults are compact: includeEventData=false, so heavy raw payloads (network bodies, redux state trees, query data) stay on the device \u2014 pass settings.includeEventData=true only when you actually need them.\n\nTHREE traps a caller must know:\n(1) COLD READS ARE EMPTY. Sources are only subscribed while a consumer holds a watch on toolId \"events\" (the adapter's subscribe() calls ensureRemoteSourcesDefault) or the on-device Events modal is open. A bare call_action does NOT arm capture \u2014 the MCP SyncClient only watches on getSnapshot, never on callAction (packages/mcp/src/tools/packs/events.ts:80). If totalAvailable is 0, say \"nothing was being recorded\", not \"nothing happened\": arm it first (read the events snapshot, or call setEnabledSources), reproduce, then export.\n(2) limit IS APPLIED BEFORE FILTERING. The adapter does all.slice(0, limit) and only then does generateExport filter by settings.filterSources / filterMode (packages/events/src/sync/eventsSyncAdapter.ts:378-381 + eventExportFormatter.ts:616). Filtering to one source with limit:25 can produce an EMPTY output while `returned` still reports 25 \u2014 `returned` counts events handed to the formatter, not events in the output. When filtering by source, pass a large limit (or omit it, which uses the whole buffer).\n(3) filterSources takes GRANULAR EventSource values, not friendly names. \"storage\" matches nothing \u2014 use [\"storage-async\",\"storage-mmkv\"]; react-query is three values ([\"react-query\",\"react-query-query\",\"react-query-mutation\"]). The MCP get_events wrapper expands those aliases for you; a direct call_action does not.\n\nAn unrecognized preset name silently falls back to DEFAULT_COPY_SETTINGS instead of erroring. settings is shallow-merged OVER the preset. Swift captures network, storage-async, storage-mmkv, and route. React state and render sources are unavailable. Native export accepts format, includeEventData, includeSource, includeStatus, includeTitle, includeSubtitle, includeSummaryHeader, filterMode, filterSources, dataSizeThreshold, and timestampFormat; other settings are rejected. Native detail inherits source snapshot payload limits.",
1534
+ "description": "Runs the device's own \"Copy Settings\" formatter over the unified store and returns {output: string, returned: number, totalAvailable: number, includedData: boolean, format: string}. The store is a 200-event ring, newest-first. Defaults are compact: includeEventData=false, so heavy raw payloads (network bodies, redux state trees, query data) stay on the device \u2014 pass settings.includeEventData=true only when you actually need them.\n\nTHREE traps a caller must know:\n(1) COLD READS ARE EMPTY. Sources are only subscribed while a consumer holds a watch on toolId \"events\" (the adapter's subscribe() calls ensureRemoteSourcesDefault) or the on-device Events modal is open. A bare call_action does NOT arm capture \u2014 the MCP SyncClient only watches on getSnapshot, never on callAction (packages/mcp/src/tools/packs/events.ts:80). If totalAvailable is 0, say \"nothing was being recorded\", not \"nothing happened\": arm it first (read the events snapshot, or call setEnabledSources), reproduce, then export.\n(2) limit IS APPLIED BEFORE FILTERING. The adapter does all.slice(0, limit) and only then does generateExport filter by settings.filterSources / filterMode (packages/events/src/sync/eventsSyncAdapter.ts:378-381 + eventExportFormatter.ts:616). Filtering to one source with limit:25 can produce an EMPTY output while `returned` still reports 25 \u2014 `returned` counts events handed to the formatter, not events in the output. When filtering by source, pass a large limit (or omit it, which uses the whole buffer).\n(3) filterSources takes GRANULAR EventSource values, not friendly names. \"storage\" matches nothing \u2014 use [\"storage-async\",\"storage-mmkv\"]; react-query is three values ([\"react-query\",\"react-query-query\",\"react-query-mutation\"]). The MCP get_events wrapper expands those aliases for you; a direct call_action does not.\n\nAn unrecognized preset name silently falls back to DEFAULT_COPY_SETTINGS instead of erroring. settings is shallow-merged OVER the preset. Swift captures network, storage-async, storage-mmkv, and route. React state and render sources are unavailable. Native export accepts format, includeEventData, includeSource, includeStatus, includeTitle, includeSubtitle, includeSummaryHeader, filterMode, filterSources, dataSizeThreshold, and timestampFormat; other settings are rejected. Native detail inherits source snapshot payload limits. A source records only while it is enabled (setEnabledSources), so enabling one now cannot show what happened before. For storage writes, read storage getSnapshot instead \u2014 the storage tool keeps its own timeline.",
1445
1535
  "requires": [
1446
1536
  "@buoy-gg/events installed and auto-discovered by FloatingDevTools",
1447
1537
  "a live watch on toolId \"events\" (an events snapshot read, a desktop Events panel, or a prior setEnabledSources call) before the window you want recorded \u2014 otherwise the buffer is empty",
@@ -1843,7 +1933,7 @@
1843
1933
  },
1844
1934
  "effect": "write",
1845
1935
  "release": "noop",
1846
- "description": "Create (or replace by id) a rule that forces matching requests to return a chosen status/body, fail outright, or arrive late. Pick this over query.setQueryData when the change must survive a refetch or reload, or when the ask is about what the API returns; follow it with query.invalidate so the mounted screen refetches through the rule. To change ONE value of the real response send fromRequestId plus `bodyPath` and `bodyValue` (bodyPath:\"sprites.front_default\", or \"stats[stat.name=hp].base_stat\" for one row of a list) \u2014 there is no nesting to get wrong, which matters most here because you read that body through a wire that truncates at 24,000 characters. For several fields use `bodyPatch` with ONLY those fields; never paste a whole captured body back. For \"just the NEXT call\" / \"once, then let it work again\", send rule.times:1 \u2014 the rule auto-disables after one match. Without times it overrides EVERY matching request until deleted. Never fake one-shot by triggering the request yourself and deleting the rule: that spends the failure the user wanted to see.",
1936
+ "description": "Create (or replace by id) a rule that forces matching requests to return a chosen status/body, fail outright, or arrive late. Pick this over query.setQueryData when the change must survive a refetch or reload, or when the ask is about what the API returns; follow it with query.invalidate so the mounted screen refetches through the rule. To change ONE value of the real response send fromRequestId plus `bodyPath` and `bodyValue` (bodyPath:\"sprites.front_default\", or \"stats[stat.name=hp].base_stat\" for one row of a list) \u2014 there is no nesting to get wrong, which matters most here because you read that body through a wire that truncates at 24,000 characters. For several fields use `bodyPatch` with ONLY those fields; never paste a whole captured body back. For \"just the NEXT call\" / \"once, then let it work again\", send rule.times:1 \u2014 the rule auto-disables after one match. Without times it overrides EVERY matching request until deleted. Never fake one-shot by triggering the request yourself and deleting the rule: that spends the failure the user wanted to see. No captured request is needed: urlPattern + methods + status/body (or kind/delayMs) works for a call the app has not made yet \u2014 use fromRequestId only when you need the real response to edit.",
1847
1937
  "releaseNote": "packages/network/src/network/overrides/engine.ts:74 \u2014 overrideForRequest returns null when __DEV__ === false. The rule is still created, persisted and listed, and this action still returns ok:true, but it is NEVER applied to real traffic in a release build. Confirm with debugOverrides \u2192 engine.devFlag before telling anyone the override took effect.",
1848
1938
  "requires": [
1849
1939
  "a __DEV__ build for the rule to actually apply",
@@ -2711,7 +2801,7 @@
2711
2801
  "properties": {
2712
2802
  "token": {
2713
2803
  "type": "string",
2714
- "description": "Token id from getState's tokens[].id. Default: the most recently used token that has a readable expiry."
2804
+ "description": "Token id or fingerprint from getState's tokens[] (the fingerprint is easier to pass on). Default: the most recently used token that has a readable expiry."
2715
2805
  },
2716
2806
  "leadMs": {
2717
2807
  "type": "number",
@@ -2736,7 +2826,7 @@
2736
2826
  "properties": {
2737
2827
  "token": {
2738
2828
  "type": "string",
2739
- "description": "Token id from getState's tokens[].id. Default: the most recently used token that has a readable expiry."
2829
+ "description": "Token id or fingerprint from getState's tokens[] (the fingerprint is easier to pass on). Default: the most recently used token that has a readable expiry."
2740
2830
  },
2741
2831
  "status": {
2742
2832
  "type": "number",
@@ -2853,7 +2943,7 @@
2853
2943
  {
2854
2944
  "action": "memoryWarning",
2855
2945
  "summary": "Send a memory warning to the app's AppState 'memoryWarning' listeners.",
2856
- "description": "Starts a report. Android never sends this event to React Native, so on Android the report carries a note saying the listeners ran anyway. Returns the new state.",
2946
+ "description": "Starts a report. Android never sends this event to React Native, so on Android the report carries a note saying the listeners ran anyway. Returns the new state. What the app did about it often shows only in its own logs, not in the report: read console.getSnapshot right after.",
2857
2947
  "effect": "write",
2858
2948
  "release": "works",
2859
2949
  "params": {
@@ -3607,8 +3697,29 @@
3607
3697
  {
3608
3698
  "toolId": "storage",
3609
3699
  "title": "Storage",
3610
- "summary": "Read and write the app's persisted state across all three backends \u2014 AsyncStorage, every registered MMKV instance, and registered Expo SecureStore keys \u2014 plus a recorded timeline of storage writes with per-event undo/jump. Reach for this first when a value is wrong, stale, missing, or only broken after a restart/upgrade: the bad value is usually sitting in storage. Buoy's own devtool keys (@react_buoy*, @buoy*, buoy-*) are stripped from every read path, so results are app data only. Nothing here is __DEV__-gated \u2014 every action really runs in a release build.",
3700
+ "summary": "Read and write the app's persisted state across all three backends \u2014 AsyncStorage, every registered MMKV instance, and registered Expo SecureStore keys \u2014 plus a recorded timeline of storage writes with per-event undo/jump. Reach for this first when a value is wrong, stale, missing, or only broken after a restart/upgrade: the bad value is usually sitting in storage. Buoy's own devtool keys (@react_buoy*, @buoy*, buoy-*) are stripped from every read path, so results are app data only. Nothing here is __DEV__-gated \u2014 every action really runs in a release build. When a saved value looks wrong, compare it with getRequiredKeys: a key written by an older app version shows up there with the wrong type (a bare \"1856\" where the app expects an object). The storage write timeline is this tool's getSnapshot (newest events, each with its key, value and the value it replaced) \u2014 read it for \"what changed in storage?\" and to find the event to undo. Do not use the Events tool for that: it records a source only after setEnabledSources, so earlier writes are not there.",
3611
3701
  "actions": [
3702
+ {
3703
+ "action": "getSnapshot",
3704
+ "summary": "Read the storage write timeline: the app's recent AsyncStorage and MMKV writes, newest first, each with its key, the value written and the value it replaced. Answers \"what changed in storage?\" and finds the event to undo.",
3705
+ "description": "Returns `{ events: [{ id, at, action, storage, key, value, prevValue }], total, returned }` newest first. Values are cut to 300 characters (read the key for the full value). `id` is what timeTravel.undo and timeTravel.jump take. Recording starts when something first watches the storage tool, so writes before that are not listed. Pass `limit` for how many writes (default 20) and `key` to list only writes whose key contains that text.",
3706
+ "params": {
3707
+ "type": "object",
3708
+ "properties": {
3709
+ "limit": {
3710
+ "type": "number",
3711
+ "description": "Most-recent N writes. Default 20."
3712
+ },
3713
+ "key": {
3714
+ "type": "string",
3715
+ "description": "Only writes whose key contains this text."
3716
+ }
3717
+ },
3718
+ "additionalProperties": false
3719
+ },
3720
+ "effect": "read",
3721
+ "release": "works"
3722
+ },
3612
3723
  {
3613
3724
  "action": "getRequiredKeys",
3614
3725
  "summary": "List the storage keys the app declares as required, with their expected types and backends.",
@@ -4029,7 +4140,7 @@
4029
4140
  },
4030
4141
  "effect": "read",
4031
4142
  "release": "works",
4032
- "description": "Returns [{key, description, keychainService, requireAuthentication}]. SecureStore has no key-enumeration API, so only keys the app declared via registerSecureStoreKeys(...) are visible \u2014 an empty [] means nothing was registered, NOT that the keychain is empty. requireAuthentication:true marks a biometric-protected key whose value Buoy never reads.",
4143
+ "description": "Returns [{key, description, keychainService, requireAuthentication}]. SecureStore has no key-enumeration API, so only keys the app declared via registerSecureStoreKeys(...) are visible \u2014 an empty [] means nothing was registered, NOT that the keychain is empty. requireAuthentication:true marks a biometric-protected key whose value Buoy never reads. Each key also has `hasValue` (true / false; null for a biometric-protected key, which is never read) \u2014 so you can tell an empty key from a set one, or see that the login moved from one key to another, without the value ever leaving the device.",
4033
4144
  "requires": [
4034
4145
  "registerSecureStoreKeys(SecureStore, [...]) called by the app (expo-secure-store)"
4035
4146
  ]
@@ -6190,7 +6301,7 @@
6190
6301
  {
6191
6302
  "toolId": "ask-buoy",
6192
6303
  "title": "Ask Buoy",
6193
- "summary": "Ask Buoy's OWN session \u2014 the changes it has made in this conversation, and the undo for them. Call listChanges when the user asks \"what did you change?\"; call undoAll when they say \"undo that\" / \"put it back\" / \"clean up\". Undo restores exactly what the ledger captured at write time: override rules Ask Buoy created are deleted, impersonation is stopped, storage keys are restored to their pre-write values, and a query cache edit (setQueryData) is put back to the data read just before the write \u2014 unless the app has refetched it since, in which case the server's data is already back and undo says so. Changes listed with reversible:false (state writes, wipes, one-shot actions) cannot be automatically reversed \u2014 say so honestly. NEVER improvise a reverse-write with a guessed shape instead of calling undoAll; a hand-rolled \"undo\" that writes invented data is worse than telling the user a change is permanent. Its retrieve action re-reads any earlier tool result in FULL: every result over 24,000 characters is cut with a `[truncated \u2014 \u2026; ref ev_N]` marker and every result compressed out of memory leaves an `[earlier result \u2014 \u2026; ref ev_N]` marker \u2014 call retrieve with that ref (and a path or pattern) instead of asking the user for a narrower slice or calling the tool again for a value you already had.",
6304
+ "summary": "Ask Buoy's OWN session \u2014 the changes it has made in this conversation, and the undo for them. Call listChanges when the user asks \"what did you change?\"; call undoChange with the id of the change they mean when they say \"undo that\" about one change, and undoAll when they say \"undo everything\" / \"put it all back\" / \"clean up\". Undo restores exactly what the ledger captured at write time: override rules Ask Buoy created are deleted, impersonation is stopped, storage keys are restored to their pre-write values, a query cache edit (setQueryData) is put back to the data read just before the write \u2014 unless the app has refetched it since, in which case the server's data is already back and undo says so \u2014 and a store edit (zustand.setState) has exactly the values it changed put back, unless something changed those same values again since. To undo ONE change and keep the rest, call listChanges and pass that change's id to undoChange. Changes listed with reversible:false (state writes, wipes, one-shot actions) cannot be automatically reversed \u2014 say so honestly. NEVER improvise a reverse-write with a guessed shape instead of calling undoAll; a hand-rolled \"undo\" that writes invented data is worse than telling the user a change is permanent. Its retrieve action re-reads any earlier tool result in FULL: every result over 24,000 characters is cut with a `[truncated \u2014 \u2026; ref ev_N]` marker and every result compressed out of memory leaves an `[earlier result \u2014 \u2026; ref ev_N]` marker \u2014 call retrieve with that ref (and a path or pattern) instead of asking the user for a narrower slice or calling the tool again for a value you already had.",
6194
6305
  "actions": [
6195
6306
  {
6196
6307
  "action": "listChanges",
@@ -6203,11 +6314,33 @@
6203
6314
  },
6204
6315
  "effect": "read",
6205
6316
  "release": "works",
6206
- "description": "Returns `{changes:[{toolId, action, kind, label, reversible}], returned}` \u2014 outstanding (not yet undone) changes only, oldest first. `reversible:true` means undoAll can restore that change exactly. `kind` \"transient\" is a one-shot action (a navigation, a tap) that changed no persistent state."
6317
+ "description": "Returns `{changes:[{id, toolId, action, kind, label, reversible}], returned}` \u2014 outstanding (not yet undone) changes only, oldest first. `reversible:true` means undoChange (with that `id`) or undoAll can restore that change exactly. `kind` \"transient\" is a one-shot action (a navigation, a tap) that changed no persistent state."
6318
+ },
6319
+ {
6320
+ "action": "undoChange",
6321
+ "summary": "Undo ONE change Ask Buoy made this conversation, by the id listChanges gave it. Use it for \"undo that\" when the user means a single change (usually the latest one) and other changes should stay.",
6322
+ "params": {
6323
+ "type": "object",
6324
+ "properties": {
6325
+ "id": {
6326
+ "type": "string",
6327
+ "description": "The change's id from listChanges, exactly as listChanges returned it, e.g. \"fx-3-1759330000000\"."
6328
+ }
6329
+ },
6330
+ "required": [
6331
+ "id"
6332
+ ],
6333
+ "additionalProperties": false,
6334
+ "description": "`{id}` from listChanges."
6335
+ },
6336
+ "effect": "write",
6337
+ "release": "works",
6338
+ "description": "Returns `{ok:true, undone:{id, label}}`, or `{ok:false, error}` when the id is unknown, already undone, or the change can't be put back (it is permanent, or the same value was changed again since \u2014 the error says which). Report a refusal honestly; do not write the old value back by hand over a newer one.",
6339
+ "servedBy": "engine"
6207
6340
  },
6208
6341
  {
6209
6342
  "action": "undoAll",
6210
- "summary": "Undo every reversible change Ask Buoy made this conversation \u2014 deletes override rules it created, stops impersonation, restores storage keys to their prior values. The safe direction: use it whenever the user asks to undo or clean up.",
6343
+ "summary": "Undo every reversible change Ask Buoy made this conversation \u2014 deletes override rules it created, stops impersonation, restores storage keys, cache edits and store edits to their prior values. Use it when the user asks to undo everything or clean up; for one change use undoChange.",
6211
6344
  "params": {
6212
6345
  "type": "object",
6213
6346
  "properties": {},
@@ -6216,7 +6349,7 @@
6216
6349
  },
6217
6350
  "effect": "write",
6218
6351
  "release": "works",
6219
- "description": "Returns `{ok, reverted, failed:[{label, error}], skippedTransients, permanent}`. Report the numbers honestly: `failed` entries were attempted and could not be restored (tell the user which, using the labels); `permanent` is how many changes were never undoable (state writes, refetches) and are still applied \u2014 say so, they are not failures; `skippedTransients` are one-shot actions that never needed undoing. This undoes ALL reversible changes from this conversation, newest first \u2014 there is no per-change undo action, so if the user wants to keep one change, say so instead of calling this."
6352
+ "description": "Returns `{ok, reverted, failed:[{label, error}], skippedTransients, permanent}`. Report the numbers honestly: `failed` entries were attempted and could not be restored (tell the user which, using the labels); `permanent` is how many changes were never undoable (state writes, refetches) and are still applied \u2014 say so, they are not failures; `skippedTransients` are one-shot actions that never needed undoing. This undoes ALL reversible changes from this conversation, newest first. To undo one change and keep the others, use undoChange instead."
6220
6353
  },
6221
6354
  {
6222
6355
  "action": "retrieve",
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.CATALOG_ACTIONS=void 0;const CATALOG_ACTIONS=exports.CATALOG_ACTIONS={"env":["getSnapshot"],"console":["getSnapshot","clearEntries"],"sentry":["getSnapshot","clearEnvelopes"],"jotai":["listAtoms","getAtomValue","getChangeDetail","setAtom","clearEvents"],"route-events":["getSnapshot","getCurrentRoute","navigate","stackGoBack","stackNavigateToIndex","stackPopToIndex","stackPopToTop","clearEvents"],"debug-borders":["cycleMode","setMode"],"zustand":["listStores","getStoreState","getChangeDetail","setState","rehydrate","clearEvents"],"redux":["getState","getActionDetail","dispatch","clearEvents"],"impersonate":["searchUsers","startImpersonation","stopImpersonation","pauseImpersonation","resumeImpersonation","updateSettings","removeFromHistory","clearHistory"],"query":["listQueries","getQueryData","refetch","invalidate","reset","remove","setQueryData","triggerError","restoreError","triggerLoading","restoreLoading","clearQueryCache","clearMutationCache","setOnline"],"events":["exportEvents","setEnabledSources","clearEvents"],"network":["getSnapshot","getCaptureStatus","getEventBody","setPinned","setSaved","removeSavedRecord","clearSavedRequests","clearPinnedRequests","clearEvents","listOverrideRules","getOverrideRuleBody","debugOverrides","upsertOverrideRule","setOverrideRuleEnabled","setOverridesEnabled","deleteOverrideRule","clearOverrideRules","getNetworkConditions","setNetworkConditions"],"js-top":["sample","getOriginDetail","setEnabled","pause","resume","clear"],"app":["ping","reloadApp"],"time-machine":["list","capture","captureBaseline","wipeAll","restore","preview","inspect","delete","rename","duplicate","setExclusions","setScope","setRestoreRoute"],"clock":["getState","setTime","shift","freeze","resume","setRate","reset","updateSettings","jumpToTokenExpiry","failNextRequest","stopTokenCheck"],"lifecycle":["getState","background","interrupt","returnToApp","memoryWarning","openUrl","pressBack","setColorScheme","setPower","resetPower","relaunch","runRecipe","clearReport","reset"],"location":["getState","setLocation","startRoute","pauseRoute","resumeRoute","stopRoute","seekRoute","setSpeed","enterRegion","exitRegion","setConditions","setSignal","setServices","tune","useRealLocation","reset","updateSettings","clearLog"],"permissions":["getState","setOverride","clearOverride","resetAll","requestReal","updateSettings","simulateReturnFromSettings","clearLog"],"storage":["getRequiredKeys","async.getAllKeys","async.multiGet","async.getItem","async.setItem","async.removeItem","async.multiRemove","async.multiSet","async.clear","clearAppStorage","getEventDetail","clearEvents","timeTravel.undo","timeTravel.jump","mmkv.snapshot","mmkv.get","mmkv.set","mmkv.remove","secure.keys","secure.snapshot","secure.get","secure.set","secure.delete"],"highlight-updates":["describeScreen","tapElement","waitFor","beginMeasurement","endMeasurement","locateComponent","getRenderDetail","setEnabled","toggle","setSilentTracking","toggleFreeze","setSpotlight","clearRenderCounts","startTouchCapture","stopTouchCapture","clearTouchCapture","readTouchCapture"],"scenarios":["listScenarios","getScenario","save","acceptDraft","discardDraft","preview","run","deactivate","delete","setFolder","listFolders","getActive","export"],"perf-monitor":["setEnabled","startRecording","stopRecording","savePending","discardPending","mark","startAutomation","cancelAutomation","acknowledgeAutomation","refreshIndex","getAutomationConfig","setAutomationConfig","loadReport","deleteReport","deleteBatch","clearAll"],"assets":["list","getDetail","rescan","measureSizes","saveBaseline","clearBaseline","getDiff","clearRecords","getScanStatus"],"tv-remote":["arm","getEventsSince","setMenuCapture","disarm","clear"],"focus-inspector":["rescan","focusElement","setTracking","clearHistory"],"images":["list","getDetail","getCaptureStatus","retry","flash","proveSavings","setOverride","clearOverride","setNetworkMode","setBlankImages","hardReload","evictDisk","massAction","clearRecords","clearExpoCaches"],"ask-buoy":["listChanges","undoAll","retrieve","openProcedure"],"push-notifications":["getSnapshot","getCapabilities","getEvent","getPermissions","listPresented","refreshToken","setCaptureSession","clearCapturedEvents","scheduleLocal"],"image-overlay":["getSnapshot","listTargets","selectTarget","loadImage","setSettings","fitToScreen","resetSettings","remove"]};
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.CATALOG_ACTIONS=void 0;const CATALOG_ACTIONS=exports.CATALOG_ACTIONS={"env":["getSnapshot"],"console":["getSnapshot","clearEntries"],"sentry":["getSnapshot","clearEnvelopes"],"jotai":["getSnapshot","listAtoms","getAtomValue","getChangeDetail","setAtom","clearEvents"],"route-events":["getSnapshot","getCurrentRoute","navigate","stackGoBack","stackNavigateToIndex","stackPopToIndex","stackPopToTop","clearEvents"],"debug-borders":["cycleMode","setMode"],"zustand":["getSnapshot","listStores","getStoreState","getChangeDetail","setState","rehydrate","clearEvents"],"redux":["getSnapshot","getState","getActionDetail","dispatch","setState","clearEvents"],"impersonate":["searchUsers","startImpersonation","stopImpersonation","pauseImpersonation","resumeImpersonation","updateSettings","removeFromHistory","clearHistory"],"query":["listQueries","getQueryData","refetch","invalidate","reset","remove","setQueryData","triggerError","restoreError","triggerLoading","restoreLoading","clearQueryCache","clearMutationCache","setOnline"],"events":["exportEvents","setEnabledSources","clearEvents"],"network":["getSnapshot","getCaptureStatus","getEventBody","setPinned","setSaved","removeSavedRecord","clearSavedRequests","clearPinnedRequests","clearEvents","listOverrideRules","getOverrideRuleBody","debugOverrides","upsertOverrideRule","setOverrideRuleEnabled","setOverridesEnabled","deleteOverrideRule","clearOverrideRules","getNetworkConditions","setNetworkConditions"],"js-top":["sample","getOriginDetail","setEnabled","pause","resume","clear"],"app":["ping","reloadApp"],"time-machine":["list","capture","captureBaseline","wipeAll","restore","preview","inspect","delete","rename","duplicate","setExclusions","setScope","setRestoreRoute"],"clock":["getState","setTime","shift","freeze","resume","setRate","reset","updateSettings","jumpToTokenExpiry","failNextRequest","stopTokenCheck"],"lifecycle":["getState","background","interrupt","returnToApp","memoryWarning","openUrl","pressBack","setColorScheme","setPower","resetPower","relaunch","runRecipe","clearReport","reset"],"location":["getState","setLocation","startRoute","pauseRoute","resumeRoute","stopRoute","seekRoute","setSpeed","enterRegion","exitRegion","setConditions","setSignal","setServices","tune","useRealLocation","reset","updateSettings","clearLog"],"permissions":["getState","setOverride","clearOverride","resetAll","requestReal","updateSettings","simulateReturnFromSettings","clearLog"],"storage":["getSnapshot","getRequiredKeys","async.getAllKeys","async.multiGet","async.getItem","async.setItem","async.removeItem","async.multiRemove","async.multiSet","async.clear","clearAppStorage","getEventDetail","clearEvents","timeTravel.undo","timeTravel.jump","mmkv.snapshot","mmkv.get","mmkv.set","mmkv.remove","secure.keys","secure.snapshot","secure.get","secure.set","secure.delete"],"highlight-updates":["describeScreen","tapElement","waitFor","beginMeasurement","endMeasurement","locateComponent","getRenderDetail","setEnabled","toggle","setSilentTracking","toggleFreeze","setSpotlight","clearRenderCounts","startTouchCapture","stopTouchCapture","clearTouchCapture","readTouchCapture"],"scenarios":["listScenarios","getScenario","save","acceptDraft","discardDraft","preview","run","deactivate","delete","setFolder","listFolders","getActive","export"],"perf-monitor":["setEnabled","startRecording","stopRecording","savePending","discardPending","mark","startAutomation","cancelAutomation","acknowledgeAutomation","refreshIndex","getAutomationConfig","setAutomationConfig","loadReport","deleteReport","deleteBatch","clearAll"],"assets":["list","getDetail","rescan","measureSizes","saveBaseline","clearBaseline","getDiff","clearRecords","getScanStatus"],"tv-remote":["arm","getEventsSince","setMenuCapture","disarm","clear"],"focus-inspector":["rescan","focusElement","setTracking","clearHistory"],"images":["list","getDetail","getCaptureStatus","retry","flash","proveSavings","setOverride","clearOverride","setNetworkMode","setBlankImages","hardReload","evictDisk","massAction","clearRecords","clearExpoCaches"],"ask-buoy":["listChanges","undoChange","undoAll","retrieve","openProcedure"],"push-notifications":["getSnapshot","getCapabilities","getEvent","getPermissions","listPresented","refreshToken","setCaptureSession","clearCapturedEvents","scheduleLocal"],"image-overlay":["getSnapshot","listTargets","selectTarget","loadImage","setSettings","fitToScreen","resetSettings","remove"]};
@@ -1,3 +1,3 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.aliasNote=aliasNote;exports.hashQueryKey=hashQueryKey;exports.normalizeParams=normalizeParams;const isPlainObject=r=>!!r&&typeof r==="object"&&!Array.isArray(r);function hashQueryKey(r){return JSON.stringify(r,(e,t)=>isPlainObject(t)?Object.keys(t).sort().reduce((a,i)=>{a[i]=t[i];return a},{}):t)}function alias(r,e,t,a){if(r[t]!==void 0||r[e]===void 0)return false;r[t]=r[e];delete r[e];a.push(`Accepted \`${e}\` as \`${t}\` \u2014 the parameter is named \`${t}\`.`);return true}const QUERY_HASH_ACTIONS=new Set(["getQueryData","refetch","invalidate","reset","remove","triggerError","restoreError","triggerLoading","restoreLoading"]);const RULE_FIELDS=["id","urlPattern","enabled","name","methods","kind","status","statusText","headers","body","bodyPatch","bodyPath","bodyValue","failKind","delayMs","times","alternate","force","bodyOmitted"];function normalizeQuery(r,e,t){if(QUERY_HASH_ACTIONS.has(r)&&e.queryHash===void 0){const a=isPlainObject(e.filters)?e.filters:void 0;const i=e.queryKey??e.key??e.hash??a?.queryKey??a?.queryHash;if(Array.isArray(i)){e.queryHash=hashQueryKey(i);t.push(`Accepted the queryKey array as the query's hash (${e.queryHash}) \u2014 the parameter is \`queryHash\`, the string from listQueries.`)}else if(typeof i==="string"){e.queryHash=i;t.push(`Accepted it as \`queryHash\` \u2014 that is the parameter name.`)}delete e.queryKey;delete e.key;delete e.hash;delete e.filters}if(r==="setQueryData"){if(isPlainObject(e.data)){for(const a of["merge","force"]){if(typeof e.data[a]==="boolean"&&e[a]===void 0){e[a]=e.data[a];delete e.data[a];t.push(`Moved \`${a}\` out of \`data\` to the top level \u2014 it is a write flag, not part of the value.`)}}}alias(e,"newData","data",t)||e.path===void 0&&alias(e,"value","data",t)||alias(e,"patch","data",t);if(e.queryKey===void 0&&typeof e.queryHash==="string"){try{const a=JSON.parse(e.queryHash);if(Array.isArray(a)){e.queryKey=a;t.push("Accepted the queryHash as the key \u2014 setQueryData takes `queryKey`, the array.")}}catch{}}if(e.patch!==void 0||e.merge===void 0){if(t.some(a=>a.includes("`patch`")))e.merge=true}}}function normalizeNetwork(r,e,t){if(r==="getEventBody"||r==="setPinned"||r==="setSaved"){alias(e,"requestId","id",t)||alias(e,"eventId","id",t)}if(r==="deleteOverrideRule"||r==="setOverrideRuleEnabled"||r==="getOverrideRuleBody"){alias(e,"ruleId","id",t)}if(r==="upsertOverrideRule"){const a=isPlainObject(e.rule)?{...e.rule}:{};if(e.fromRequestId===void 0&&typeof a.fromRequestId==="string"){e.fromRequestId=a.fromRequestId;t.push("Accepted `rule.fromRequestId` \u2014 it is a top-level parameter, beside `rule`.")}delete a.fromRequestId;unwrapRuleSections(a,t);ruleIntentWords(e,a,t);const i=[];for(const d of RULE_FIELDS){if(e[d]!==void 0&&a[d]===void 0){a[d]=e[d];i.push(d)}delete e[d]}if(i.length)t.push(`Accepted ${i.map(d=>`\`${d}\``).join(", ")} at the top level \u2014 rule fields belong inside \`rule\`.`);if(Object.keys(a).length)e.rule=a}}function unwrapRuleSections(r,e){for(const[t,a]of[["match",["urlPattern","methods","method","url","pattern","urlContains","contains"]],["response",["status","statusText","body","headers","bodyPatch"]]]){const i=r[t];if(!isPlainObject(i))continue;const d=[];for(const s of a){if(i[s]===void 0)continue;const n=s==="method"?"methods":s==="url"||s==="pattern"||s==="urlContains"||s==="contains"?"urlPattern":s;if(r[n]!==void 0)continue;r[n]=n==="methods"&&!Array.isArray(i[s])?[i[s]]:i[s];delete i[s];d.push(s)}if(!d.length)continue;if(!Object.keys(i).length)delete r[t];e.push(`Read \`rule.${t}.{${d.join(", ")}}\` as top-level rule fields \u2014 this rule is flat, unlike msw/nock-style mocks.`)}}function ruleIntentWords(r,e,t){if(e.bodyPath===void 0&&typeof e.path==="string"){e.bodyPath=e.path;if(e.bodyValue===void 0&&e.value!==void 0)e.bodyValue=e.value;delete e.path;delete e.value;t.push("Read `path`/`value` as `bodyPath`/`bodyValue` \u2014 on a rule they address the response body.")}const a=s=>e[s]!==void 0?e[s]:r[s];const i=s=>{delete e[s];delete r[s]};const d=a("mode");if(typeof d==="string"&&["respond","fail","delay"].includes(d)&&e.kind===void 0){e.kind=d;t.push(`Read \`mode: "${d}"\` as \`kind: "${d}"\`.`)}i("mode");if(a("fail")===true&&e.kind===void 0){e.kind="fail";t.push('Read `fail: true` as `kind: "fail"` \u2014 a transport failure, as if offline.')}i("fail");if(a("failOnce")===true){if(e.kind===void 0)e.kind="fail";if(e.times===void 0)e.times=1;t.push('Read `failOnce: true` as `kind: "fail"` with `times: 1` \u2014 it fires once, then the rule disables itself.')}i("failOnce");if(a("once")===true&&e.times===void 0){e.times=1;t.push("Read `once: true` as `times: 1` \u2014 the rule auto-disables after one match.")}i("once");for(const s of["maxHits","hits","count","maxTimes","applyTimes"]){const n=a(s);if(typeof n==="number"&&Number.isFinite(n)&&n>0&&e.times===void 0){e.times=n;t.push(`Read \`${s}: ${n}\` as \`times: ${n}\` \u2014 the rule auto-disables after ${n} match(es).`)}i(s)}}function normalizeZustand(r,e,t){alias(e,"name","storeName",t)||alias(e,"store","storeName",t);if(r==="setState"){alias(e,"statePatch","state",t)||alias(e,"partial","state",t)||alias(e,"patch","state",t)||alias(e,"newState","state",t)||alias(e,"nextState","state",t)||e.path===void 0&&alias(e,"value","state",t);if(e.replace===void 0){e.replace=false;t.push("Applied replace:false (merge) \u2014 a store keeps action functions that can't be re-sent, and replacing would wipe them (the buttons that call them would then crash). Send replace:true only to deliberately reset the whole store.")}}}function normalizeRoutes(r,e,t){if(r==="navigate"){alias(e,"route","path",t)||alias(e,"to","path",t)||alias(e,"url","path",t)||alias(e,"pathname","path",t)}}function normalizeParams(r,e,t){const a={...t};const i=[];switch(r){case"query":normalizeQuery(e,a,i);break;case"network":normalizeNetwork(e,a,i);break;case"zustand":normalizeZustand(e,a,i);break;case"route-events":normalizeRoutes(e,a,i);break;default:break}return{params:a,notes:i}}function aliasNote(r){return r.length?`
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.aliasNote=aliasNote;exports.hashQueryKey=hashQueryKey;exports.normalizeParams=normalizeParams;const isPlainObject=i=>!!i&&typeof i==="object"&&!Array.isArray(i);function hashQueryKey(i){return JSON.stringify(i,(e,t)=>isPlainObject(t)?Object.keys(t).sort().reduce((a,r)=>{a[r]=t[r];return a},{}):t)}function alias(i,e,t,a){if(i[t]!==void 0||i[e]===void 0)return false;i[t]=i[e];delete i[e];a.push(`Accepted \`${e}\` as \`${t}\` \u2014 the parameter is named \`${t}\`.`);return true}const QUERY_HASH_ACTIONS=new Set(["getQueryData","refetch","invalidate","reset","remove","triggerError","restoreError","triggerLoading","restoreLoading"]);const RULE_FIELDS=["id","urlPattern","enabled","name","methods","kind","status","statusText","headers","body","bodyPatch","bodyPath","bodyValue","failKind","delayMs","times","alternate","force","bodyOmitted"];function normalizeQuery(i,e,t){if(QUERY_HASH_ACTIONS.has(i)&&e.queryHash===void 0){const a=isPlainObject(e.filters)?e.filters:void 0;const r=e.queryKey??e.key??e.hash??a?.queryKey??a?.queryHash;if(Array.isArray(r)){e.queryHash=hashQueryKey(r);t.push(`Accepted the queryKey array as the query's hash (${e.queryHash}) \u2014 the parameter is \`queryHash\`, the string from listQueries.`)}else if(typeof r==="string"){e.queryHash=r;t.push(`Accepted it as \`queryHash\` \u2014 that is the parameter name.`)}delete e.queryKey;delete e.key;delete e.hash;delete e.filters}if(i==="setQueryData"){if(isPlainObject(e.data)){for(const a of["merge","force"]){if(typeof e.data[a]==="boolean"&&e[a]===void 0){e[a]=e.data[a];delete e.data[a];t.push(`Moved \`${a}\` out of \`data\` to the top level \u2014 it is a write flag, not part of the value.`)}}}alias(e,"newData","data",t)||e.path===void 0&&alias(e,"value","data",t)||alias(e,"patch","data",t);if(e.queryKey===void 0&&typeof e.queryHash==="string"){try{const a=JSON.parse(e.queryHash);if(Array.isArray(a)){e.queryKey=a;t.push("Accepted the queryHash as the key \u2014 setQueryData takes `queryKey`, the array.")}}catch{}}if(e.patch!==void 0||e.merge===void 0){if(t.some(a=>a.includes("`patch`")))e.merge=true}}}function normalizeNetwork(i,e,t){if(i==="getEventBody"||i==="setPinned"||i==="setSaved"){alias(e,"requestId","id",t)||alias(e,"eventId","id",t)}if(i==="deleteOverrideRule"||i==="setOverrideRuleEnabled"||i==="getOverrideRuleBody"){alias(e,"ruleId","id",t)}if(i==="upsertOverrideRule"){const a=isPlainObject(e.rule)?{...e.rule}:{};if(e.fromRequestId===void 0&&typeof a.fromRequestId==="string"){e.fromRequestId=a.fromRequestId;t.push("Accepted `rule.fromRequestId` \u2014 it is a top-level parameter, beside `rule`.")}delete a.fromRequestId;unwrapRuleSections(a,t);ruleIntentWords(e,a,t);const r=[];for(const d of RULE_FIELDS){if(e[d]!==void 0&&a[d]===void 0){a[d]=e[d];r.push(d)}delete e[d]}if(r.length)t.push(`Accepted ${r.map(d=>`\`${d}\``).join(", ")} at the top level \u2014 rule fields belong inside \`rule\`.`);if(typeof a.times==="number"&&a.times<=0){delete a.times;t.push("Read `times: 0` as no limit \u2014 leave `times` out for a rule that keeps firing; `times: 1` fires once.")}if(Object.keys(a).length)e.rule=a}}function unwrapRuleSections(i,e){for(const[t,a]of[["match",["urlPattern","methods","method","url","pattern","urlContains","contains"]],["response",["status","statusText","body","headers","bodyPatch"]]]){const r=i[t];if(!isPlainObject(r))continue;const d=[];for(const s of a){if(r[s]===void 0)continue;const n=s==="method"?"methods":s==="url"||s==="pattern"||s==="urlContains"||s==="contains"?"urlPattern":s;if(i[n]!==void 0)continue;i[n]=n==="methods"&&!Array.isArray(r[s])?[r[s]]:r[s];delete r[s];d.push(s)}if(!d.length)continue;if(!Object.keys(r).length)delete i[t];e.push(`Read \`rule.${t}.{${d.join(", ")}}\` as top-level rule fields \u2014 this rule is flat, unlike msw/nock-style mocks.`)}}function ruleIntentWords(i,e,t){if(e.bodyPath===void 0&&typeof e.path==="string"){e.bodyPath=e.path;if(e.bodyValue===void 0&&e.value!==void 0)e.bodyValue=e.value;delete e.path;delete e.value;t.push("Read `path`/`value` as `bodyPath`/`bodyValue` \u2014 on a rule they address the response body.")}const a=s=>e[s]!==void 0?e[s]:i[s];const r=s=>{delete e[s];delete i[s]};const d=a("mode");if(typeof d==="string"&&["respond","fail","delay"].includes(d)&&e.kind===void 0){e.kind=d;t.push(`Read \`mode: "${d}"\` as \`kind: "${d}"\`.`)}r("mode");if(a("fail")===true&&e.kind===void 0){e.kind="fail";t.push('Read `fail: true` as `kind: "fail"` \u2014 a transport failure, as if offline.')}r("fail");if(a("failOnce")===true){if(e.kind===void 0)e.kind="fail";if(e.times===void 0)e.times=1;t.push('Read `failOnce: true` as `kind: "fail"` with `times: 1` \u2014 it fires once, then the rule disables itself.')}r("failOnce");if(a("once")===true&&e.times===void 0){e.times=1;t.push("Read `once: true` as `times: 1` \u2014 the rule auto-disables after one match.")}r("once");for(const s of["maxHits","hits","count","maxTimes","applyTimes"]){const n=a(s);if(typeof n==="number"&&Number.isFinite(n)&&n>0&&e.times===void 0){e.times=n;t.push(`Read \`${s}: ${n}\` as \`times: ${n}\` \u2014 the rule auto-disables after ${n} match(es).`)}r(s)}}function normalizeZustand(i,e,t){alias(e,"name","storeName",t)||alias(e,"store","storeName",t);if(i==="setState"){alias(e,"statePatch","state",t)||alias(e,"partial","state",t)||alias(e,"patch","state",t)||alias(e,"newState","state",t)||alias(e,"nextState","state",t)||e.path===void 0&&alias(e,"value","state",t);if(e.replace===void 0){e.replace=false;t.push("Applied replace:false (merge) \u2014 a store keeps action functions that can't be re-sent, and replacing would wipe them (the buttons that call them would then crash). Send replace:true only to deliberately reset the whole store.")}}}function normalizeRoutes(i,e,t){if(i==="navigate"){alias(e,"route","path",t)||alias(e,"to","path",t)||alias(e,"url","path",t)||alias(e,"pathname","path",t)}}function normalizeParams(i,e,t){const a={...t};const r=[];switch(i){case"query":normalizeQuery(e,a,r);break;case"network":normalizeNetwork(e,a,r);break;case"zustand":normalizeZustand(e,a,r);break;case"route-events":normalizeRoutes(e,a,r);break;default:break}return{params:a,notes:r}}function aliasNote(i){return i.length?`
2
2
 
3
- [Buoy] ${r.join(" ")}`:""}
3
+ [Buoy] ${i.join(" ")}`:""}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.SNAPSHOT_ACTION=void 0;exports.projectSnapshot=projectSnapshot;var _redact=require("../policy/redact");const SNAPSHOT_ACTION=exports.SNAPSHOT_ACTION="getSnapshot";const SEVERITY={verbose:0,debug:0,trace:0,log:1,info:1,warn:2,warning:2,error:3};function asArray(t){return Array.isArray(t)?t:[]}function rec(t){return t&&typeof t==="object"?t:{}}function num(t,e){return typeof t==="number"&&t>0?t:e}function newest(t,e){return t.length<=e?t:t.slice(0,e)}function projectConsole(t,e){const o=asArray(rec(t).entries);const u=typeof e.level==="string"?SEVERITY[e.level]??0:0;const i=typeof e.pattern==="string"?e.pattern.toLowerCase():void 0;const c=o.filter(a=>{const n=rec(a);const r=String(n.level??"log");if((SEVERITY[r]??1)<u)return false;if(!i)return true;return String(n.message??"").toLowerCase().includes(i)});const p=newest(c,num(e.limit,40));return{totalCaptured:o.length,shown:p.length,entries:p.map(a=>{const n=rec(a);return{at:n.timestamp,level:n.level,message:n.message}})}}function projectNetwork(t,e){const o=rec(t);const u=asArray(o.events).filter(n=>!(0,_redact.isSelfTraffic)(rec(n).url));const i=e.failedOnly===true;const c=typeof e.pattern==="string"?e.pattern.toLowerCase():void 0;const p=u.filter(n=>{const r=rec(n);const s=typeof r.status==="number"?r.status:void 0;if(i&&!(r.error||s!==void 0&&s>=400))return false;if(!c)return true;return String(r.url??"").toLowerCase().includes(c)});const a=newest(p,num(e.limit,25));return{totalCaptured:u.length,shown:a.length,requests:a.map(n=>{const r=rec(n);return{id:r.id,method:r.method,url:r.url,status:r.status,durationMs:r.duration,error:r.error,overridden:r.override?true:void 0}}),hint:"Read one response with network.getEventBody({ id })."}}function flattenRoutes(t,e=[],o=80){for(const u of asArray(t)){if(e.length>=o)break;const i=rec(u);if(i.isInternal!==true&&typeof i.path==="string")e.push(i.path);flattenRoutes(i.children,e,o)}return e}function projectRoutes(t,e){const o=rec(t);const u=rec(o.sitemap);const i=asArray(o.stack).map(rec);const c=asArray(o.events).map(rec);const p=i.map(s=>({name:s.name,path:s.pathname,focused:s.isFocused===true?true:void 0}));const a=c.slice(0,num(e.limit,15)).map(s=>({at:s.timestamp,from:s.previousPathname,to:s.pathname}));const n=i.find(s=>s.isFocused===true)??i[i.length-1];const r=c.length?{path:c[0].pathname,source:"event"}:typeof n?.pathname==="string"?{path:n.pathname,source:"stack"}:void 0;return{currentRoute:r,stack:p,routes:flattenRoutes(u.routes),sitemapSource:u.source,recentNavigations:a}}function projectEnv(t){const e=rec(t);return{env:e.env??e.variables,requiredEnvVars:e.requiredEnvVars}}function projectSentry(t,e){const o=rec(t);const u=asArray(o.envelopes).map(rec);const i=typeof e.type==="string"?e.type:void 0;const c=typeof e.pattern==="string"?e.pattern.toLowerCase():void 0;const p=u.filter(n=>{const r=asArray(n.items).map(rec);if(i&&!r.some(s=>s.type===i))return false;if(!c)return true;return r.some(s=>String(s.summary??"").toLowerCase().includes(c))});const a=newest(p,num(e.limit,10));return{status:o.status,totalCaptured:u.length,shown:a.length,envelopes:a.map(n=>({id:n.id,at:n.timestamp,origin:n.origin,eventId:n.eventId,totalBytes:n.totalBytes,items:asArray(n.items).map(r=>{const s=rec(r);return{type:s.type,summary:s.summary,spanCount:s.spanCount,bytes:s.bytes}})})),hint:"For crash stack traces, console's [FATAL] entries carry the component stacks."}}function projectSnapshot(t,e,o={}){switch(t){case"console":return projectConsole(e,o);case"network":return projectNetwork(e,o);case"route-events":return projectRoutes(e,o);case"env":return projectEnv(e);case"sentry":return projectSentry(e,o);default:return e}}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.SNAPSHOT_ACTION=void 0;exports.projectSnapshot=projectSnapshot;var _redact=require("../policy/redact");const SNAPSHOT_ACTION=exports.SNAPSHOT_ACTION="getSnapshot";const SEVERITY={verbose:0,debug:0,trace:0,log:1,info:1,warn:2,warning:2,error:3};function asArray(r){return Array.isArray(r)?r:[]}function rec(r){return r&&typeof r==="object"?r:{}}function num(r,t){return typeof r==="number"&&r>0?r:t}function newest(r,t){return r.length<=t?r:r.slice(0,t)}function projectConsole(r,t){const s=asArray(rec(r).entries);const a=typeof t.level==="string"?SEVERITY[t.level]??0:0;const u=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const d=s.filter(c=>{const n=rec(c);const o=String(n.level??"log");if((SEVERITY[o]??1)<a)return false;if(!u)return true;return String(n.message??"").toLowerCase().includes(u)});const e=newest(d,num(t.limit,40));return{totalCaptured:s.length,shown:e.length,entries:e.map(c=>{const n=rec(c);return{at:n.timestamp,level:n.level,message:n.message}})}}function projectNetwork(r,t){const s=rec(r);const a=asArray(s.events).filter(n=>!(0,_redact.isSelfTraffic)(rec(n).url));const u=t.failedOnly===true;const d=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const e=a.filter(n=>{const o=rec(n);const i=typeof o.status==="number"?o.status:void 0;if(u&&!(o.error||i!==void 0&&i>=400))return false;if(!d)return true;return String(o.url??"").toLowerCase().includes(d)});const c=newest(e,num(t.limit,25));return{totalCaptured:a.length,shown:c.length,requests:c.map(n=>{const o=rec(n);return{id:o.id,method:o.method,url:o.url,status:o.status,durationMs:o.duration,error:o.error,overridden:o.override?true:void 0}}),hint:"Read one response with network.getEventBody({ id })."}}function flattenRoutes(r,t=[],s=80){for(const a of asArray(r)){if(t.length>=s)break;const u=rec(a);if(u.isInternal!==true&&typeof u.path==="string")t.push(u.path);flattenRoutes(u.children,t,s)}return t}function projectStorage(r,t){const s=e=>typeof e==="string"&&e.length>300?`${e.slice(0,300)}\u2026`:e;const a=typeof t.key==="string"?t.key.toLowerCase():void 0;const u=asArray(r).map(rec).map(e=>{const c=rec(e.data);return{id:e.id,at:e.timestamp,action:e.action,storage:e.storageType,key:c.key??c.keys,value:s(c.value),prevValue:s(c.prevValue)}}).filter(e=>!a||JSON.stringify(e.key??"").toLowerCase().includes(a)).sort((e,c)=>String(c.at).localeCompare(String(e.at)));const d=num(t.limit,20);return{events:u.slice(0,d),total:u.length,returned:Math.min(d,u.length)}}function projectRedux(r,t){const s=e=>{if(e===void 0)return void 0;const c=typeof e==="string"?e:JSON.stringify(e);return c&&c.length>400?`${c.slice(0,400)}\u2026`:e};const a=typeof t.type==="string"?t.type.toLowerCase():void 0;const u=asArray(r).map(rec).filter(e=>e.category!=="internal").map(e=>({id:e.id,at:e.timestamp,type:e.type,payload:s(e.payload),meta:s(e.meta),error:s(e.error),changed:e.diffSummary||void 0})).filter(e=>!a||String(e.type).toLowerCase().includes(a)).sort((e,c)=>Number(c.at)-Number(e.at));const d=num(t.limit,20);return{actions:u.slice(0,d),total:u.length,returned:Math.min(d,u.length)}}function projectStoreChanges(r,t,s,a){const u=n=>{if(n===void 0)return void 0;const o=typeof n==="string"?n:JSON.stringify(n);return o&&o.length>400?`${o.slice(0,400)}\u2026`:n};const d=typeof t[a]==="string"?String(t[a]).toLowerCase():void 0;const e=asArray(rec(r).changes).map(rec).map(n=>({id:n.id,at:n.timestamp,[a]:n[s],changed:n.changedKeys,summary:n.diffSummary||void 0,...s==="storeName"?{partial:u(n.partial)}:{value:u(n.valuePreview)}})).filter(n=>!d||String(n[a]??"").toLowerCase().includes(d)).sort((n,o)=>Number(o.at)-Number(n.at));const c=num(t.limit,20);return{changes:e.slice(0,c),total:e.length,returned:Math.min(c,e.length)}}function projectRoutes(r,t){const s=rec(r);const a=rec(s.sitemap);const u=asArray(s.stack).map(rec);const d=asArray(s.events).map(rec);const e=u.map(i=>({name:i.name,path:i.pathname,focused:i.isFocused===true?true:void 0}));const c=d.slice(0,num(t.limit,15)).map(i=>({at:i.timestamp,from:i.previousPathname,to:i.pathname}));const n=u.find(i=>i.isFocused===true)??u[u.length-1];const o=d.length?{path:d[0].pathname,source:"event"}:typeof n?.pathname==="string"?{path:n.pathname,source:"stack"}:void 0;return{currentRoute:o,stack:e,routes:flattenRoutes(a.routes),sitemapSource:a.source,recentNavigations:c}}function projectEnv(r){const t=rec(r);return{env:t.env??t.variables,requiredEnvVars:t.requiredEnvVars,...Array.isArray(t.checks)?{checks:t.checks}:{}}}function projectSentry(r,t){const s=rec(r);const a=asArray(s.envelopes).map(rec);const u=typeof t.type==="string"?t.type:void 0;const d=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const e=a.filter(n=>{const o=asArray(n.items).map(rec);if(u&&!o.some(i=>i.type===u))return false;if(!d)return true;return o.some(i=>String(i.summary??"").toLowerCase().includes(d))});const c=newest(e,num(t.limit,10));return{status:s.status,totalCaptured:a.length,shown:c.length,envelopes:c.map(n=>({id:n.id,at:n.timestamp,origin:n.origin,eventId:n.eventId,totalBytes:n.totalBytes,items:asArray(n.items).map(o=>{const i=rec(o);return{type:i.type,summary:i.summary,spanCount:i.spanCount,bytes:i.bytes}})})),drops:(()=>{const n=rec(s.drops);const o=newest(asArray(n.recent).map(rec),8).map(i=>({at:i.timestamp,reason:i.reason,category:i.category,quantity:i.quantity,detail:typeof i.detail==="string"?i.detail.slice(0,200):i.detail}));return o.length||n.summary?{summary:n.summary,recent:o}:void 0})(),hint:"For crash stack traces, console's [FATAL] entries carry the component stacks. `drops` lists events the SDK discarded before sending, with the reason."}}function projectSnapshot(r,t,s={}){switch(r){case"console":return projectConsole(t,s);case"network":return projectNetwork(t,s);case"route-events":return projectRoutes(t,s);case"storage":return projectStorage(t,s);case"redux":return projectRedux(t,s);case"zustand":return projectStoreChanges(t,s,"storeName","store");case"jotai":return projectStoreChanges(t,s,"atomLabel","atom");case"env":return projectEnv(t);case"sentry":return projectSentry(t,s);default:return t}}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.buildContextDigest=buildContextDigest;exports.buildLiveContext=buildLiveContext;var _snapshotReads=require("../catalog/snapshotReads");var _realNow=require("../realNow");const PROBE_TIMEOUT_MS=2500;const list=e=>Array.isArray(e)?e:Array.isArray(e?.items)?e.items:[];const rec=e=>e&&typeof e==="object"?e:{};const names=(e,t,n=40)=>list(e).slice(0,n).map(s=>{if(!s||typeof s!=="object")return s;const i=s;for(const o of t){if(i[o]!==void 0)return i[o]}return i.id??i.name??i.key});const DIGEST_STORE_CAP=30;const DIGEST_STORE_KEYS=24;const DIGEST_LISTS_PER_STORE=8;const DIGEST_ITEM_CHARS=240;const DIGEST_SHAPES_TOTAL_CHARS=12e3;const clampText=(e,t)=>e.length<=t?e:`${e.slice(0,t-1)}\u2026`;function topLevelFields(e){const t=new Map;if(typeof e!=="string"||!e.startsWith("{"))return t;let n=0;let s="";const i=()=>{const o=s.indexOf(":");if(o>0)t.set(s.slice(0,o).trim(),s.slice(o+1).trim());s=""};for(const o of e.slice(1,-1)){if(o==="{"||o==="["||o==="(")n++;else if(o==="}"||o==="]"||o===")")n--;if(o===","&&n===0)i();else s+=o}i();return t}const STABLE_PROBES=[{key:"routes",toolId:"route-events",action:_snapshotReads.SNAPSHOT_ACTION,shape:e=>list(e?.routes).slice(0,60)},{key:"reduxSlices",toolId:"redux",action:"getState",params:{includeValues:false},shape:e=>e&&typeof e==="object"?Object.keys(e.state??e).slice(0,40):e},{key:"zustandStores",toolId:"zustand",action:"listStores",shape:e=>{let t=0;return list(e?.stores??e).slice(0,DIGEST_STORE_CAP).map(n=>{const s=n??{};const i=typeof s.name==="string"?s.name:s.id;const o=list(s.shape?.lists).map(rec);const u={};const l=[];for(const r of o.slice(0,DIGEST_LISTS_PER_STORE)){const a=typeof r.path==="string"?r.path:void 0;if(!a)continue;if(a.includes("[]."))continue;if(typeof r.item!=="string"){l.push(a);continue}const c=clampText(r.item,DIGEST_ITEM_CHARS);if(t+c.length>DIGEST_SHAPES_TOTAL_CHARS)continue;t+=c.length;u[a]=c}return{name:i,...typeof s.persistName==="string"?{persistsTo:s.persistName}:{},...(()=>{if(!Array.isArray(s.keys)||!s.keys.length)return{};const r=topLevelFields(s.shape?.shape);const a=s.keys.filter(c=>r.get(String(c))!=="function");return a.length?{keys:a.slice(0,DIGEST_STORE_KEYS)}:{}})(),...Object.keys(u).length?{listItemShapes:u}:{},...l.length?{unknownLists:l}:{}}})}},{key:"jotaiAtoms",toolId:"jotai",action:"listAtoms",shape:e=>names(e,["debugLabel","label","id"],40)},{key:"storageKeys",toolId:"storage",action:"async.getAllKeys",shape:e=>list(e).slice(0,60)},{key:"env",toolId:"env",action:_snapshotReads.SNAPSHOT_ACTION,shape:e=>{const t=e?.env;return t&&typeof t==="object"?Object.keys(t).slice(0,40):void 0}}];const LIVE_QUERY_CAP=25;const LIVE_QUERY_SCAN=LIVE_QUERY_CAP*2;const LIVE_REQUEST_CAP=8;const LIVE_PROBES=[{key:"currentRoute",toolId:"route-events",action:"getCurrentRoute",shape:e=>{const t=rec(e);if(typeof t.path!=="string")return void 0;return t.params&&Object.keys(rec(t.params)).length?{path:t.path,params:t.params}:{path:t.path}}},{key:"queries",toolId:"query",action:"listQueries",params:{limit:LIVE_QUERY_SCAN},shape:e=>{const t=list(e?.queries??e).map(rec);const n=t.map(s=>{const i=typeof s.observers==="number"?s.observers:0;return{key:s.queryKey??s.queryHash,status:s.status,...i>0?{onScreen:true,observers:i}:{}}});n.sort((s,i)=>Number(Boolean(i.onScreen))-Number(Boolean(s.onScreen)));return n.slice(0,LIVE_QUERY_CAP)}},{key:"networkCapture",toolId:"network",action:"getCaptureStatus",shape:e=>{const t=rec(e);if(typeof t.capturing!=="boolean")return void 0;return{recording:t.capturing,...t.interceptorLive===false?{interceptorLive:false}:{},captured:typeof t.eventCount==="number"?t.eventCount:void 0}}},{key:"recentRequests",toolId:"network",action:_snapshotReads.SNAPSHOT_ACTION,params:{limit:LIVE_REQUEST_CAP},shape:e=>list(e?.requests).map(t=>{const n=rec(t);return{id:n.id,method:n.method,url:n.url,status:n.status,...n.overridden?{overridden:true}:{}}})},{key:"appClock",toolId:"clock",action:"getState",shape:e=>{const t=rec(e);if(t.active!==true||typeof t.virtualIso!=="string")return void 0;return{appTime:t.virtualIso,mode:t.mode,...typeof t.rate==="number"&&t.rate!==1?{rate:t.rate}:{},offsetMs:t.offsetMs}}},{key:"appLocation",toolId:"location",action:"getState",shape:e=>{const t=rec(e);if(t.active!==true)return void 0;const n=rec(t.override);const s=rec(t.current);const i=rec(s.fix);const o=rec(n.place);const u=rec(n.route);return{mode:n.mode,...typeof i.latitude==="number"?{latitude:i.latitude,longitude:i.longitude,accuracy:i.accuracy}:{noFix:s.reason},...n.mode==="fixed"&&typeof o.label==="string"?{place:o.label}:{},...n.mode==="route"?{route:u.name??u.id,playing:n.playing===true}:{},...n.signal===false?{signal:false}:{},...n.services===false?{services:false}:{}}}},{key:"appPermissions",toolId:"permissions",action:_snapshotReads.SNAPSHOT_ACTION,shape:e=>{const t=list(rec(e).permissions).map(rec).filter(n=>typeof n.override==="string");if(!t.length)return void 0;return Object.fromEntries(t.map(n=>[String(n.id),n.real&&n.real!==n.override?`${n.override} (system: ${n.real})`:n.override]))}},{key:"networkOverrides",toolId:"network",action:"listOverrideRules",shape:e=>{const t=rec(e);const n=list(t.rules).map(rec).filter(s=>s.enabled!==false).slice(0,10).map(s=>({id:s.id,urlPattern:s.urlPattern,kind:s.kind,status:s.status}));if(!n.length)return void 0;return{enabled:t.enabled===true,rules:n}}}];async function withTimeout(e,t){return await Promise.race([e.catch(()=>void 0),new Promise(n=>(0,_realNow.realSetTimeout)(()=>n(void 0),t))])}async function runProbes(e,{dispatch:t,readSnapshot:n,availableToolIds:s,availableActions:i}){const o=e.filter(r=>{if(!s.includes(r.toolId))return false;const a=i?.[r.toolId];return!a||a.includes(r.action)});const u=await Promise.all(o.map(async r=>{const a=r.action===_snapshotReads.SNAPSHOT_ACTION?n?Promise.resolve(n(r.toolId)).then(y=>(0,_snapshotReads.projectSnapshot)(r.toolId,y,r.params??{})):Promise.resolve(void 0):Promise.resolve(t(r.toolId,r.action,r.params));const c=await withTimeout(a,PROBE_TIMEOUT_MS);if(c===void 0)return[r.key,void 0];try{return[r.key,r.shape(c)]}catch{return[r.key,void 0]}}));const l={};for(const[r,a]of u){const c=a===void 0||a===null||Array.isArray(a)&&a.length===0;if(!c)l[r]=a}return l}function buildContextDigest(e){return runProbes(STABLE_PROBES,e)}function keyTokens(e){const t=Array.isArray(e)?e:typeof e==="string"?(()=>{try{const n=JSON.parse(e);return Array.isArray(n)?n:[e]}catch{return[e]}})():[e];return t.flatMap(n=>n===null||n===void 0?[]:splitTokens(String(typeof n==="object"?JSON.stringify(n):n)))}function splitTokens(e){return e.toLowerCase().split(/[^a-z0-9]+/i).filter(Boolean)}function annotateLikelyScreen(e){const t=e.currentRoute;const n=e.queries;if(!t?.path||!Array.isArray(n))return;const s=Object.values(t.params??{}).map(r=>splitTokens(String(r))).filter(r=>r.length>0);const i=splitTokens(t.path);let o=-1;let u=-1;let l=false;n.forEach((r,a)=>{if(!r.onScreen)return;const c=new Set(keyTokens(r.key));const y=s.filter(d=>d.every(p=>c.has(p))).length;const m=i.filter(d=>c.has(d)||[...c].some(p=>p.includes(d)||d.includes(p))).length;const f=y*3+m;if(f<=0)return;if(f>o){o=f;u=a;l=false}else if(f===o){l=true}});if(u>=0&&!l){n[u].likelyScreen=true;const[r]=n.splice(u,1);n.unshift(r)}}async function buildLiveContext(e){const t=await runProbes(LIVE_PROBES,e);annotateLikelyScreen(t);return t}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.buildContextDigest=buildContextDigest;exports.buildLiveContext=buildLiveContext;var _snapshotReads=require("../catalog/snapshotReads");var _realNow=require("../realNow");const PROBE_TIMEOUT_MS=2500;const list=t=>Array.isArray(t)?t:Array.isArray(t?.items)?t.items:[];const rec=t=>t&&typeof t==="object"?t:{};const names=(t,e,n=40)=>list(t).slice(0,n).map(s=>{if(!s||typeof s!=="object")return s;const i=s;for(const o of e){if(i[o]!==void 0)return i[o]}return i.id??i.name??i.key});const DIGEST_STORE_CAP=30;const DIGEST_STORE_KEYS=24;const DIGEST_LISTS_PER_STORE=8;const DIGEST_ITEM_CHARS=240;const DIGEST_SHAPES_TOTAL_CHARS=12e3;const clampText=(t,e)=>t.length<=e?t:`${t.slice(0,e-1)}\u2026`;function topLevelFields(t){const e=new Map;if(typeof t!=="string"||!t.startsWith("{"))return e;let n=0;let s="";const i=()=>{const o=s.indexOf(":");if(o>0)e.set(s.slice(0,o).trim(),s.slice(o+1).trim());s=""};for(const o of t.slice(1,-1)){if(o==="{"||o==="["||o==="(")n++;else if(o==="}"||o==="]"||o===")")n--;if(o===","&&n===0)i();else s+=o}i();return e}const STABLE_PROBES=[{key:"routes",toolId:"route-events",action:_snapshotReads.SNAPSHOT_ACTION,shape:t=>list(t?.routes).slice(0,60)},{key:"reduxSlices",toolId:"redux",action:"getState",params:{includeValues:false},shape:t=>{const e=rec(t);if(e.available===false)return void 0;const n=rec(e.sliceFields);if(Array.isArray(e.slices))return e.slices.slice(0,40).map(s=>Array.isArray(n[String(s)])&&n[String(s)].length?`${String(s)}: ${n[String(s)].join(", ")}`:s);return e.state&&typeof e.state==="object"?Object.keys(e.state).slice(0,40):void 0}},{key:"zustandStores",toolId:"zustand",action:"listStores",shape:t=>{let e=0;return list(t?.stores??t).slice(0,DIGEST_STORE_CAP).map(n=>{const s=n??{};const i=typeof s.name==="string"?s.name:s.id;const o=list(s.shape?.lists).map(rec);const u={};const l=[];for(const r of o.slice(0,DIGEST_LISTS_PER_STORE)){const a=typeof r.path==="string"?r.path:void 0;if(!a)continue;if(a.includes("[]."))continue;if(typeof r.item!=="string"){l.push(a);continue}const c=clampText(r.item,DIGEST_ITEM_CHARS);if(e+c.length>DIGEST_SHAPES_TOTAL_CHARS)continue;e+=c.length;u[a]=c}return{name:i,...typeof s.persistName==="string"?{persistsTo:s.persistName}:{},...(()=>{if(!Array.isArray(s.keys)||!s.keys.length)return{};const r=topLevelFields(s.shape?.shape);const a=s.keys.filter(c=>r.get(String(c))!=="function");return a.length?{keys:a.slice(0,DIGEST_STORE_KEYS)}:{}})(),...Object.keys(u).length?{listItemShapes:u}:{},...l.length?{unknownLists:l}:{}}})}},{key:"jotaiAtoms",toolId:"jotai",action:"listAtoms",shape:t=>names(rec(t).atoms??t,["debugLabel","label","id"],40)},{key:"storageKeys",toolId:"storage",action:"async.getAllKeys",shape:t=>list(t).slice(0,60)},{key:"env",toolId:"env",action:_snapshotReads.SNAPSHOT_ACTION,shape:t=>{const e=t?.env;return e&&typeof e==="object"?Object.keys(e).slice(0,40):void 0}}];const LIVE_QUERY_CAP=25;const LIVE_QUERY_SCAN=LIVE_QUERY_CAP*2;const LIVE_REQUEST_CAP=8;const LIVE_PROBES=[{key:"currentRoute",toolId:"route-events",action:"getCurrentRoute",shape:t=>{const e=rec(t);if(typeof e.path!=="string")return void 0;return e.params&&Object.keys(rec(e.params)).length?{path:e.path,params:e.params}:{path:e.path}}},{key:"queries",toolId:"query",action:"listQueries",params:{limit:LIVE_QUERY_SCAN},shape:t=>{const e=list(t?.queries??t).map(rec);const n=e.map(s=>{const i=typeof s.observers==="number"?s.observers:0;return{key:s.queryKey??s.queryHash,status:s.status,...i>0?{onScreen:true,observers:i}:{}}});n.sort((s,i)=>Number(Boolean(i.onScreen))-Number(Boolean(s.onScreen)));return n.slice(0,LIVE_QUERY_CAP)}},{key:"networkCapture",toolId:"network",action:"getCaptureStatus",shape:t=>{const e=rec(t);if(typeof e.capturing!=="boolean")return void 0;return{recording:e.capturing,...e.interceptorLive===false?{interceptorLive:false}:{},captured:typeof e.eventCount==="number"?e.eventCount:void 0}}},{key:"recentRequests",toolId:"network",action:_snapshotReads.SNAPSHOT_ACTION,params:{limit:LIVE_REQUEST_CAP},shape:t=>list(t?.requests).map(e=>{const n=rec(e);return{id:n.id,method:n.method,url:n.url,status:n.status,...n.overridden?{overridden:true}:{}}})},{key:"appClock",toolId:"clock",action:"getState",shape:t=>{const e=rec(t);if(e.active!==true||typeof e.virtualIso!=="string")return void 0;return{appTime:e.virtualIso,mode:e.mode,...typeof e.rate==="number"&&e.rate!==1?{rate:e.rate}:{},offsetMs:e.offsetMs}}},{key:"impersonating",toolId:"impersonate",action:_snapshotReads.SNAPSHOT_ACTION,shape:t=>{const e=rec(t);if(e.isActive!==true)return void 0;const n=rec(e.currentUser);return{user:typeof n.displayName==="string"?n.displayName:n.id,id:n.id,...e.isPaused===true?{paused:true}:{},header:e.headerKey}}},{key:"appLocation",toolId:"location",action:"getState",shape:t=>{const e=rec(t);if(e.active!==true)return void 0;const n=rec(e.override);const s=rec(e.current);const i=rec(s.fix);const o=rec(n.place);const u=rec(n.route);return{mode:n.mode,...typeof i.latitude==="number"?{latitude:i.latitude,longitude:i.longitude,accuracy:i.accuracy}:{noFix:s.reason},...n.mode==="fixed"&&typeof o.label==="string"?{place:o.label}:{},...n.mode==="route"?{route:u.name??u.id,playing:n.playing===true}:{},...n.signal===false?{signal:false}:{},...n.services===false?{services:false}:{}}}},{key:"appPermissions",toolId:"permissions",action:_snapshotReads.SNAPSHOT_ACTION,shape:t=>{const e=list(rec(t).permissions).map(rec).filter(n=>typeof n.override==="string");if(!e.length)return void 0;return Object.fromEntries(e.map(n=>[String(n.id),n.real&&n.real!==n.override?`${n.override} (system: ${n.real})`:n.override]))}},{key:"networkOverrides",toolId:"network",action:"listOverrideRules",shape:t=>{const e=rec(t);const n=list(e.rules).map(rec).filter(s=>s.enabled!==false).slice(0,10).map(s=>({id:s.id,urlPattern:s.urlPattern,kind:s.kind,status:s.status}));if(!n.length)return void 0;return{enabled:e.enabled===true,rules:n}}}];async function withTimeout(t,e){return await Promise.race([t.catch(()=>void 0),new Promise(n=>(0,_realNow.realSetTimeout)(()=>n(void 0),e))])}async function runProbes(t,{dispatch:e,readSnapshot:n,availableToolIds:s,availableActions:i}){const o=t.filter(r=>{if(!s.includes(r.toolId))return false;const a=i?.[r.toolId];return!a||a.includes(r.action)});const u=await Promise.all(o.map(async r=>{const a=r.action===_snapshotReads.SNAPSHOT_ACTION?n?Promise.resolve(n(r.toolId)).then(y=>(0,_snapshotReads.projectSnapshot)(r.toolId,y,r.params??{})):Promise.resolve(void 0):Promise.resolve(e(r.toolId,r.action,r.params));const c=await withTimeout(a,PROBE_TIMEOUT_MS);if(c===void 0)return[r.key,void 0];try{return[r.key,r.shape(c)]}catch{return[r.key,void 0]}}));const l={};for(const[r,a]of u){const c=a===void 0||a===null||Array.isArray(a)&&a.length===0;if(!c)l[r]=a}return l}function buildContextDigest(t){return runProbes(STABLE_PROBES,t)}function keyTokens(t){const e=Array.isArray(t)?t:typeof t==="string"?(()=>{try{const n=JSON.parse(t);return Array.isArray(n)?n:[t]}catch{return[t]}})():[t];return e.flatMap(n=>n===null||n===void 0?[]:splitTokens(String(typeof n==="object"?JSON.stringify(n):n)))}function splitTokens(t){return t.toLowerCase().split(/[^a-z0-9]+/i).filter(Boolean)}function annotateLikelyScreen(t){const e=t.currentRoute;const n=t.queries;if(!e?.path||!Array.isArray(n))return;const s=Object.values(e.params??{}).map(r=>splitTokens(String(r))).filter(r=>r.length>0);const i=splitTokens(e.path);let o=-1;let u=-1;let l=false;n.forEach((r,a)=>{if(!r.onScreen)return;const c=new Set(keyTokens(r.key));const y=s.filter(d=>d.every(p=>c.has(p))).length;const m=i.filter(d=>c.has(d)||[...c].some(p=>p.includes(d)||d.includes(p))).length;const f=y*3+m;if(f<=0)return;if(f>o){o=f;u=a;l=false}else if(f===o){l=true}});if(u>=0&&!l){n[u].likelyScreen=true;const[r]=n.splice(u,1);n.unshift(r)}}async function buildLiveContext(t){const e=await runProbes(LIVE_PROBES,t);annotateLikelyScreen(e);let n;try{n=Intl.DateTimeFormat().resolvedOptions().timeZone}catch{n=void 0}e.now={iso:new Date((0,_realNow.realNow)()).toISOString(),...n?{timeZone:n}:{}};return e}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.EffectLedger=void 0;exports.isReversible=isReversible;var _digest=require("./digest");function isReversible(f){return f.kind!=="transient"&&f.kind!=="state-write"}let seq=0;class EffectLedger{entries=[];listeners=new Set;gen=0;get generation(){return this.gen}record(t,n,e,r){if(r!==void 0&&r!==this.gen)return void 0;const i={id:`fx-${++seq}-${Date.now()}`,at:Date.now(),toolId:t,action:n,effect:e};this.entries=[...this.entries,i];this.emit();return i}list(){return this.entries}liveCallIds(){const t=new Set;for(const n of this.entries)if(!n.undoneAt&&n.callId)t.add(n.callId);return t}restore(t){if(this.entries.length>0||t.length===0)return false;this.entries=[...t];this.emit();return true}touch(){this.entries=[...this.entries];this.emit()}reversible(){return this.entries.filter(t=>!t.undoneAt&&isReversible(t.effect))}transients(){return this.entries.filter(t=>!t.undoneAt&&t.effect.kind==="transient")}isEmpty(){return this.entries.every(t=>t.undoneAt!==void 0)}subscribe(t){this.listeners.add(t);return()=>this.listeners.delete(t)}clear(){this.entries=[];this.gen+=1;this.emit()}noteExternalRevert(t,n,e){let r=false;const i=s=>{for(const o of this.entries){if(!o.undoneAt&&s(o)){o.undoneAt=Date.now();o.undoError=void 0;r=true}}};if(t==="network"&&n==="deleteOverrideRule"){i(s=>s.effect.kind==="network-override"&&s.effect.ruleId===e.id)}else if(t==="network"&&n==="clearOverrideRules"){i(s=>s.effect.kind==="network-override")}else if(t==="impersonate"&&n==="stopImpersonation"){i(s=>s.effect.kind==="impersonation")}else{i(s=>{const o=s.effect;if(o.kind!=="inverse-call"||o.toolId!==t||o.action!==n)return false;return Object.entries(o.params).every(([a,u])=>JSON.stringify(e[a]??(a==="enabled"?false:void 0))===JSON.stringify(u))})}if(r)this.touch();return r}emit(){for(const t of this.listeners){try{t()}catch{}}}async revertOne(t,n){if(t.undoneAt)return void 0;const e=t.effect;try{switch(e.kind){case"network-override":await n("network","deleteOverrideRule",{id:e.ruleId});break;case"impersonation":await n("impersonate","stopImpersonation",{});break;case"storage-write":{const r=e.instanceId?{instanceId:e.instanceId}:{};if(e.wrote!==void 0){const i=e.action==="mmkv.set"?"mmkv.get":"async.getItem";try{const s=await n(e.toolId,i,{...r,key:e.key});const o=s&&typeof s==="object"?s:void 0;const a=o?o.value:s;if(o?.found!==false&&a!==void 0&&a!==e.wrote){return"This value was changed again after Ask Buoy wrote it \u2014 left the newer value alone"}}catch{}}if(e.before===void 0){if(!e.removeAction)return"No remove action known for this key";await n(e.toolId,e.removeAction,{...r,key:e.key})}else{await n(e.toolId,e.action,{...r,key:e.key,value:e.before})}break}case"query-write":{if(e.wroteDigest!==void 0){try{const i=await n("query","getQueryData",{queryHash:e.queryHash});if(i?.found===true){const s=i.hasData===false?void 0:(0,_digest.digestOf)(i.data);if(s!==e.wroteDigest){return"The app has refetched or changed this query since Ask Buoy wrote it \u2014 left the newer data alone"}}}catch{}}const r=await(e.before===void 0?n("query","invalidate",{queryHash:e.queryHash}):n("query","setQueryData",{queryHash:e.queryHash,data:e.before,force:true}));if(r&&typeof r==="object"&&r.ok===false){return typeof r.error==="string"?r.error:"The query could not be restored"}break}case"inverse-call":{const r=await n(e.toolId,e.action,e.params);if(r&&typeof r==="object"&&r.ok===false){return typeof r.error==="string"?r.error:"The change could not be undone"}break}case"state-write":return"This change can't be undone automatically";case"transient":return void 0}t.undoneAt=Date.now();t.undoError=void 0;this.touch();return void 0}catch(r){const i=r instanceof Error?r.message:String(r);t.undoError=i;this.touch();return i}}async revertAll(t){const n={reverted:0,failed:[],skippedTransients:0,permanent:0};const e=[...this.entries].filter(r=>!r.undoneAt).reverse();for(const r of e){if(r.effect.kind==="transient"){n.skippedTransients+=1;continue}if(!isReversible(r.effect)){n.permanent+=1;continue}const i=await this.revertOne(r,t);if(i)n.failed.push({label:r.effect.label,error:i});else n.reverted+=1}this.emit();return n}}exports.EffectLedger=EffectLedger;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.EffectLedger=void 0;exports.isReversible=isReversible;var _digest=require("./digest");var _storeDiff=require("./storeDiff");function isReversible(f){return f.kind!=="transient"&&f.kind!=="state-write"}let seq=0;class EffectLedger{entries=[];listeners=new Set;gen=0;get generation(){return this.gen}record(r,n,e,t){if(t!==void 0&&t!==this.gen)return void 0;const i={id:`fx-${++seq}-${Date.now()}`,at:Date.now(),toolId:r,action:n,effect:e};this.entries=[...this.entries,i];this.emit();return i}list(){return this.entries}liveCallIds(){const r=new Set;for(const n of this.entries)if(!n.undoneAt&&n.callId)r.add(n.callId);return r}restore(r){if(this.entries.length>0||r.length===0)return false;this.entries=[...r];this.emit();return true}touch(){this.entries=[...this.entries];this.emit()}reversible(){return this.entries.filter(r=>!r.undoneAt&&isReversible(r.effect))}transients(){return this.entries.filter(r=>!r.undoneAt&&r.effect.kind==="transient")}isEmpty(){return this.entries.every(r=>r.undoneAt!==void 0)}subscribe(r){this.listeners.add(r);return()=>this.listeners.delete(r)}clear(){this.entries=[];this.gen+=1;this.emit()}noteExternalRevert(r,n,e){let t=false;const i=o=>{for(const s of this.entries){if(!s.undoneAt&&o(s)){s.undoneAt=Date.now();s.undoError=void 0;t=true}}};if(r==="network"&&n==="deleteOverrideRule"){i(o=>o.effect.kind==="network-override"&&o.effect.ruleId===e.id)}else if(r==="network"&&n==="clearOverrideRules"){i(o=>o.effect.kind==="network-override")}else if(r==="impersonate"&&n==="stopImpersonation"){i(o=>o.effect.kind==="impersonation")}else{i(o=>{const s=o.effect;if(s.kind!=="inverse-call"||s.toolId!==r||s.action!==n)return false;return Object.entries(s.params).every(([a,u])=>JSON.stringify(e[a]??(a==="enabled"?false:void 0))===JSON.stringify(u))})}if(t)this.touch();return t}emit(){for(const r of this.listeners){try{r()}catch{}}}async revertOne(r,n){if(r.undoneAt)return void 0;const e=r.effect;try{switch(e.kind){case"network-override":await n("network","deleteOverrideRule",{id:e.ruleId});break;case"impersonation":await n("impersonate","stopImpersonation",{});break;case"storage-write":{const t=e.instanceId?{instanceId:e.instanceId}:{};if(e.wrote!==void 0){const i=e.action==="mmkv.set"?"mmkv.get":"async.getItem";try{const o=await n(e.toolId,i,{...t,key:e.key});const s=o&&typeof o==="object"?o:void 0;const a=s?s.value:o;if(s?.found!==false&&a!==void 0&&a!==e.wrote){return"This value was changed again after Ask Buoy wrote it \u2014 left the newer value alone"}}catch{}}if(e.before===void 0){if(!e.removeAction)return"No remove action known for this key";await n(e.toolId,e.removeAction,{...t,key:e.key})}else{await n(e.toolId,e.action,{...t,key:e.key,value:e.before})}break}case"query-write":{if(e.wroteDigest!==void 0){try{const i=await n("query","getQueryData",{queryHash:e.queryHash});if(i?.found===true){const o=i.hasData===false?void 0:(0,_digest.digestOf)(i.data);if(o!==e.wroteDigest){return"The app has refetched or changed this query since Ask Buoy wrote it \u2014 left the newer data alone"}}}catch{}}const t=await(e.before===void 0?n("query","invalidate",{queryHash:e.queryHash}):n("query","setQueryData",{queryHash:e.queryHash,data:e.before,force:true}));if(t&&typeof t==="object"&&t.ok===false){return typeof t.error==="string"?t.error:"The query could not be restored"}break}case"store-write":{let t;try{const o=await n(e.toolId,"getStoreState",{storeName:e.storeName});t=o?.found===true?o.currentState:void 0}catch{t=void 0}if(!t||typeof t!=="object"){return"Couldn't read the store to check for newer changes, so nothing was undone"}{const o=e.leaves.filter(s=>{const a=(0,_storeDiff.readLeaf)(t,s.path);return!a.found||(0,_digest.digestOf)(a.value)!==s.wroteDigest});if(o.length){return`${o.map(s=>s.path.join(".")).join(", ")} changed again after Ask Buoy wrote it \u2014 left the newer value alone`}}const i=await n(e.toolId,"setState",{storeName:e.storeName,state:(0,_storeDiff.restorePatch)(e.leaves),replace:false,force:true});if(i&&typeof i==="object"&&i.ok===false){return typeof i.error==="string"?i.error:"The store could not be restored"}break}case"inverse-call":{const t=await n(e.toolId,e.action,e.params);if(t&&typeof t==="object"&&t.ok===false){return typeof t.error==="string"?t.error:"The change could not be undone"}break}case"state-write":return"This change can't be undone automatically";case"transient":return void 0}r.undoneAt=Date.now();r.undoError=void 0;this.touch();return void 0}catch(t){const i=t instanceof Error?t.message:String(t);r.undoError=i;this.touch();return i}}async revertAll(r){const n={reverted:0,failed:[],skippedTransients:0,permanent:0};const e=[...this.entries].filter(t=>!t.undoneAt).reverse();for(const t of e){if(t.effect.kind==="transient"){n.skippedTransients+=1;continue}if(!isReversible(t.effect)){n.permanent+=1;continue}const i=await this.revertOne(t,r);if(i)n.failed.push({label:t.effect.label,error:i});else n.reverted+=1}this.emit();return n}}exports.EffectLedger=EffectLedger;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.diffLeaves=diffLeaves;exports.readLeaf=readLeaf;exports.restorePatch=restorePatch;var _digest=require("./digest");const isPlainRecord=o=>typeof o==="object"&&o!==null&&!Array.isArray(o);function diffLeaves(o,n){const e=[];const t=(r,s,u)=>{if(isPlainRecord(r)&&isPlainRecord(s)){const a=Object.keys(r);const i=Object.keys(s);if(a.length!==i.length||i.some(f=>!(f in r)))return false;return i.every(f=>t(r[f],s[f],[...u,f]))}const c=(0,_digest.digestOf)(s);if((0,_digest.digestOf)(r)!==c)e.push({path:u,before:r,wroteDigest:c});return true};return t(o,n,[])?e:void 0}function readLeaf(o,n){let e=o;for(const t of n){if(!isPlainRecord(e)||!(t in e))return{found:false};e=e[t]}return{found:true,value:e}}function restorePatch(o){const n={};for(const e of o){let t=n;e.path.forEach((r,s)=>{if(s===e.path.length-1)t[r]=e.before;else t=t[r]=isPlainRecord(t[r])?t[r]:{}})}return n}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.compactScreen=compactScreen;const ICON_ONLY=/^[\s-]*$/;const words=o=>o.label||o.text||"";function compactScreen(o){const s=o;if(!s||!Array.isArray(s.elements))return o;const r=new Map;for(const e of s.elements){const n=JSON.stringify([e.role,e.name.split("(")[0],words(e),e.testID,e.control,e.frame]);const t=r.get(n);if(!t||(e.nativeTag??0)>(t.nativeTag??0))r.set(n,e)}const l=[...r.values()].sort((e,n)=>(n.nativeTag??0)-(e.nativeTag??0));const i=[];let a;for(const e of l){const n=words(e);if(!e.testID&&!e.control&&ICON_ONLY.test(n))continue;if(e.name==="Text"&&!e.testID&&a&&a.nativeTag!==null&&e.nativeTag!==null&&a.nativeTag-e.nativeTag<=12&&words(a).includes(n)){continue}const t=[String(e.nativeTag),e.role??e.name.split("(")[0],JSON.stringify(e.label&&e.text&&e.text!==e.label?`${e.label} / ${e.text}`:n)];if(e.testID)t.push(`testID=${e.testID}`);if(e.control)t.push(`${e.control}=${JSON.stringify(e.value)}`);if(e.longPressable)t.push("longPress");if(!e.interactive)t.push("not tappable");if(e.frame&&(e.frame.y>=1||e.frame.y+e.frame.height<=0))t.push("off-screen");i.push(t.join(" \xB7 "));a=e}return{screen:s.screen,count:i.length,...s.hiddenBuoy?{hiddenBuoy:s.hiddenBuoy}:{},...s.lists?.length?{lists:s.lists.map(e=>`${e.owner??"A list"} shows ${e.items} items: that is the screen's own count of what the user can scroll through. Only ${e.listed} are listed above; the rest are further down.`)}:{},format:"nativeTag \xB7 role or component \xB7 label/text \xB7 extras. Newest first: the screen opened last is at the top, screens still mounted beneath it follow. Tap with tapElement {nativeTag}; off-screen rows scroll into view on tap.",elements:i}}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.effectFor=effectFor;var _policy=require("../policy/policy");var _digest=require("../effects/digest");const MAX_UNDO_JSON_CHARS=64e3;const cacheRead=n=>n&&typeof n==="object"?n:void 0;const QUERY_RESTORES={setQueryData:"query.invalidate",triggerError:"query.restoreError",triggerLoading:"query.restoreLoading",setOnline:"query.setOnline"};function effectFor(n,t,i,l,u,c){const r=(0,_policy.describeCall)(n,t,i);const d=l??{};if(n==="network"&&t.action==="upsertOverrideRule"){const e=d.rule;const a=e?.id??d.id;const s=i.id===void 0;if(a&&s)return{kind:"network-override",ruleId:a,label:r};return{kind:"state-write",toolId:n,action:t.action,label:r}}if(n==="impersonate"&&t.action==="startImpersonation"){return{kind:"impersonation",label:`${r} \u2014 stopping also clears cached app data`}}if(n==="storage"&&typeof i.key==="string"&&u){const e=t.action.startsWith("mmkv.");const a=e?"mmkv.set":"async.setItem";return{kind:"storage-write",toolId:n,action:a,key:i.key,instanceId:e&&typeof i.instanceId==="string"?i.instanceId:void 0,before:u.had?u.value:void 0,wrote:typeof i.value==="string"?i.value:void 0,removeAction:e?"mmkv.remove":"async.removeItem",label:r}}if(n==="query"&&t.action==="setQueryData"){const e=cacheRead(c?.before);const a=typeof i.queryHash==="string"?i.queryHash:e?.queryHash;if(e?.found===true&&typeof a==="string"){const s=e.hasData!==false&&e.data!==void 0;const f=s?e.data:void 0;if((0,_digest.jsonLength)(f)<=MAX_UNDO_JSON_CHARS){const o=cacheRead(c?.after);const y=o?.found===true&&o.hasData!==false?(0,_digest.digestOf)(o.data):void 0;return{kind:"query-write",queryHash:a,before:f,wroteDigest:y,label:r}}return{kind:"state-write",toolId:n,action:t.action,label:`${r} \u2014 prior value too large to hold for undo; undo with query.invalidate`}}}if(n==="query"&&typeof i.queryHash==="string"){const e=t.action==="triggerLoading"?"restoreLoading":t.action==="triggerError"?"restoreError":void 0;if(e){return{kind:"inverse-call",toolId:n,action:e,params:{queryHash:i.queryHash},label:r}}}if(n==="query"&&["refetch","invalidate","restoreLoading"].includes(t.action)){return{kind:"transient",label:r}}if(n==="network"&&(t.action==="setOverrideRuleEnabled"||t.action==="setOverridesEnabled")&&u?.had&&(u.value==="true"||u.value==="false")){const e=u.value==="true";const a=i.enabled===true;if(e===a)return void 0;const s=t.action==="setOverrideRuleEnabled"?{id:i.id,enabled:e}:{enabled:e};return{kind:"inverse-call",toolId:n,action:t.action,params:s,label:r}}if(n==="query"&&t.effect!=="read"){const e=QUERY_RESTORES[t.action];return{kind:"state-write",toolId:n,action:t.action,label:e?`${r} \u2014 undo with ${e}`:r}}if(t.effect==="destructive"){return{kind:"state-write",toolId:n,action:t.action,label:r}}return{kind:"transient",label:r}}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.effectFor=effectFor;var _policy=require("../policy/policy");var _digest=require("../effects/digest");var _storeDiff=require("../effects/storeDiff");const MAX_UNDO_JSON_CHARS=64e3;const cacheRead=t=>t&&typeof t==="object"?t:void 0;const QUERY_RESTORES={setQueryData:"query.invalidate",triggerError:"query.restoreError",triggerLoading:"query.restoreLoading",setOnline:"query.setOnline"};function effectFor(t,n,r,g,f,d){const i=(0,_policy.describeCall)(t,n,r);const y=g??{};if(t==="network"&&n.action==="upsertOverrideRule"){const e=y.rule;const a=e?.id??y.id;const o=r.id===void 0;if(a&&o)return{kind:"network-override",ruleId:a,label:i};return{kind:"state-write",toolId:t,action:n.action,label:i}}if(t==="impersonate"&&n.action==="startImpersonation"){return{kind:"impersonation",label:`${i} \u2014 stopping also clears cached app data`}}if(t==="storage"&&typeof r.key==="string"&&f){const e=n.action.startsWith("mmkv.");const a=e?"mmkv.set":"async.setItem";return{kind:"storage-write",toolId:t,action:a,key:r.key,instanceId:e&&typeof r.instanceId==="string"?r.instanceId:void 0,before:f.had?f.value:void 0,wrote:typeof r.value==="string"?r.value:void 0,removeAction:e?"mmkv.remove":"async.removeItem",label:i}}if(t==="query"&&n.action==="setQueryData"){const e=cacheRead(d?.before);const a=typeof r.queryHash==="string"?r.queryHash:e?.queryHash;if(e?.found===true&&typeof a==="string"){const o=e.hasData!==false&&e.data!==void 0;const s=o?e.data:void 0;if((0,_digest.jsonLength)(s)<=MAX_UNDO_JSON_CHARS){const u=cacheRead(d?.after);const c=u?.found===true&&u.hasData!==false?(0,_digest.digestOf)(u.data):void 0;return{kind:"query-write",queryHash:a,before:s,wroteDigest:c,label:i}}return{kind:"state-write",toolId:t,action:n.action,label:`${i} \u2014 prior value too large to hold for undo; undo with query.invalidate`}}}if(t==="zustand"&&n.action==="setState"&&typeof r.storeName==="string"){const e=u=>{const c=u&&typeof u==="object"?u:void 0;const l=c?.found===true?c.currentState:void 0;return l&&typeof l==="object"&&!Array.isArray(l)?l:void 0};const a=e(d?.before);const o=e(d?.after);const s=a&&o?(0,_storeDiff.diffLeaves)(a,o):void 0;if(s&&s.length===0)return void 0;if(s&&s.every(u=>u.path.length>0)&&(0,_digest.jsonLength)(s)<=MAX_UNDO_JSON_CHARS){return{kind:"store-write",toolId:t,storeName:r.storeName,leaves:s,label:i}}return{kind:"state-write",toolId:t,action:n.action,label:s?`${i} \u2014 too large to hold for undo`:`${i} \u2014 added or removed fields, so it can't be undone exactly`}}if(t==="query"&&typeof r.queryHash==="string"){const e=n.action==="triggerLoading"?"restoreLoading":n.action==="triggerError"?"restoreError":void 0;if(e){return{kind:"inverse-call",toolId:t,action:e,params:{queryHash:r.queryHash},label:i}}}if(t==="query"&&["refetch","invalidate","restoreLoading"].includes(n.action)){return{kind:"transient",label:i}}if(t==="network"&&(n.action==="setOverrideRuleEnabled"||n.action==="setOverridesEnabled")&&f?.had&&(f.value==="true"||f.value==="false")){const e=f.value==="true";const a=r.enabled===true;if(e===a)return void 0;const o=n.action==="setOverrideRuleEnabled"?{id:r.id,enabled:e}:{enabled:e};return{kind:"inverse-call",toolId:t,action:n.action,params:o,label:i}}if(t==="query"&&n.effect!=="read"){const e=QUERY_RESTORES[n.action];return{kind:"state-write",toolId:t,action:n.action,label:e?`${i} \u2014 undo with ${e}`:i}}if(n.effect==="destructive"){return{kind:"state-write",toolId:t,action:n.action,label:i}}return{kind:"transient",label:i}}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.SILENT_NUDGE=exports.PLAN_ONLY_NUDGE=void 0;exports.announcesUnstartedWork=announcesUnstartedWork;const PLAN_ONLY_NUDGE=exports.PLAN_ONLY_NUDGE="[Buoy, not the user: your last message said what you would do next but called no tool, so nothing ran and the turn would end there. If the task is not finished, make the next call now. If you are done, say so. If the user has to pick or answer, send the buoy_ui block (choice, confirm or input) now \u2014 text alone gives them nothing to tap.]";const SILENT_NUDGE=exports.SILENT_NUDGE="[Buoy, not the user: you ended the turn without a word to the user. Tell them in a sentence or two what you found or did \u2014 a card alone does not say it.]";const WAITS_ON_USER=/\b(if you|once you|when you|after you|only if|let me know|would you like|do you want|shall i|should i|tap|choose|approve)\b/i;const POINTS_AT_MISSING_CONTROL=/\b(?:tap|press|click|choose|select)\s+\**["“]?(?:go|approve|yes|continue|start|confirm)\b|ready for your (?:approval|go-ahead|ok)|(?:plan|steps|card|options?) (?:above|below)\b|approve the plan|for your (?:approval|go-ahead)|here['’]s the (?:plan|flow|steps)\b|\b(?:choose|pick|select) (?:what|which|one|from)\b[^.?!]*:/i;const ASKS_TO_PICK=/\b(?:pick|choose|select|tell me) (?:what|which)\b/i;const ANNOUNCES=/\b(i['’]ll|i will|let me|i['’]m going to|i am going to|next,? i|now i['’]ll|i['’]m about to)\b/i;const IN_PROGRESS=/\bi['’]m (?:now )?(?!sorry\b)[a-z]+ing\b/i;function announcesUnstartedWork(n){const t=n.trim();if(POINTS_AT_MISSING_CONTROL.test(t))return true;if(ASKS_TO_PICK.test(t))return true;if(/:\s*$/.test(t))return true;if(!t||/\?\s*$/.test(t))return false;const e=t.split(/(?<=[.!*])\s*(?=[A-Z“"])/).filter((i,a,s)=>!(a===s.length-1&&s.length>1&&/^[“"][^“”"]*[”"]\s*$/.test(i.trim())));const o=e.slice(-2).join(" ");const r=e.length>1&&!/\d/.test(e.at(-1)??"");if((IN_PROGRESS.test(e.at(-1)??"")||r&&IN_PROGRESS.test(e.at(-2)??""))&&!WAITS_ON_USER.test(o))return true;return ANNOUNCES.test(o)&&!WAITS_ON_USER.test(o)}