@buoy-gg/agent-core 7.0.45 → 7.0.47
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.
- package/lib/commonjs/catalog/catalog.g.js +2 -2
- package/lib/commonjs/catalog/catalog.source.json +96 -6
- package/lib/commonjs/catalog/catalog.types.g.js +1 -1
- package/lib/commonjs/catalog/snapshotReads.js +1 -1
- package/lib/commonjs/context/buildContextPack.js +1 -1
- package/lib/commonjs/engine/compactScreen.js +1 -1
- package/lib/commonjs/engine/planOnlyStop.js +1 -1
- package/lib/commonjs/engine/runAgentTurn.js +1 -1
- package/lib/commonjs/engine/systemPrompt.js +7 -1
- package/lib/commonjs/engine/verify.js +1 -1
- package/lib/commonjs/policy/redact.js +1 -1
- package/lib/module/catalog/catalog.g.js +2 -2
- package/lib/module/catalog/catalog.source.json +96 -6
- package/lib/module/catalog/catalog.types.g.js +1 -1
- package/lib/module/catalog/snapshotReads.js +1 -1
- package/lib/module/context/buildContextPack.js +1 -1
- package/lib/module/engine/compactScreen.js +1 -1
- package/lib/module/engine/planOnlyStop.js +1 -1
- package/lib/module/engine/runAgentTurn.js +1 -1
- package/lib/module/engine/systemPrompt.js +7 -1
- package/lib/module/engine/verify.js +1 -1
- package/lib/module/policy/redact.js +1 -1
- package/lib/typescript/catalog/catalog.g.d.ts +2 -2
- package/lib/typescript/catalog/catalog.types.g.d.ts +4 -4
- package/lib/typescript/engine/verify.d.ts +3 -1
- package/lib/web/index.mjs +44 -38
- 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.",
|
|
@@ -2711,7 +2801,7 @@
|
|
|
2711
2801
|
"properties": {
|
|
2712
2802
|
"token": {
|
|
2713
2803
|
"type": "string",
|
|
2714
|
-
"description": "Token id from getState's tokens[].
|
|
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[].
|
|
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": {
|
|
@@ -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":["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
|
+
"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 +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(
|
|
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 u=typeof t.level==="string"?SEVERITY[t.level]??0:0;const i=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)<u)return false;if(!i)return true;return String(n.message??"").toLowerCase().includes(i)});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 u=asArray(s.events).filter(n=>!(0,_redact.isSelfTraffic)(rec(n).url));const i=t.failedOnly===true;const d=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const e=u.filter(n=>{const o=rec(n);const a=typeof o.status==="number"?o.status:void 0;if(i&&!(o.error||a!==void 0&&a>=400))return false;if(!d)return true;return String(o.url??"").toLowerCase().includes(d)});const c=newest(e,num(t.limit,25));return{totalCaptured:u.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 u of asArray(r)){if(t.length>=s)break;const i=rec(u);if(i.isInternal!==true&&typeof i.path==="string")t.push(i.path);flattenRoutes(i.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 u=typeof t.key==="string"?t.key.toLowerCase():void 0;const i=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=>!u||JSON.stringify(e.key??"").toLowerCase().includes(u)).sort((e,c)=>String(c.at).localeCompare(String(e.at)));const d=num(t.limit,20);return{events:i.slice(0,d),total:i.length,returned:Math.min(d,i.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 u=typeof t.type==="string"?t.type.toLowerCase():void 0;const i=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=>!u||String(e.type).toLowerCase().includes(u)).sort((e,c)=>Number(c.at)-Number(e.at));const d=num(t.limit,20);return{actions:i.slice(0,d),total:i.length,returned:Math.min(d,i.length)}}function projectStoreChanges(r,t,s,u){const i=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[u]==="string"?String(t[u]).toLowerCase():void 0;const e=asArray(rec(r).changes).map(rec).map(n=>({id:n.id,at:n.timestamp,[u]:n[s],changed:n.changedKeys,summary:n.diffSummary||void 0,...s==="storeName"?{partial:i(n.partial)}:{value:i(n.valuePreview)}})).filter(n=>!d||String(n[u]??"").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 u=rec(s.sitemap);const i=asArray(s.stack).map(rec);const d=asArray(s.events).map(rec);const e=i.map(a=>({name:a.name,path:a.pathname,focused:a.isFocused===true?true:void 0}));const c=d.slice(0,num(t.limit,15)).map(a=>({at:a.timestamp,from:a.previousPathname,to:a.pathname}));const n=i.find(a=>a.isFocused===true)??i[i.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(u.routes),sitemapSource:u.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 u=asArray(s.envelopes).map(rec);const i=typeof t.type==="string"?t.type:void 0;const d=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const e=u.filter(n=>{const o=asArray(n.items).map(rec);if(i&&!o.some(a=>a.type===i))return false;if(!d)return true;return o.some(a=>String(a.summary??"").toLowerCase().includes(d))});const c=newest(e,num(t.limit,10));return{status:s.status,totalCaptured:u.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 a=rec(o);return{type:a.type,summary:a.summary,spanCount:a.spanCount,bytes:a.bytes}})})),drops:(()=>{const n=rec(s.drops);const o=asArray(n.recent).map(rec);const a=l=>l.category==="span"||l.category==="transaction";const p=[...newest(o.filter(l=>!a(l)),8),...newest(o.filter(a),3)];const f=p.map(l=>({at:l.timestamp,reason:l.reason,category:l.category,quantity:l.quantity,detail:typeof l.detail==="string"?l.detail.slice(0,200):l.detail}));return f.length||n.summary?{summary:n.summary,recent:f}: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=
|
|
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.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
|
|
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.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(
|
|
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)}
|
|
@@ -5,7 +5,7 @@ Resend ONLY the corrected block. Do not rewrite your answer \u2014 whatever you
|
|
|
5
5
|
${H.errors.join("\n")}
|
|
6
6
|
|
|
7
7
|
Expected schema:
|
|
8
|
-
${JSON.stringify(f.params)}`,isError:true});continue}const J=(0,_normalizeParams.aliasNote)(l.notes);const M=(0,_policy.describeCall)(r,f,p);const Y=(0,_policy.decide)({descriptor:f,toolId:r,policy:P,isRelease:q});const K=s=>[{type:"tool-start",id:e.id,toolId:r,action:i,label:M,description:f.summary,params:p,effect:f.effect},{type:"tool-end",id:e.id,ok:false,summary:s,durationMs:0}];if(Y.verdict==="refuse"){for(const s of K("refused"))yield s;k.push({toolCallId:e.id,content:Y.reason,isError:true});continue}const ue=Y.verdict==="needs-approval";if(Be&&!_e&&(f.effect!=="read"||ue)){yield*ze()}if(Ee&&(f.effect!=="read"||ue)){for(const s of K("declined"))yield s;k.push({toolCallId:e.id,content:L.get(e.id)===false?declinedResult(B.get(e.id)):"Not run \u2014 the user declined another change in this same batch, so none of it was applied. Ask what they would prefer before proposing it again.",isError:true});continue}if(ue){let s;if(L.has(e.id)){s=L.get(e.id)}else if(A?.has(`${r}.${i}`)){s=true;L.set(e.id,true)}else{yield{type:"approval-required",id:e.id,toolId:r,action:i,label:M,description:f.summary,reason:Y.reason};const m=(0,_realNow.realNow)();const S=await readTargetDigest({toolId:r,action:i,params:p,dispatch:a,catalog:n});const R=readAnswer(U?await U({id:e.id,toolId:r,action:i,label:M,description:f.summary,reason:Y.reason,params:p,targetDigest:S}):false);X+=(0,_realNow.realNow)()-m;s=R.approved;L.set(e.id,s);if(R.trust)A?.add(`${r}.${i}`);if(R.reason)B.set(e.id,R.reason)}if(!s){for(const m of K("declined"))yield m;k.push({toolCallId:e.id,content:declinedResult(B.get(e.id)),isError:true});continue}}if(_askGate.ACQUISITION_CALLS.has(`${r}.${i}`))ve=true;if(r==="ask-buoy"&&i==="openProcedure")we=true;else if(f.effect!=="read")Te=true;yield{type:"tool-start",id:e.id,toolId:r,action:i,label:M,description:f.summary,params:p,effect:f.effect};const $e=(0,_realNow.realNow)();const Ce=await captureBefore(f,r,p,a);const fe=await captureStoreState(f,r,p,a);if($?.aborted){for(const s of K("stopped"))yield s;k.push({toolCallId:e.id,content:"Not run \u2014 the user pressed Stop before this call started.",isError:true});continue}const Oe=(0,_policy.decide)({descriptor:f,toolId:r,policy:P,isRelease:q});if(Oe.verdict==="refuse"){for(const s of K("refused"))yield s;k.push({toolCallId:e.id,content:Oe.reason,isError:true});continue}try{const s=i===_snapshotReads.SNAPSHOT_ACTION?(0,_snapshotReads.projectSnapshot)(r,await requireSnapshot(y,r),p):r==="ask-buoy"?await runAskBuoyAction(i,v,a,D,p,j):await(Ie.get(e.id)??a(r,i,p));const m=(0,_redact.redact)((0,_redact.stripSelfTraffic)(s));const S=reportsFailure(s);const R=fullText(r==="highlight-updates"&&i==="describeScreen"&&!S?(0,_compactScreen.compactScreen)(m):m);const pe=D&&!S&&!(r==="ask-buoy"&&i==="retrieve")?D.stash({toolId:r,action:i,params:p,capturedAt:(0,_realNow.realNow)(),text:R}):void 0;const Q=encodeResult(R,pe);const Fe=Q.length>MAX_TRACE_RESULT_CHARS?`${Q.slice(0,MAX_TRACE_RESULT_CHARS)}\u2026`:Q;yield{type:"tool-end",id:e.id,ok:!S,summary:summarise(s),durationMs:(0,_realNow.realNow)()-$e,result:Fe};const he=fe===void 0||S?void 0:await captureStoreState(f,r,p,a);if(f.effect!=="read"&&r!=="ask-buoy"&&!S){const ye=v.noteExternalRevert(r,i,p);const qe=(0,_effectFor.effectFor)(r,f,p,s,Ce,{before:fe,after:he});if(qe&&!ye){const xe=v.record(r,i,qe,Le);if(xe)xe.callId=e.id}}const Pe=S?void 0:(0,_receipts.projectReceipt)({toolId:r,action:i,params:p,result:m,before:fe??Ce,after:he,effect:f.effect});if(Pe)yield{type:"block",block:Pe,forToolCallId:e.id};const oe=f.effect!=="read"&&!S?await(0,_verify.verifyOutcome)({toolId:r,action:i,params:p,result:s,dispatch:a,signal:$,after:he}):void 0;if(oe)yield{type:"tool-verified",id:e.id,verification:oe};for(const ye of Q.match(Me)??[])se.add(ye);k.push({toolCallId:e.id,content:Q+ownedKeyNote(r,f,p,E)+J+(oe?(0,_verify.verificationTrailer)(oe):"")+(Ae.has(e.id)?_textToolCalls.SALVAGE_NOTE:""),...pe?{ref:pe}:{}});if(r==="app"&&(i==="reloadApp"||i==="reload")){Re=true}}catch(s){const m=s instanceof Error?s.message:String(s);yield{type:"tool-end",id:e.id,ok:false,summary:m,durationMs:(0,_realNow.realNow)()-$e};k.push({toolCallId:e.id,content:`Failed: ${m}`,isError:true})}}if(w.length){z.push(callSignature(w));if(z.length>LOOP_REPEATS)z.shift();const e=!ke&&z.length===LOOP_REPEATS&&z.every(d=>d===z[0]);if(e){ke=true;const d=k[k.length-1];if(d)d.content=`${d.content}
|
|
8
|
+
${JSON.stringify(f.params)}`,isError:true});continue}const J=(0,_normalizeParams.aliasNote)(l.notes);const M=(0,_policy.describeCall)(r,f,p);const Y=(0,_policy.decide)({descriptor:f,toolId:r,policy:P,isRelease:q});const K=s=>[{type:"tool-start",id:e.id,toolId:r,action:i,label:M,description:f.summary,params:p,effect:f.effect},{type:"tool-end",id:e.id,ok:false,summary:s,durationMs:0}];if(Y.verdict==="refuse"){for(const s of K("refused"))yield s;k.push({toolCallId:e.id,content:Y.reason,isError:true});continue}const ue=Y.verdict==="needs-approval";if(Be&&!_e&&(f.effect!=="read"||ue)){yield*ze()}if(Ee&&(f.effect!=="read"||ue)){for(const s of K("declined"))yield s;k.push({toolCallId:e.id,content:L.get(e.id)===false?declinedResult(B.get(e.id)):"Not run \u2014 the user declined another change in this same batch, so none of it was applied. Ask what they would prefer before proposing it again.",isError:true});continue}if(ue){let s;if(L.has(e.id)){s=L.get(e.id)}else if(A?.has(`${r}.${i}`)){s=true;L.set(e.id,true)}else{yield{type:"approval-required",id:e.id,toolId:r,action:i,label:M,description:f.summary,reason:Y.reason};const m=(0,_realNow.realNow)();const S=await readTargetDigest({toolId:r,action:i,params:p,dispatch:a,catalog:n});const R=readAnswer(U?await U({id:e.id,toolId:r,action:i,label:M,description:f.summary,reason:Y.reason,params:p,targetDigest:S}):false);X+=(0,_realNow.realNow)()-m;s=R.approved;L.set(e.id,s);if(R.trust)A?.add(`${r}.${i}`);if(R.reason)B.set(e.id,R.reason)}if(!s){for(const m of K("declined"))yield m;k.push({toolCallId:e.id,content:declinedResult(B.get(e.id)),isError:true});continue}}if(_askGate.ACQUISITION_CALLS.has(`${r}.${i}`))ve=true;if(r==="ask-buoy"&&i==="openProcedure")we=true;else if(f.effect!=="read")Te=true;yield{type:"tool-start",id:e.id,toolId:r,action:i,label:M,description:f.summary,params:p,effect:f.effect};const $e=(0,_realNow.realNow)();const Ce=await captureBefore(f,r,p,a);const fe=await captureStoreState(f,r,p,a);if($?.aborted){for(const s of K("stopped"))yield s;k.push({toolCallId:e.id,content:"Not run \u2014 the user pressed Stop before this call started.",isError:true});continue}const Oe=(0,_policy.decide)({descriptor:f,toolId:r,policy:P,isRelease:q});if(Oe.verdict==="refuse"){for(const s of K("refused"))yield s;k.push({toolCallId:e.id,content:Oe.reason,isError:true});continue}try{const s=i===_snapshotReads.SNAPSHOT_ACTION?(0,_snapshotReads.projectSnapshot)(r,await requireSnapshot(y,r),p):r==="ask-buoy"?await runAskBuoyAction(i,v,a,D,p,j):await(Ie.get(e.id)??a(r,i,p));const m=(0,_redact.redact)((0,_redact.stripSelfTraffic)(s));const S=reportsFailure(s);const R=fullText(r==="highlight-updates"&&i==="describeScreen"&&!S?(0,_compactScreen.compactScreen)(m):m);const pe=D&&!S&&!(r==="ask-buoy"&&i==="retrieve")?D.stash({toolId:r,action:i,params:p,capturedAt:(0,_realNow.realNow)(),text:R}):void 0;const Q=encodeResult(R,pe);const Fe=Q.length>MAX_TRACE_RESULT_CHARS?`${Q.slice(0,MAX_TRACE_RESULT_CHARS)}\u2026`:Q;yield{type:"tool-end",id:e.id,ok:!S,summary:summarise(s),durationMs:(0,_realNow.realNow)()-$e,result:Fe};const he=fe===void 0||S?void 0:await captureStoreState(f,r,p,a);if(f.effect!=="read"&&r!=="ask-buoy"&&!S){const ye=v.noteExternalRevert(r,i,p);const qe=(0,_effectFor.effectFor)(r,f,p,s,Ce,{before:fe,after:he});if(qe&&!ye){const xe=v.record(r,i,qe,Le);if(xe)xe.callId=e.id}}const Pe=S?void 0:(0,_receipts.projectReceipt)({toolId:r,action:i,params:p,result:m,before:fe??Ce,after:he,effect:f.effect});if(Pe)yield{type:"block",block:Pe,forToolCallId:e.id};const oe=f.effect!=="read"&&!S?await(0,_verify.verifyOutcome)({toolId:r,action:i,params:p,result:s,dispatch:a,readSnapshot:y,signal:$,after:he}):void 0;if(oe)yield{type:"tool-verified",id:e.id,verification:oe};for(const ye of Q.match(Me)??[])se.add(ye);k.push({toolCallId:e.id,content:Q+ownedKeyNote(r,f,p,E)+J+(oe?(0,_verify.verificationTrailer)(oe):"")+(Ae.has(e.id)?_textToolCalls.SALVAGE_NOTE:""),...pe?{ref:pe}:{}});if(r==="app"&&(i==="reloadApp"||i==="reload")){Re=true}}catch(s){const m=s instanceof Error?s.message:String(s);yield{type:"tool-end",id:e.id,ok:false,summary:m,durationMs:(0,_realNow.realNow)()-$e};k.push({toolCallId:e.id,content:`Failed: ${m}`,isError:true})}}if(w.length){z.push(callSignature(w));if(z.length>LOOP_REPEATS)z.shift();const e=!ke&&z.length===LOOP_REPEATS&&z.every(d=>d===z[0]);if(e){ke=true;const d=k[k.length-1];if(d)d.content=`${d.content}
|
|
9
9
|
|
|
10
10
|
${LOOP_WARNING}`}}h.push({role:"tool-results",results:k});if(ce){yield{type:"awaiting-user",blockId:ce};yield{type:"done",stopReason:"awaiting-user"};return h}if(Re){yield{type:"done",stopReason:"realm-died"};return h}}yield{type:"block",block:{id:`cap${(0,_realNow.realNow)()}`,kind:"notice",tone:"warning",text:`Stopped after ${V} steps to avoid looping.`,actions:[{label:"Continue",primary:true,send:"Continue where you left off."}]}};yield{type:"done",stopReason:"step-cap"};return h}function ownedKeyNote(t,o,n,c){if(t!=="storage"||o.effect==="read")return"";if(!c||typeof n.key!=="string")return"";const u=c[n.key];if(!u)return"";return`
|
|
11
11
|
|
|
@@ -36,6 +36,12 @@ You can read the app's live state and change it, through the Buoy tools availabl
|
|
|
36
36
|
\xB7 Storage (storage tool \u2014 async.*, mmkv.*, secure.*): only what the app reads at its NEXT launch. Use it when nothing live owns the value, or the user means "on restart".
|
|
37
37
|
- TO FIND THE OWNER of what the user is looking at, read the RIGHT NOW block at the end of this prompt: the current route and its params, and the queries. The query marked likelyScreen backs the focused screen \u2014 edit that one. "onScreen" alone only means mounted somewhere in the stack, and a background screen keeps its query mounted too, so if two queries share the route's id (a detail page and a shop page for the same item) pick the likelyScreen one, never another just because its name matches the word the user said. If RIGHT NOW lacks what you need, read it \u2014 query.listQueries, zustand.listStores, network.getSnapshot \u2014 before deciding.
|
|
38
38
|
- Developer notes tell you what data MEANS. They do not tell you where the thing on screen came from. When a note describes one store and RIGHT NOW shows the data on screen in a query, the query is the owner. An empty collection is never evidence that something belongs in it.
|
|
39
|
+
- A FORM ISN'T CHANGED UNTIL IT'S SAVED. When you type into a field or flip a switch on a screen that has a Save (or Submit, Update, Done) button, the change lives only on screen until that button is pressed. Press it, then check the saved result (the request it sent, or the value after a reload) before you say it's done.
|
|
40
|
+
- SAY ONLY WHAT YOU RAN. Describe a simulation, test or check as done only if a tool call in this conversation ran it. Requests you saw while the app was active are not "the background simulation"; if you didn't run it, run it or say you didn't.
|
|
41
|
+
- LEAVE WHAT THEY ASKED FOR IN PLACE. When the user asks you to make the app show or be something (a countdown near zero, a frozen clock, a failing endpoint, a user to impersonate), that state IS the result: leave it on when you finish. Never reset, restore or delete it on your own to "clean up" \u2014 say it is still on and how to undo it. Clean up only scaffolding the user never asked for, like a one-off rule you made to look something up.
|
|
42
|
+
- WHAT THE USER SEES WINS. When the question is about what the app shows ("which offers expired?", "what's on screen?"), answer from the screen after your change: describeScreen, and its \`lists\` counts for long lists. Data only says what SHOULD show. If the screen and the data disagree (the data says six ended, the list still holds 11), that gap is the answer: say both, and that the screen is wrong.
|
|
43
|
+
- NAME THE STEP, NOT JUST THE MISMATCH. When a computed value is wrong, put it next to the raw value it came from and say what conversion turns one into the other. "1,250 Cal" read as 1 is a number parse stopping at the comma; "1,250" turning into NaN is text treated as a number; a date one hour off is a time zone read wrong. "The data is wrong" is where the answer starts, not where it ends.
|
|
44
|
+
- ONE WORD, SEVERAL STORES. The same word can name different things in different layers: a zustand cart's \`offer\` field and a redux \`offers\` slice are not the same data. Before you answer "none", "empty" or "not there", read EVERY place the app summary lists under that name \u2014 zustandStores keys, reduxSlices and jotaiAtoms \u2014 not just the first one you open. The screen is the tiebreaker: if it shows something as added or selected, find the store that says so.
|
|
39
45
|
- If two edits would both satisfy the request and the difference matters (change the cache now vs. make the API return it), do the one the user most likely meant and mention the other in one line \u2014 or use buoy_ui choice when you genuinely cannot tell.
|
|
40
46
|
- REACT QUERY TESTING MOVES. Change what a screen shows \u2192 query.setQueryData with the row's queryKey ARRAY and merge:true. merge is a TYPED LEAF EDIT, exactly like the React Query devtools value editor: you may only change the VALUE of a field that ALREADY EXISTS, to the SAME type. You cannot add or remove object fields, change a string to a number/boolean/array/object, or null out a list or object the screen renders. A list MAY gain or lose items, but any item you send must have the SAME fields as the items already in the list. To change ONE ITEM inside a list, address it by its own id instead of resending the list: if rows carry an id, send {"results":{"pikachu":{"name":"test123"}}} \u2014 items you don't name are untouched, and this is the only form that is correct when the read was capped and you did not see every row. Sending a real ARRAY replaces the whole list, so a one-item array deletes the rest; only do that when you have read every item. READ getQueryData FIRST \u2014 always \u2014 and look at the shape sketch it returns. Then say the edit ONE of two ways. (a) path plus value, where path is copied from the shape you just read: if the payload is {name, id, stats} the path is "name"; if it is {item:{name}} the path is "item.name"; for one row of a list it is "results[name=pikachu].url". (b) data plus merge:true, holding ONLY the field(s) you're changing, nested to match that same real shape \u2014 if the value is {item:{name}} send {"item":{"name":"test123"}}, never {name}. Either way the shape you write has to be the shape that is THERE. A path saves you from mis-nesting; it does not save you from aiming at a wrapper the data does not have, and that is refused too. If merge is refused it tells you exactly which field and why, and when the problem was the depth it hands back suggestedData: the same edit rebuilt correctly, which you can send straight back as data. Fix it that way; never reach for force (force writes raw and can crash the screen). Show the error state \u2192 triggerError; undo with restoreError (which also drops the cached data and refetches). A screen that refetches on a timer (listQueries shows refetchEveryMs) replaces that error \u2014 and any setQueryData edit or loading pin \u2014 within seconds; for anything that must stay, change the API response with a network override instead. Either way React Query retries a failing fetch about 3 times over ~7 seconds and keeps showing the old data meanwhile, so wait ~10 seconds before deciding the error screen did not appear. If the API is failing and the screen STILL shows the old data after that, that is how this screen handles a failure: tell the user so \u2014 it is often the bug they are looking for \u2014 instead of clearing caches and reloading to force an error view the app may not have. Show the loading state \u2192 triggerLoading; undo with restoreLoading. A RULE NEEDS NO CAPTURED REQUEST: when you know the path (from the notes, the screen, or a call seen earlier) write urlPattern like "*/api/refunds" with its method, and the rule catches the call when the app makes it. Never press a button that submits a real action \u2014 a refund, an order, a payment \u2014 just to learn its endpoint. Making a call fail, time out or slow down is a rule, not a reason to open a procedure or carry out the action yourself; do that only when the user asks you to. FEATURE FLAGS and admin or beta modes usually arrive in ONE request at launch \u2014 a flags, config or remote-config call (LaunchDarkly, Statsig, Firebase Remote Config, an app's own /flags). To turn a feature or role on, find that request in the captured traffic, override its response with the flag changed (bodyPath on just that flag), then invalidate the query that holds it. Make the API itself answer differently \u2192 network.upsertOverrideRule with fromRequestId (seeds the rule from the real response) and bodyPatch holding ONLY the fields that change (to change one row of a list in the response, address it by its id: {"results":{"pikachu":{"name":"test123"}}}), then query.invalidate so the screen picks it up; delete the rule when done. Fresh data \u2192 invalidate, so the app's own screens refetch while the old data stays visible. "Throw it away / load from scratch" \u2192 reset, which also shows the loading state.
|
|
41
47
|
- NEVER write a storage key that backs a live state store. The grounding below marks those with "persistsTo". Writing the key directly changes nothing on screen, and the next time the app touches that store it writes its own copy back over you. Use the store's tool instead \u2014 and if the key has already been written, zustand.rehydrate makes the store re-read it.
|
|
@@ -74,5 +80,5 @@ ${u.map(o=>`- ${o.id} \u2014 ${o.summary}`).join("\n")}`)}if(e?.extra)t.push(e.e
|
|
|
74
80
|
YOU HAVE ALREADY CHANGED THIS APP, this conversation. What the app shows may be your own doing, not the server's or the user's \u2014 say so rather than reporting it as found state.
|
|
75
81
|
${e.slice(-s).map(h=>`- ${h.label}${h.undone?" (undone)":""}`).join("\n")}${e.length>s?`
|
|
76
82
|
- \u2026and ${e.length-s} earlier`:""}`:"";return`RIGHT NOW
|
|
77
|
-
Re-read just before this message. \`currentRoute\` is the screen the user sees, with its params. A query marked \`likelyScreen:true\` backs THAT screen \u2014 edit that one. \`onScreen:true\` only means the query is mounted somewhere in the nav stack (a stack navigator keeps the screen beneath the current one mounted, so several queries can be onScreen at once); the one tied to \`currentRoute\` is \`likelyScreen\`. If nothing is marked likelyScreen, match \`currentRoute\` yourself \u2014 and never pick a query just because its key contains the word the user said. \`recentRequests\` are the newest API calls (ids work with network.getEventBody and upsertOverrideRule.fromRequestId). \`networkOverrides\` lists armed rules: a response that disagrees with the server may be one of these, not a bug. \`networkCapture.recording:false\` means the request recorder is OFF right now: recentRequests is only what was captured earlier, an empty list says nothing about the app, and you say that in your first sentence rather than reading the list three times to find out. \`appClock\` appears only while the Clock tool overrides the app's clock: \`appTime\` is what the app thinks now is (\`offsetMs\` ahead of real time, \`mode\` frozen or running, \`rate\` if sped up), so judge expiry, countdowns and "last seen" against appTime, and say the clock is overridden before calling a date bug. \`appLocation\` appears only while the Location tool overrides where the app is: distances, "nearby" lists and geofence check-ins are computed from that position (or \`noFix\` when the signal or location services are off), so say the location is simulated before calling one of them a bug. \`appPermissions\` appears only while the Permissions tool overrides a permission: each entry is what the app sees for that permission (\`denied\`, \`limited\`\u2026, with the real OS answer when it differs), so a Settings prompt, a missing camera view or an unsorted store list may be the override; say so before calling it a bug.
|
|
83
|
+
Re-read just before this message. \`currentRoute\` is the screen the user sees, with its params. A query marked \`likelyScreen:true\` backs THAT screen \u2014 edit that one. \`onScreen:true\` only means the query is mounted somewhere in the nav stack (a stack navigator keeps the screen beneath the current one mounted, so several queries can be onScreen at once); the one tied to \`currentRoute\` is \`likelyScreen\`. If nothing is marked likelyScreen, match \`currentRoute\` yourself \u2014 and never pick a query just because its key contains the word the user said. \`recentRequests\` are the newest API calls (ids work with network.getEventBody and upsertOverrideRule.fromRequestId). \`networkOverrides\` lists armed rules: a response that disagrees with the server may be one of these, not a bug. \`networkCapture.recording:false\` means the request recorder is OFF right now: recentRequests is only what was captured earlier, an empty list says nothing about the app, and you say that in your first sentence rather than reading the list three times to find out. \`appClock\` appears only while the Clock tool overrides the app's clock: \`appTime\` is what the app thinks now is (\`offsetMs\` ahead of real time, \`mode\` frozen or running, \`rate\` if sped up), so judge expiry, countdowns and "last seen" against appTime, and say the clock is overridden before calling a date bug. \`now\` is the real date and time on the device (\`timeZone\` is its zone): a date the user names without a year is in that year, and "next Monday" counts from it. \`impersonating\` appears only while the Impersonate tool acts as another user: every API call carries that user's id (\`header\`), so the server answers as them; asked who the user is, say Buoy is impersonating that person (and whether it is \`paused\`) before anything else. \`appLocation\` appears only while the Location tool overrides where the app is: distances, "nearby" lists and geofence check-ins are computed from that position (or \`noFix\` when the signal or location services are off), so say the location is simulated before calling one of them a bug. \`appPermissions\` appears only while the Permissions tool overrides a permission: each entry is what the app sees for that permission (\`denied\`, \`limited\`\u2026, with the real OS answer when it differs), so a Settings prompt, a missing camera view or an unsorted store list may be the override; say so before calling it a bug.
|
|
78
84
|
${r}${i}`}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.VERIFIERS=void 0;exports.atPath=atPath;exports.isSubset=isSubset;exports.verificationTrailer=verificationTrailer;exports.verifyOutcome=verifyOutcome;var _realNow=require("../realNow");const rec=e=>e&&typeof e==="object"&&!Array.isArray(e)?e:void 0;function sleep(e,r){return new Promise(i=>{if(r?.aborted)return i();const t=(0,_realNow.realSetTimeout)(n,e);function n(){(0,_realNow.realClearTimeout)(t);r?.removeEventListener("abort",n);i()}r?.addEventListener("abort",n,{once:true})})}async function poll(e,r,i,t,n){const d=(0,_realNow.realNow)()+i;for(;;){const a=await e();if(r(a))return{value:a,satisfied:true};if(n?.aborted||(0,_realNow.realNow)()+t>d)return{value:a,satisfied:false};await sleep(t,n)}}function isSubset(e,r){if(e===null||typeof e!=="object"||Array.isArray(e))return JSON.stringify(e)===JSON.stringify(r);const i=rec(r);if(!i)return false;for(const[t,n]of Object.entries(e)){if(!(t in i))return false;if(!isSubset(n,i[t]))return false}return true}function atPath(e,r){const i=r.replace(/^\/+/,"").match(/\[[^\]]*\]|[^.[\]/]+/g)??[];let t=e;for(const n of i){if(t===null||typeof t!=="object")return void 0;if(n.startsWith("[")){const d=n.slice(1,-1).trim();if(!Array.isArray(t))return void 0;const a=d.indexOf("=");if(a<0){t=t[Number(d)]}else{const s=d.slice(0,a).trim();const o=d.slice(a+1).trim().replace(/^["']|["']$/g,"");t=t.find(u=>u&&typeof u==="object"&&String(u[s])===o)}}else{t=t[n]}}return t}const brief=e=>{const r=JSON.stringify(e);return r===void 0?"undefined":r.length>80?`${r.slice(0,77)}\u2026`:r};const storageSetItem=async({params:e,dispatch:r})=>{if(typeof e.key!=="string")return void 0;const i=await r("storage","async.getItem",{key:e.key});const t=typeof i==="string"?i:rec(i)?.value;const n=typeof e.value==="string"?e.value:JSON.stringify(e.value);if(t===n)return{status:"verified",detail:`Read back: "${e.key}" now holds the written value.`};return{status:"failed",detail:`Read back after the write: "${e.key}" holds ${brief(t)}, not what was written. The write did not take.`}};const zustandSetState=async({params:e,dispatch:r,after:i})=>{if(typeof e.storeName!=="string")return void 0;const t=rec(i!==void 0?i:await r("zustand","getStoreState",{storeName:e.storeName}));if(!t||t.found===false)return{status:"failed",detail:`Read back: store "${e.storeName}" could not be read after the write.`};const n=t.currentState;if(typeof e.path==="string"){const s=atPath(n,e.path);if(JSON.stringify(s)===JSON.stringify(e.value))return{status:"verified",detail:`Read back: ${e.storeName}.${e.path} is now ${brief(s)}.`};return{status:"failed",detail:`Read back: ${e.storeName}.${e.path} is ${brief(s)}, not ${brief(e.value)}. The store did not take the write.`}}const d=rec(e.state);if(!d)return void 0;if(isSubset(d,n))return{status:"verified",detail:`Read back: "${e.storeName}" now holds the written fields (${Object.keys(d).join(", ")}).`};const a=Object.keys(d).filter(s=>!isSubset(d[s],rec(n)?.[s]));return{status:"failed",detail:`Read back: "${e.storeName}" does not hold the written value for ${a.join(", ")}. The store did not take the write \u2014 read it before deciding what to do.`}};const querySetQueryData=async({params:e,dispatch:r,after:i,signal:t})=>{const n=e.queryHash!==void 0?{queryHash:e.queryHash}:e.queryKey!==void 0?{queryKey:e.queryKey}:void 0;if(!n)return void 0;const d=rec(i!==void 0?i:await r("query","getQueryData",n));if(!d||d.found===false)return{status:"failed",detail:"Read back: the query could not be read after the write."};const a=d.data;if(typeof e.path==="string"){const s=atPath(a,e.path);if(JSON.stringify(s)===JSON.stringify(e.value))return outlasts(e,r,`Read back: the cached ${e.path} is now ${brief(s)}.`,t);return{status:"failed",detail:`Read back: the cached ${e.path} is ${brief(s)}, not ${brief(e.value)}. The cache did not take the write \u2014 the app may have refetched over it.`}}if(e.data===void 0)return void 0;if(isSubset(e.data,a))return outlasts(e,r,"Read back: the cache now holds the written data.",t);return{status:"failed",detail:"Read back: the cache does not hold the written data \u2014 the app may have refetched over it, or the shape differed."}};async function outlasts(e,r,i,t){const n=typeof e.queryHash==="string"?e.queryHash:Array.isArray(e.queryKey)?JSON.stringify(e.queryKey):void 0;const d=n?await queryRow(r,n).catch(()=>void 0):void 0;const a=typeof d?.refetchEveryMs==="number"?d.refetchEveryMs:void 0;if(!n||!a||a>1e4)return{status:"verified",detail:i};await sleep(a+750,t);if(t?.aborted)return{status:"verified",detail:i};const s=rec(await r("query","getQueryData",{queryHash:n}).catch(()=>void 0));if(!s||s.found===false)return{status:"verified",detail:i};const o=typeof e.path==="string"?JSON.stringify(atPath(s.data,e.path))===JSON.stringify(e.value):e.data!==void 0&&isSubset(e.data,s.data);if(o)return{status:"verified",detail:`${i} It is still there after the screen's ${a} ms refetch.`};return{status:"failed",detail:`${i} But this screen refetches every ${a} ms, and it already has: the server's data replaced the edit. For a change that stays, override the API response \u2014 network.upsertOverrideRule (fromRequestId + bodyPatch, or a full body), then query.invalidate.`}}const navigate=async({params:e,dispatch:r,signal:i})=>{if(typeof e.path!=="string")return void 0;const t=e.path.split("?")[0];const{value:n,satisfied:d}=await poll(async()=>rec(await r("route-events","getCurrentRoute",{})),s=>typeof s?.path==="string"&&(s.path===t||s.path.split("?")[0]===t),2e3,250,i);if(d)return{status:"verified",detail:`The app is now on ${t}.`};const a=typeof n?.path==="string"?n.path:"an unknown route";return{status:"failed",detail:`Two seconds after navigating, the app is on ${a}, not ${t}. Do not navigate again blindly \u2014 check the route exists (the sitemap) and whether something redirected.`}};const upsertOverrideRule=async({params:e,result:r,dispatch:i,signal:t})=>{const n=rec(rec(r)?.rule)?.id??rec(e.rule)?.id;if(typeof n!=="string")return void 0;const d=async()=>{const o=rec(await i("network","listOverrideRules",{}));const u=Array.isArray(o?.rules)?o.rules:[];return u.map(rec).find(f=>f?.id===n)};const{value:a,satisfied:s}=await poll(d,o=>typeof o?.hits==="number"&&o.hits>0,3e3,500,t);if(!a)return{status:"failed",detail:"The rule is not in the override list after the write. It did not install."};if(a.enabled===false)return{status:"failed",detail:"The rule is installed but disabled, so it will not fire."};if(s)return{status:"verified",detail:`The rule has matched ${a.hits} request${a.hits===1?"":"s"} \u2014 the app has already received the overridden response.`};return{status:"unverified",detail:"The rule is installed, but no request has matched it yet \u2014 the app is still showing whatever it loaded before. A refetch or a visit to the screen that fetches this will exercise it; until then, do not say the screen shows the override."}};async function queryRow(e,r){const i=rec(await e("query","listQueries",{limit:100}));const t=Array.isArray(i?.queries)?i.queries:[];return t.map(rec).find(n=>n?.queryHash===r)}function pinWatcher(e,r,i){return async({params:t,dispatch:n,signal:d})=>{if(typeof t.queryHash!=="string")return void 0;const a=async()=>{const u=await queryRow(n,t.queryHash);return typeof u?.status==="string"?u.status:void 0};const{value:s,satisfied:o}=await poll(a,u=>u!==void 0&&!e(u),3e3,500,d);if(s===void 0)return void 0;if(!o)return{status:"verified",detail:`Still ${r} three seconds later.`};return{status:"failed",detail:`Within three seconds the app refetched and the query is "${s}" again: this screen polls, so a cache ${r} pin cannot last. ${i}`}}}const queryTriggerError=pinWatcher(e=>e==="error","error","For an error that stays, make the API fail instead \u2014 network.upsertOverrideRule with status 500 for this endpoint, then query.invalidate \u2014 and restoreError this query.");const queryTriggerLoading=pinWatcher(e=>e==="fetching"||e==="pending"||e==="loading","loading","To keep it loading, delay the API instead \u2014 network.upsertOverrideRule with a long delayMs (60000) for this endpoint, then query.invalidate \u2014 and restoreLoading this query.");const lifecycleRelaunch=async({dispatch:e,signal:r})=>{await sleep(2e3,r);const{satisfied:i}=await poll(async()=>{try{return rec(await e("app","ping",{}))?.ok===true}catch{return false}},t=>t,4e4,1500,r);return i?{status:"verified",detail:"The app reloaded and is answering again \u2014 read its screen or state now."}:{status:"failed",detail:"The app has not answered for 40 seconds after the relaunch. It may have crashed on start: check the console, then reload_app."}};const VERIFIERS=exports.VERIFIERS={"lifecycle.relaunch":lifecycleRelaunch,"query.triggerError":queryTriggerError,"query.triggerLoading":queryTriggerLoading,"storage.async.setItem":storageSetItem,"zustand.setState":zustandSetState,"query.setQueryData":querySetQueryData,"route-events.navigate":navigate,"network.upsertOverrideRule":upsertOverrideRule};async function verifyOutcome(e){const r=VERIFIERS[`${e.toolId}.${e.action}`];if(!r)return void 0;try{return await r(e)}catch{return void 0}}function verificationTrailer(e){switch(e.status){case"verified":return`
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.VERIFIERS=void 0;exports.atPath=atPath;exports.isSubset=isSubset;exports.verificationTrailer=verificationTrailer;exports.verifyOutcome=verifyOutcome;var _realNow=require("../realNow");const rec=e=>e&&typeof e==="object"&&!Array.isArray(e)?e:void 0;function sleep(e,n){return new Promise(r=>{if(n?.aborted)return r();const t=(0,_realNow.realSetTimeout)(i,e);function i(){(0,_realNow.realClearTimeout)(t);n?.removeEventListener("abort",i);r()}n?.addEventListener("abort",i,{once:true})})}async function poll(e,n,r,t,i){const a=(0,_realNow.realNow)()+r;for(;;){const o=await e();if(n(o))return{value:o,satisfied:true};if(i?.aborted||(0,_realNow.realNow)()+t>a)return{value:o,satisfied:false};await sleep(t,i)}}function isSubset(e,n){if(e===null||typeof e!=="object"||Array.isArray(e))return JSON.stringify(e)===JSON.stringify(n);const r=rec(n);if(!r)return false;for(const[t,i]of Object.entries(e)){if(!(t in r))return false;if(!isSubset(i,r[t]))return false}return true}function atPath(e,n){const r=n.replace(/^\/+/,"").match(/\[[^\]]*\]|[^.[\]/]+/g)??[];let t=e;for(const i of r){if(t===null||typeof t!=="object")return void 0;if(i.startsWith("[")){const a=i.slice(1,-1).trim();if(!Array.isArray(t))return void 0;const o=a.indexOf("=");if(o<0){t=t[Number(a)]}else{const s=a.slice(0,o).trim();const u=a.slice(o+1).trim().replace(/^["']|["']$/g,"");t=t.find(d=>d&&typeof d==="object"&&String(d[s])===u)}}else{t=t[i]}}return t}const brief=e=>{const n=JSON.stringify(e);return n===void 0?"undefined":n.length>80?`${n.slice(0,77)}\u2026`:n};const storageSetItem=async({params:e,dispatch:n})=>{if(typeof e.key!=="string")return void 0;const r=await n("storage","async.getItem",{key:e.key});const t=typeof r==="string"?r:rec(r)?.value;const i=typeof e.value==="string"?e.value:JSON.stringify(e.value);if(t===i)return{status:"verified",detail:`Read back: "${e.key}" now holds the written value.`};return{status:"failed",detail:`Read back after the write: "${e.key}" holds ${brief(t)}, not what was written. The write did not take.`}};function writtenLeaves(e){if(typeof e.path==="string"){const t=e.path.split(".").pop()?.replace(/\[.*\]$/,"")??"";return t?[[t,e.value]]:[]}const n=[];const r=(t,i,a)=>{const o=rec(t);if(o&&a<4)for(const[s,u]of Object.entries(o))r(u,s,a+1);else if(i&&(typeof t==="number"||typeof t==="string"||typeof t==="boolean"))n.push([i,t])};r(e.state,"",0);return n}function sameNamedDiffers(e,n,r,t=0){const i=rec(e);if(!i||t>4)return[];const a=[];for(const[o,s]of Object.entries(i)){if(o===n&&(typeof s==="number"||typeof s==="string"||typeof s==="boolean")&&JSON.stringify(s)!==JSON.stringify(r))a.push(s);else if(rec(s))a.push(...sameNamedDiffers(s,n,r,t+1))}return a}const GENERIC_FIELDS=new Set(["id","key","type","name","title","status","value","label"]);async function shadowingQuery(e,n){const r=writtenLeaves(e).filter(([a])=>a.length>2&&!GENERIC_FIELDS.has(a));if(!r.length)return void 0;const t=rec(await n("query","listQueries",{limit:25}).catch(()=>void 0));const i=(Array.isArray(t?.queries)?t.queries:[]).map(rec).filter(a=>!!a&&Number(a.observers)>0&&typeof a.queryHash==="string").slice(0,5);for(const a of i){const o=rec(await n("query","getQueryData",{queryHash:a.queryHash}).catch(()=>void 0));if(!o||o.found===false)continue;for(const[s,u]of r){const d=sameNamedDiffers(o.data,s,u);if(d.length)return`The query ${a.queryHash} on screen also has ${s} = ${brief(d[0])}. If the screen reads that query, it still shows ${brief(d[0])}: check the screen, and change the query (setQueryData) if so.`}}return void 0}const zustandSetState=async({params:e,dispatch:n,after:r})=>{const t=await zustandStoreReadBack({params:e,dispatch:n,after:r});if(t?.status!=="verified")return t;const i=await shadowingQuery(e,n).catch(()=>void 0);return i?{status:"unverified",detail:`${t.detail.replace(/\.$/,"")}, but that is the store, not the screen. ${i}`}:t};const zustandStoreReadBack=async({params:e,dispatch:n,after:r})=>{if(typeof e.storeName!=="string")return void 0;const t=rec(r!==void 0?r:await n("zustand","getStoreState",{storeName:e.storeName}));if(!t||t.found===false)return{status:"failed",detail:`Read back: store "${e.storeName}" could not be read after the write.`};const i=t.currentState;if(typeof e.path==="string"){const s=atPath(i,e.path);if(JSON.stringify(s)===JSON.stringify(e.value))return{status:"verified",detail:`Read back: ${e.storeName}.${e.path} is now ${brief(s)}.`};return{status:"failed",detail:`Read back: ${e.storeName}.${e.path} is ${brief(s)}, not ${brief(e.value)}. The store did not take the write.`}}const a=rec(e.state);if(!a)return void 0;if(isSubset(a,i))return{status:"verified",detail:`Read back: "${e.storeName}" now holds the written fields (${Object.keys(a).join(", ")}).`};const o=Object.keys(a).filter(s=>!isSubset(a[s],rec(i)?.[s]));return{status:"failed",detail:`Read back: "${e.storeName}" does not hold the written value for ${o.join(", ")}. The store did not take the write \u2014 read it before deciding what to do.`}};const querySetQueryData=async({params:e,dispatch:n,after:r,signal:t})=>{const i=e.queryHash!==void 0?{queryHash:e.queryHash}:e.queryKey!==void 0?{queryKey:e.queryKey}:void 0;if(!i)return void 0;const a=rec(r!==void 0?r:await n("query","getQueryData",i));if(!a||a.found===false)return{status:"failed",detail:"Read back: the query could not be read after the write."};const o=a.data;if(typeof e.path==="string"){const s=atPath(o,e.path);if(JSON.stringify(s)===JSON.stringify(e.value))return outlasts(e,n,`Read back: the cached ${e.path} is now ${brief(s)}.`,t);return{status:"failed",detail:`Read back: the cached ${e.path} is ${brief(s)}, not ${brief(e.value)}. The cache did not take the write \u2014 the app may have refetched over it.`}}if(e.data===void 0)return void 0;if(isSubset(e.data,o))return outlasts(e,n,"Read back: the cache now holds the written data.",t);return{status:"failed",detail:"Read back: the cache does not hold the written data \u2014 the app may have refetched over it, or the shape differed."}};async function outlasts(e,n,r,t){const i=typeof e.queryHash==="string"?e.queryHash:Array.isArray(e.queryKey)?JSON.stringify(e.queryKey):void 0;const a=i?await queryRow(n,i).catch(()=>void 0):void 0;const o=typeof a?.refetchEveryMs==="number"?a.refetchEveryMs:void 0;if(!i||!o||o>1e4)return{status:"verified",detail:r};await sleep(o+750,t);if(t?.aborted)return{status:"verified",detail:r};const s=rec(await n("query","getQueryData",{queryHash:i}).catch(()=>void 0));if(!s||s.found===false)return{status:"verified",detail:r};const u=typeof e.path==="string"?JSON.stringify(atPath(s.data,e.path))===JSON.stringify(e.value):e.data!==void 0&&isSubset(e.data,s.data);if(u)return{status:"verified",detail:`${r} It is still there after the screen's ${o} ms refetch.`};return{status:"failed",detail:`${r} But this screen refetches every ${o} ms, and it already has: the server's data replaced the edit. For a change that stays, override the API response \u2014 network.upsertOverrideRule (fromRequestId + bodyPatch, or a full body), then query.invalidate.`}}const navigate=async({params:e,dispatch:n,signal:r})=>{if(typeof e.path!=="string")return void 0;const t=e.path.split("?")[0];const{value:i,satisfied:a}=await poll(async()=>rec(await n("route-events","getCurrentRoute",{})),s=>typeof s?.path==="string"&&(s.path===t||s.path.split("?")[0]===t),2e3,250,r);if(a)return{status:"verified",detail:`The app is now on ${t}.`};const o=typeof i?.path==="string"?i.path:"an unknown route";return{status:"failed",detail:`Two seconds after navigating, the app is on ${o}, not ${t}. Do not navigate again blindly \u2014 check the route exists (the sitemap) and whether something redirected.`}};const upsertOverrideRule=async({params:e,result:n,dispatch:r,signal:t})=>{const i=rec(rec(n)?.rule)?.id??rec(e.rule)?.id;if(typeof i!=="string")return void 0;const a=async()=>{const u=rec(await r("network","listOverrideRules",{}));const d=Array.isArray(u?.rules)?u.rules:[];return d.map(rec).find(l=>l?.id===i)};const{value:o,satisfied:s}=await poll(a,u=>typeof u?.hits==="number"&&u.hits>0,3e3,500,t);if(!o)return{status:"failed",detail:"The rule is not in the override list after the write. It did not install."};if(o.enabled===false)return{status:"failed",detail:"The rule is installed but disabled, so it will not fire."};if(s)return{status:"verified",detail:`The rule has matched ${o.hits} request${o.hits===1?"":"s"} \u2014 the app has already received the overridden response.`};return{status:"unverified",detail:"The rule is installed, but no request has matched it yet \u2014 the app is still showing whatever it loaded before. A refetch or a visit to the screen that fetches this will exercise it; until then, do not say the screen shows the override."}};async function queryRow(e,n){const r=rec(await e("query","listQueries",{limit:100}));const t=Array.isArray(r?.queries)?r.queries:[];return t.map(rec).find(i=>i?.queryHash===n)}function pinWatcher(e,n,r){return async({params:t,dispatch:i,signal:a})=>{if(typeof t.queryHash!=="string")return void 0;const o=async()=>{const d=await queryRow(i,t.queryHash);return typeof d?.status==="string"?d.status:void 0};const{value:s,satisfied:u}=await poll(o,d=>d!==void 0&&!e(d),3e3,500,a);if(s===void 0)return void 0;if(!u)return{status:"verified",detail:`Still ${n} three seconds later.`};return{status:"failed",detail:`Within three seconds the app refetched and the query is "${s}" again: this screen polls, so a cache ${n} pin cannot last. ${r}`}}}const queryTriggerError=pinWatcher(e=>e==="error","error","For an error that stays, make the API fail instead \u2014 network.upsertOverrideRule with status 500 for this endpoint, then query.invalidate \u2014 and restoreError this query.");const queryTriggerLoading=pinWatcher(e=>e==="fetching"||e==="pending"||e==="loading","loading","To keep it loading, delay the API instead \u2014 network.upsertOverrideRule with a long delayMs (60000) for this endpoint, then query.invalidate \u2014 and restoreLoading this query.");const lifecycleRelaunch=async({dispatch:e,signal:n})=>{await sleep(2e3,n);const{satisfied:r}=await poll(async()=>{try{return rec(await e("app","ping",{}))?.ok===true}catch{return false}},t=>t,4e4,1500,n);return r?{status:"verified",detail:"The app reloaded and is answering again \u2014 read its screen or state now."}:{status:"failed",detail:"The app has not answered for 40 seconds after the relaunch. It may have crashed on start: check the console, then reload_app."}};const RECIPE_AWAY_MS={"phone-call":2e4,"quick-switch":5e3,"low-memory":1e4};const lifecycleAway=async({action:e,params:n,dispatch:r,signal:t})=>{let i;if(e==="runRecipe")i=RECIPE_AWAY_MS[String(n.id)];else if(e==="background"&&n.duration!==void 0&&n.skipWait!==true){const c=n.duration;const f=/^(\d+(?:\.\d+)?)\s*(ms|s|m)$/.exec(String(c));i=typeof c==="number"?c:f?Number(f[1])*(f[2]==="ms"?1:f[2]==="s"?1e3:6e4):void 0}if(!i||i>12e4)return void 0;const{satisfied:a}=await poll(async()=>rec(await r("lifecycle","getState",{}).catch(()=>void 0)),c=>!!c&&(c.away===null||c.away===void 0)&&c.realAppState!=="background",i+8e3,1e3,t);if(!a)return{status:"unverified",detail:`The app has not come back yet (expected about ${Math.round(i/1e3)} s). Do not report the result until it has.`};const o=await poll(async()=>rec(rec(await r("lifecycle","getState",{}).catch(()=>void 0))?.report),c=>!c||c.collectingUntil===null||c.collectingUntil===void 0,8e3,1e3,t);const s=o.value;const u=rec(s?.counts);const d=u&&Object.keys(u).length?` Seen during it: ${Object.entries(u).map(([c,f])=>`${c} ${f}`).join(", ")}.`:" Nothing was recorded during it.";const l=typeof s?.finding==="string"?` ${s.finding}`:"";return{status:"verified",detail:`The interruption is over and the app is active again.${s?`${l}${d}`:""}`}};const lifecycleSignal=async({readSnapshot:e,signal:n})=>{if(!e)return void 0;const r=(0,_realNow.realNow)();await sleep(1500,n);const t=rec(await Promise.resolve(e("console")).catch(()=>void 0));const i=(Array.isArray(t?.entries)?t.entries:Array.isArray(t)?t:[]).map(rec).filter(a=>!!a&&Number(a.timestamp??a.at??0)>=r-250).slice(-6).map(a=>`${String(a.level??a.type??"log")}: ${String(a.message??(Array.isArray(a.args)?a.args.join(" "):"")).slice(0,160)}`);if(!i.length)return{status:"unverified",detail:"Delivered. The app logged nothing in the next 1.5 s; check the screen or report for its reaction."};return{status:"verified",detail:`Delivered. The app logged right after: ${i.join(" | ")}`}};const SAVE_LABEL=/^\s*(save|submit|update|apply|done|confirm)\b/i;const tapControl=async({result:e,dispatch:n})=>{const r=rec(rec(e)?.matched);const t=typeof r?.via==="string"?r.via:"";if(t!=="onValueChange"&&t!=="onChangeText")return void 0;const i=rec(await n("highlight-updates","describeScreen",{}).catch(()=>void 0));const a=Array.isArray(i?.elements)?i.elements.map(rec):[];const o=a.find(u=>!!u&&u.interactive===true&&SAVE_LABEL.test(String(u.label??u.text??""))&&u.nativeTag!==r?.nativeTag);if(!o)return void 0;const s=String(o.label??o.text).trim();return{status:"unverified",detail:`Changed on screen only. This screen has a "${s}" button: the change isn't saved until it's pressed. Press it, then check the result.`}};const VERIFIERS=exports.VERIFIERS={"highlight-updates.tapElement":tapControl,"lifecycle.memoryWarning":lifecycleSignal,"lifecycle.relaunch":lifecycleRelaunch,"lifecycle.runRecipe":lifecycleAway,"lifecycle.background":lifecycleAway,"query.triggerError":queryTriggerError,"query.triggerLoading":queryTriggerLoading,"storage.async.setItem":storageSetItem,"zustand.setState":zustandSetState,"query.setQueryData":querySetQueryData,"route-events.navigate":navigate,"network.upsertOverrideRule":upsertOverrideRule};async function verifyOutcome(e){const n=VERIFIERS[`${e.toolId}.${e.action}`];if(!n)return void 0;try{return await n(e)}catch{return void 0}}function verificationTrailer(e){switch(e.status){case"verified":return`
|
|
2
2
|
|
|
3
3
|
[Buoy] Verified: ${e.detail}`;case"unverified":return`
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.isSelfTraffic=isSelfTraffic;exports.redact=redact;exports.registerSelfEndpoint=registerSelfEndpoint;exports.stripSelfTraffic=stripSelfTraffic;const SECRET_KEYS=["authorization","cookie","setcookie","apikey","proxyauthorization","authtoken","accesstoken","refreshtoken","idtoken","sessiontoken","bearertoken","licensekey","licencekey","buoykey","privatekey","secret","password","passwd","credential"];const REDACTED="[redacted by Buoy]";const SECRET_VALUE_PATTERNS=[{re:/\bBearer\s+[A-Za-z0-9._~+/-]{12,}=*/gi,label:"[bearer token removed]"},{re:/\beyJ[A-Za-z0-9._-]{20,}/g,label:"[jwt removed]"},{re:/\bsk-[A-Za-z0-9_-]{16,}/g,label:"[api key removed]"},{re:/\bBUOY-[A-Z0-9-]{8,}/g,label:"[license key removed]"},{re:/\b[A-F0-9]{6}(?:-[A-F0-9]{6}){4}-V\d\b/g,label:"[license key removed]"},{re:/("|')?[A-Za-z0-9_-]*(authorization|api[_-]?key|password|passwd|secret|access[_-]?token|refresh[_-]?token|auth[_-]?token|session[_-]?token|license[_-]?key|licence[_-]?key|buoy[_-]?key|private[_-]?key)\1?\s*[:=]\s*("|')?[^\s"',}]{6,}/gi,label:"[credential removed]"}];function scrubString(e){let r=e;for(const{re:o,label:t}of SECRET_VALUE_PATTERNS){r=r.replace(o,t)}return r}function isSecretKey(e){const r=e.toLowerCase().replace(/[-_.\s]/g,"");return SECRET_KEYS.some(o=>r.includes(o))}function redact(e,r=0){if(typeof e==="string")return scrubString(e);if(r>12||e===null||typeof e!=="object")return e;if(Array.isArray(e)){return e.map(t=>redact(t,r+1))}const o={};for(const[t,s]of Object.entries(e)){o[t]=isSecretKey(t)?REDACTED:redact(s,r+1)}return o}const selfHosts=new Set;const INTERNAL_HOSTS_KEY="__BUOY_INTERNAL_HOSTS__";function registerSelfEndpoint(e){try{const r=/^[a-z]+:\/\/([^/?#]+)/i.exec(e);if(!r?.[1])return;const o=r[1].toLowerCase();selfHosts.add(o);const t=globalThis;const s=t[INTERNAL_HOSTS_KEY];const n=s instanceof Set?s:new Set;n.add(o);t[INTERNAL_HOSTS_KEY]=n}catch{}}function isSelfTraffic(e){if(typeof e!=="string")return false;const r=e.toLowerCase();for(const o of selfHosts){if(r.includes(o))return true}return false}function hasSelfUrl(e){if(!e||typeof e!=="object")return false;const r=e;return isSelfTraffic(r.url??r.uri??r.href)}function stripSelfTraffic(e,r=0){if(r>6||e===null||typeof e!=="object")return e;if(Array.isArray(e)){return e.filter(t=>!hasSelfUrl(t)).map(t=>stripSelfTraffic(t,r+1))}const o={};for(const[t,s]of Object.entries(e)){o[t]=stripSelfTraffic(s,r+1)}return o}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.isSelfTraffic=isSelfTraffic;exports.redact=redact;exports.registerSelfEndpoint=registerSelfEndpoint;exports.stripSelfTraffic=stripSelfTraffic;const SECRET_KEYS=["authorization","cookie","setcookie","apikey","proxyauthorization","authtoken","accesstoken","refreshtoken","idtoken","sessiontoken","bearertoken","licensekey","licencekey","buoykey","privatekey","secret","password","passwd","credential"];const REDACTED="[redacted by Buoy]";const SECRET_VALUE_PATTERNS=[{re:/\bBearer\s+[A-Za-z0-9._~+/-]{12,}=*/gi,label:"[bearer token removed]"},{re:/\beyJ[A-Za-z0-9._-]{20,}/g,label:"[jwt removed]"},{re:/\bsk-[A-Za-z0-9_-]{16,}/g,label:"[api key removed]"},{re:/\bBUOY-[A-Z0-9-]{8,}/g,label:"[license key removed]"},{re:/\b[A-F0-9]{6}(?:-[A-F0-9]{6}){4}-V\d\b/g,label:"[license key removed]"},{re:/("|')?[A-Za-z0-9_-]*(authorization|api[_-]?key|password|passwd|secret|access[_-]?token|refresh[_-]?token|auth[_-]?token|session[_-]?token|license[_-]?key|licence[_-]?key|buoy[_-]?key|private[_-]?key)\1?\s*[:=]\s*("|')?(?!(?:authorization|bearer|cookie|header)\b)[^\s"',}]{6,}/gi,label:"[credential removed]"}];function scrubString(e){let r=e;for(const{re:o,label:t}of SECRET_VALUE_PATTERNS){r=r.replace(o,t)}return r}function isSecretKey(e){const r=e.toLowerCase().replace(/[-_.\s]/g,"");return SECRET_KEYS.some(o=>r.includes(o))}function redact(e,r=0){if(typeof e==="string")return scrubString(e);if(r>12||e===null||typeof e!=="object")return e;if(Array.isArray(e)){return e.map(t=>redact(t,r+1))}const o={};for(const[t,s]of Object.entries(e)){o[t]=isSecretKey(t)?REDACTED:redact(s,r+1)}return o}const selfHosts=new Set;const INTERNAL_HOSTS_KEY="__BUOY_INTERNAL_HOSTS__";function registerSelfEndpoint(e){try{const r=/^[a-z]+:\/\/([^/?#]+)/i.exec(e);if(!r?.[1])return;const o=r[1].toLowerCase();selfHosts.add(o);const t=globalThis;const s=t[INTERNAL_HOSTS_KEY];const n=s instanceof Set?s:new Set;n.add(o);t[INTERNAL_HOSTS_KEY]=n}catch{}}function isSelfTraffic(e){if(typeof e!=="string")return false;const r=e.toLowerCase();for(const o of selfHosts){if(r.includes(o))return true}return false}function hasSelfUrl(e){if(!e||typeof e!=="object")return false;const r=e;return isSelfTraffic(r.url??r.uri??r.href)}function stripSelfTraffic(e,r=0){if(r>6||e===null||typeof e!=="object")return e;if(Array.isArray(e)){return e.filter(t=>!hasSelfUrl(t)).map(t=>stripSelfTraffic(t,r+1))}const o={};for(const[t,s]of Object.entries(e)){o[t]=stripSelfTraffic(s,r+1)}return o}
|