@buoy-gg/agent-core 7.0.34 → 7.0.36
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/README.md +10 -7
- package/lib/commonjs/blocks/receipts.js +83 -24
- package/lib/commonjs/blocks/receipts.js.map +1 -1
- package/lib/commonjs/blocks/runId.js +31 -0
- package/lib/commonjs/blocks/runId.js.map +1 -0
- package/lib/commonjs/blocks/types.js +23 -2
- package/lib/commonjs/blocks/types.js.map +1 -1
- package/lib/commonjs/blocks/uiTool.js +426 -12
- package/lib/commonjs/blocks/uiTool.js.map +1 -1
- package/lib/commonjs/catalog/catalog.g.js +121 -43
- package/lib/commonjs/catalog/catalog.g.js.map +1 -1
- package/lib/commonjs/catalog/catalog.source.json +121 -45
- package/lib/commonjs/catalog/catalog.types.g.js +44 -0
- package/lib/commonjs/catalog/catalog.types.g.js.map +1 -0
- package/lib/commonjs/catalog/normalizeParams.js +333 -0
- package/lib/commonjs/catalog/normalizeParams.js.map +1 -0
- package/lib/commonjs/catalog/signature.js +68 -0
- package/lib/commonjs/catalog/signature.js.map +1 -0
- package/lib/commonjs/catalog/snapshotReads.js +13 -6
- package/lib/commonjs/catalog/snapshotReads.js.map +1 -1
- package/lib/commonjs/catalog/toProviderTools.js +41 -5
- package/lib/commonjs/catalog/toProviderTools.js.map +1 -1
- package/lib/commonjs/context/buildContextPack.js +367 -19
- package/lib/commonjs/context/buildContextPack.js.map +1 -1
- package/lib/commonjs/effects/digest.js +34 -0
- package/lib/commonjs/effects/digest.js.map +1 -0
- package/lib/commonjs/effects/ledger.js +85 -1
- package/lib/commonjs/effects/ledger.js.map +1 -1
- package/lib/commonjs/engine/askGate.js +287 -0
- package/lib/commonjs/engine/askGate.js.map +1 -0
- package/lib/commonjs/engine/effectFor.js +82 -1
- package/lib/commonjs/engine/effectFor.js.map +1 -1
- package/lib/commonjs/engine/historyBudget.js +260 -0
- package/lib/commonjs/engine/historyBudget.js.map +1 -0
- package/lib/commonjs/engine/runAgentTurn.js +958 -88
- package/lib/commonjs/engine/runAgentTurn.js.map +1 -1
- package/lib/commonjs/engine/systemPrompt.js +72 -8
- package/lib/commonjs/engine/systemPrompt.js.map +1 -1
- package/lib/commonjs/engine/textToolCalls.js +276 -0
- package/lib/commonjs/engine/textToolCalls.js.map +1 -0
- package/lib/commonjs/index.js +81 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/policy/labels.js +19 -3
- package/lib/commonjs/policy/labels.js.map +1 -1
- package/lib/commonjs/policy/policy.js +6 -1
- package/lib/commonjs/policy/policy.js.map +1 -1
- package/lib/commonjs/policy/redact.js +42 -5
- package/lib/commonjs/policy/redact.js.map +1 -1
- package/lib/commonjs/providers/anthropic.js +340 -174
- package/lib/commonjs/providers/anthropic.js.map +1 -1
- package/lib/commonjs/providers/openai.js +229 -103
- package/lib/commonjs/providers/openai.js.map +1 -1
- package/lib/commonjs/providers/sse.js +164 -29
- package/lib/commonjs/providers/sse.js.map +1 -1
- package/lib/commonjs/providers/streamTimer.js +123 -0
- package/lib/commonjs/providers/streamTimer.js.map +1 -0
- package/lib/commonjs/providers/transport.js +107 -0
- package/lib/commonjs/providers/transport.js.map +1 -0
- package/lib/commonjs/providers/xhrStream.js +209 -0
- package/lib/commonjs/providers/xhrStream.js.map +1 -0
- package/lib/commonjs/session.js +245 -52
- package/lib/commonjs/session.js.map +1 -1
- package/lib/module/blocks/receipts.js +83 -24
- package/lib/module/blocks/receipts.js.map +1 -1
- package/lib/module/blocks/runId.js +26 -0
- package/lib/module/blocks/runId.js.map +1 -0
- package/lib/module/blocks/types.js +23 -2
- package/lib/module/blocks/types.js.map +1 -1
- package/lib/module/blocks/uiTool.js +424 -12
- package/lib/module/blocks/uiTool.js.map +1 -1
- package/lib/module/catalog/catalog.g.js +121 -43
- package/lib/module/catalog/catalog.g.js.map +1 -1
- package/lib/module/catalog/catalog.source.json +121 -45
- package/lib/module/catalog/catalog.types.g.js +40 -0
- package/lib/module/catalog/catalog.types.g.js.map +1 -0
- package/lib/module/catalog/normalizeParams.js +327 -0
- package/lib/module/catalog/normalizeParams.js.map +1 -0
- package/lib/module/catalog/signature.js +63 -0
- package/lib/module/catalog/signature.js.map +1 -0
- package/lib/module/catalog/snapshotReads.js +13 -6
- package/lib/module/catalog/snapshotReads.js.map +1 -1
- package/lib/module/catalog/toProviderTools.js +41 -5
- package/lib/module/catalog/toProviderTools.js.map +1 -1
- package/lib/module/context/buildContextPack.js +366 -19
- package/lib/module/context/buildContextPack.js.map +1 -1
- package/lib/module/effects/digest.js +29 -0
- package/lib/module/effects/digest.js.map +1 -0
- package/lib/module/effects/ledger.js +85 -1
- package/lib/module/effects/ledger.js.map +1 -1
- package/lib/module/engine/askGate.js +280 -0
- package/lib/module/engine/askGate.js.map +1 -0
- package/lib/module/engine/effectFor.js +83 -1
- package/lib/module/engine/effectFor.js.map +1 -1
- package/lib/module/engine/historyBudget.js +251 -0
- package/lib/module/engine/historyBudget.js.map +1 -0
- package/lib/module/engine/runAgentTurn.js +954 -88
- package/lib/module/engine/runAgentTurn.js.map +1 -1
- package/lib/module/engine/systemPrompt.js +71 -8
- package/lib/module/engine/systemPrompt.js.map +1 -1
- package/lib/module/engine/textToolCalls.js +270 -0
- package/lib/module/engine/textToolCalls.js.map +1 -0
- package/lib/module/index.js +7 -4
- package/lib/module/index.js.map +1 -1
- package/lib/module/policy/labels.js +19 -3
- package/lib/module/policy/labels.js.map +1 -1
- package/lib/module/policy/policy.js +6 -1
- package/lib/module/policy/policy.js.map +1 -1
- package/lib/module/policy/redact.js +42 -5
- package/lib/module/policy/redact.js.map +1 -1
- package/lib/module/providers/anthropic.js +341 -175
- package/lib/module/providers/anthropic.js.map +1 -1
- package/lib/module/providers/openai.js +230 -104
- package/lib/module/providers/openai.js.map +1 -1
- package/lib/module/providers/sse.js +160 -29
- package/lib/module/providers/sse.js.map +1 -1
- package/lib/module/providers/streamTimer.js +118 -0
- package/lib/module/providers/streamTimer.js.map +1 -0
- package/lib/module/providers/transport.js +103 -0
- package/lib/module/providers/transport.js.map +1 -0
- package/lib/module/providers/xhrStream.js +204 -0
- package/lib/module/providers/xhrStream.js.map +1 -0
- package/lib/module/session.js +228 -54
- package/lib/module/session.js.map +1 -1
- package/lib/typescript/blocks/receipts.d.ts +5 -10
- package/lib/typescript/blocks/receipts.d.ts.map +1 -1
- package/lib/typescript/blocks/runId.d.ts +20 -0
- package/lib/typescript/blocks/runId.d.ts.map +1 -0
- package/lib/typescript/blocks/types.d.ts +23 -2
- package/lib/typescript/blocks/types.d.ts.map +1 -1
- package/lib/typescript/blocks/uiTool.d.ts +11 -2
- package/lib/typescript/blocks/uiTool.d.ts.map +1 -1
- package/lib/typescript/catalog/catalog.g.d.ts.map +1 -1
- package/lib/typescript/catalog/catalog.types.g.d.ts +40 -0
- package/lib/typescript/catalog/catalog.types.g.d.ts.map +1 -0
- package/lib/typescript/catalog/normalizeParams.d.ts +43 -0
- package/lib/typescript/catalog/normalizeParams.d.ts.map +1 -0
- package/lib/typescript/catalog/signature.d.ts +29 -0
- package/lib/typescript/catalog/signature.d.ts.map +1 -0
- package/lib/typescript/catalog/snapshotReads.d.ts.map +1 -1
- package/lib/typescript/catalog/toProviderTools.d.ts +28 -15
- package/lib/typescript/catalog/toProviderTools.d.ts.map +1 -1
- package/lib/typescript/context/buildContextPack.d.ts +24 -2
- package/lib/typescript/context/buildContextPack.d.ts.map +1 -1
- package/lib/typescript/effects/digest.d.ts +10 -0
- package/lib/typescript/effects/digest.d.ts.map +1 -0
- package/lib/typescript/effects/ledger.d.ts +55 -0
- package/lib/typescript/effects/ledger.d.ts.map +1 -1
- package/lib/typescript/engine/askGate.d.ts +29 -0
- package/lib/typescript/engine/askGate.d.ts.map +1 -0
- package/lib/typescript/engine/effectFor.d.ts +6 -1
- package/lib/typescript/engine/effectFor.d.ts.map +1 -1
- package/lib/typescript/engine/historyBudget.d.ts +92 -0
- package/lib/typescript/engine/historyBudget.d.ts.map +1 -0
- package/lib/typescript/engine/runAgentTurn.d.ts +115 -1
- package/lib/typescript/engine/runAgentTurn.d.ts.map +1 -1
- package/lib/typescript/engine/systemPrompt.d.ts +41 -0
- package/lib/typescript/engine/systemPrompt.d.ts.map +1 -1
- package/lib/typescript/engine/textToolCalls.d.ts +58 -0
- package/lib/typescript/engine/textToolCalls.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +8 -5
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/policy/labels.d.ts.map +1 -1
- package/lib/typescript/policy/policy.d.ts.map +1 -1
- package/lib/typescript/policy/redact.d.ts.map +1 -1
- package/lib/typescript/providers/anthropic.d.ts +5 -0
- package/lib/typescript/providers/anthropic.d.ts.map +1 -1
- package/lib/typescript/providers/openai.d.ts +0 -16
- package/lib/typescript/providers/openai.d.ts.map +1 -1
- package/lib/typescript/providers/sse.d.ts +82 -1
- package/lib/typescript/providers/sse.d.ts.map +1 -1
- package/lib/typescript/providers/streamTimer.d.ts +59 -0
- package/lib/typescript/providers/streamTimer.d.ts.map +1 -0
- package/lib/typescript/providers/transport.d.ts +40 -0
- package/lib/typescript/providers/transport.d.ts.map +1 -0
- package/lib/typescript/providers/types.d.ts +116 -3
- package/lib/typescript/providers/types.d.ts.map +1 -1
- package/lib/typescript/providers/xhrStream.d.ts +43 -0
- package/lib/typescript/providers/xhrStream.d.ts.map +1 -0
- package/lib/typescript/session.d.ts +63 -2
- package/lib/typescript/session.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"actions": [
|
|
133
133
|
{
|
|
134
134
|
"action": "listAtoms",
|
|
135
|
-
"summary": "
|
|
135
|
+
"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` — the item type of any list it holds — which is the cheap way to learn what an addition must look like.",
|
|
136
136
|
"params": {
|
|
137
137
|
"type": "object",
|
|
138
138
|
"properties": {
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
},
|
|
150
150
|
"effect": "read",
|
|
151
151
|
"release": "works",
|
|
152
|
-
"description": "
|
|
152
|
+
"description": "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` — the item type of any list it holds — which is the cheap way to learn what an addition must look like.",
|
|
153
153
|
"requires": [
|
|
154
154
|
"@buoy-gg/jotai installed in the app",
|
|
155
155
|
"watchAtoms(store, atoms) or watchDefaultStoreAtoms(atoms) called at app startup"
|
|
@@ -157,13 +157,17 @@
|
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
"action": "getAtomValue",
|
|
160
|
-
"summary": "Fetch one atom's current value on demand, by label.",
|
|
160
|
+
"summary": "Fetch one atom's current value on demand, by label. Send `path` to get back ONE value instead of the whole atom (path:\"lines[id=seed-1].qty\") — do that whenever the value is big, because results are cut off at 24,000 characters. Also returns `shape`: a one-line sketch of the value's type plus, for each list in it, what its items look like. Read it before writing and match it exactly.",
|
|
161
161
|
"params": {
|
|
162
162
|
"type": "object",
|
|
163
163
|
"properties": {
|
|
164
164
|
"label": {
|
|
165
165
|
"type": "string",
|
|
166
166
|
"description": "Atom label exactly as registered — the object key in watchAtoms(store, { countAtom }), e.g. \"countAtom\". Get exact labels from listAtoms."
|
|
167
|
+
},
|
|
168
|
+
"path": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"description": "Return only the value at this path instead of the whole atom value."
|
|
167
171
|
}
|
|
168
172
|
},
|
|
169
173
|
"required": [
|
|
@@ -173,7 +177,7 @@
|
|
|
173
177
|
},
|
|
174
178
|
"effect": "read",
|
|
175
179
|
"release": "works",
|
|
176
|
-
"description": "
|
|
180
|
+
"description": "Fetch one atom's current value on demand, by label. Send `path` to get back ONE value instead of the whole atom (path:\"lines[id=seed-1].qty\") — do that whenever the value is big, because results are cut off at 24,000 characters. Also returns `shape`: a one-line sketch of the value's type plus, for each list in it, what its items look like. Read it before writing and match it exactly.",
|
|
177
181
|
"requires": [
|
|
178
182
|
"The label must already be registered via watchAtoms — call listAtoms first for exact labels"
|
|
179
183
|
]
|
|
@@ -203,7 +207,7 @@
|
|
|
203
207
|
},
|
|
204
208
|
{
|
|
205
209
|
"action": "setAtom",
|
|
206
|
-
"summary": "Write
|
|
210
|
+
"summary": "Write to a writable Jotai atom, named by its `label` — this mutates the running app's state. Three forms: `value` alone REPLACES; `value` with `merge:true` merges a patch under the typed-edit rule (change existing fields to the same type; a list may grow or shrink but every item must have the fields the others have); `path`+`value` sets ONE value with no nesting to get wrong (path:\"lines[id=seed-1].qty\"). To change one item of a list, address it by its own id — {\"lines\":{\"seed-1\":{\"qty\":5}}} changes one, {\"lines\":{\"seed-2\":null}} removes one, a new id adds one; items you don't name are untouched. A plain array replaces the whole list. A refused merge names the field and, when the problem was depth, hands back the corrected patch — fix it that way rather than reaching for `force`, which writes raw and can crash the screen.",
|
|
207
211
|
"params": {
|
|
208
212
|
"type": "object",
|
|
209
213
|
"properties": {
|
|
@@ -213,6 +217,18 @@
|
|
|
213
217
|
},
|
|
214
218
|
"value": {
|
|
215
219
|
"description": "The new value — any JSON (number, string, boolean, object, array, null). Passed straight to store.set(atom, value); it REPLACES the value, no merging."
|
|
220
|
+
},
|
|
221
|
+
"merge": {
|
|
222
|
+
"type": "boolean",
|
|
223
|
+
"description": "Merge `value` into the atom's current value instead of replacing it, under the typed-edit rule. Default false."
|
|
224
|
+
},
|
|
225
|
+
"path": {
|
|
226
|
+
"type": "string",
|
|
227
|
+
"description": "Set ONE value inside the atom, named by its path in the CURRENT value — read it first and copy the path from `shape`. Always a merge. A list step is written [field=value] or [0] and resolves to that item's own id."
|
|
228
|
+
},
|
|
229
|
+
"force": {
|
|
230
|
+
"type": "boolean",
|
|
231
|
+
"description": "Bypass the typed-edit safety and write raw. This is what crashes screens; use ONLY to deliberately replace the whole shape."
|
|
216
232
|
}
|
|
217
233
|
},
|
|
218
234
|
"required": [
|
|
@@ -223,7 +239,7 @@
|
|
|
223
239
|
},
|
|
224
240
|
"effect": "write",
|
|
225
241
|
"release": "works",
|
|
226
|
-
"description": "
|
|
242
|
+
"description": "Write to a writable Jotai atom, named by its `label` — this mutates the running app's state. Three forms: `value` alone REPLACES; `value` with `merge:true` merges a patch under the typed-edit rule (change existing fields to the same type; a list may grow or shrink but every item must have the fields the others have); `path`+`value` sets ONE value with no nesting to get wrong (path:\"lines[id=seed-1].qty\"). To change one item of a list, address it by its own id — {\"lines\":{\"seed-1\":{\"qty\":5}}} changes one, {\"lines\":{\"seed-2\":null}} removes one, a new id adds one; items you don't name are untouched. A plain array replaces the whole list. A refused merge names the field and, when the problem was depth, hands back the corrected patch — fix it that way rather than reaching for `force`, which writes raw and can crash the screen.",
|
|
227
243
|
"requires": [
|
|
228
244
|
"listAtoms reports writable:true for this label (atom has a write fn AND the store exposes set())"
|
|
229
245
|
]
|
|
@@ -280,7 +296,7 @@
|
|
|
280
296
|
},
|
|
281
297
|
{
|
|
282
298
|
"action": "navigate",
|
|
283
|
-
"summary": "Navigate the device to a concrete path (pushes by default; replace:true swaps the current screen).",
|
|
299
|
+
"summary": "Navigate the device to a concrete path (pushes by default; replace:true swaps the current screen). ALSO HOW YOU LOAD DATA THE APP HAS NOT FETCHED YET: go to the screen that fetches it, waitFor something on it, then read. Moving the user's screen to go and look is not a change to the app — you do not need to ask, and you do not have to navigate back.",
|
|
284
300
|
"params": {
|
|
285
301
|
"type": "object",
|
|
286
302
|
"properties": {
|
|
@@ -300,7 +316,7 @@
|
|
|
300
316
|
},
|
|
301
317
|
"effect": "write",
|
|
302
318
|
"release": "works",
|
|
303
|
-
"description": "Calls expo-router's router.navigate(path), or router.replace(path) when replace:true. On bare React Navigation (no expo-router) it falls back to navigating by SCREEN NAME through the captured container ref — pass the screen's name ('Settings' or '/Settings'; replace is ignored there), and nested navigators are resolved automatically. The fallback needs <RouteTracker/> mounted inside the NavigationContainer. Pass a CONCRETE path — sitemap entries are templates ('/pokemon/[id]'), so resolve dynamic segments to real values ('/pokemon/25') first; a query string is allowed ('/pokemon/25?tab=stats'). Push is the default so navigate-then-back flows keep working; pass replace:true when you mean 'leave this screen' (e.g. resetting to '/'), otherwise repeated 'go to home' stacks another '/' on top. Throws 'navigate requires a path param' when path is missing/empty, and 'expo-router is not available on this device' on React-Navigation-only apps. Returns { navigated: path, replaced: boolean }. The return only proves the router call was made, not that the screen rendered — re-read the snapshot's stack to confirm.",
|
|
319
|
+
"description": "Calls expo-router's router.navigate(path), or router.replace(path) when replace:true. On bare React Navigation (no expo-router) it falls back to navigating by SCREEN NAME through the captured container ref — pass the screen's name ('Settings' or '/Settings'; replace is ignored there), and nested navigators are resolved automatically. The fallback needs <RouteTracker/> mounted inside the NavigationContainer. Pass a CONCRETE path — sitemap entries are templates ('/pokemon/[id]'), so resolve dynamic segments to real values ('/pokemon/25') first; a query string is allowed ('/pokemon/25?tab=stats'). Push is the default so navigate-then-back flows keep working; pass replace:true when you mean 'leave this screen' (e.g. resetting to '/'), otherwise repeated 'go to home' stacks another '/' on top. Throws 'navigate requires a path param' when path is missing/empty, and 'expo-router is not available on this device' on React-Navigation-only apps. Returns { navigated: path, replaced: boolean }. The return only proves the router call was made, not that the screen rendered — re-read the snapshot's stack to confirm, and use highlight-updates.waitFor before reading anything the new screen has to FETCH. This is the main tool for filling a gap in what you can read: the query cache, the request list and the events timeline only ever hold what the app has ALREADY done, so when the data you were asked about was never loaded, the answer is to go to the screen that loads it rather than to report the gap.",
|
|
304
320
|
"requires": [
|
|
305
321
|
"expo-router installed and initialized in the app",
|
|
306
322
|
"<FloatingDevTools> mounted (adapter registered)"
|
|
@@ -482,7 +498,7 @@
|
|
|
482
498
|
},
|
|
483
499
|
"effect": "read",
|
|
484
500
|
"release": "works",
|
|
485
|
-
"description": "The entry point — call this first to learn valid storeName values for getStoreState/setState. Returns {stores:[{name, changes, isPersisted, persistName?, keys?|currentState?}], total, returned, includedValues}. Compact by default: `keys` is Object.keys() of the state (undefined when the state isn't a plain object). includeValues:true swaps `keys` for the full `currentState` object and can be very large — the state objects here are NOT wire-budget-capped the way the streaming snapshot is. `changes` is that store's recorded state-change count, reset to 0 by clearEvents. Names come from the app: the object keys passed to watchStores({counterStore: useCounterStore}) or the `name` option of buoyDevTools(). An empty stores array means the app never instrumented its stores, not that it has none.",
|
|
501
|
+
"description": "The entry point — call this first to learn valid storeName values for getStoreState/setState. Returns {stores:[{name, changes, isPersisted, persistName?, keys?|currentState?}], total, returned, includedValues}. Compact by default: `keys` is Object.keys() of the state (undefined when the state isn't a plain object). includeValues:true swaps `keys` for the full `currentState` object and can be very large — the state objects here are NOT wire-budget-capped the way the streaming snapshot is. `changes` is that store's recorded state-change count, reset to 0 by clearEvents. Names come from the app: the object keys passed to watchStores({counterStore: useCounterStore}) or the `name` option of buoyDevTools(). An empty stores array means the app never instrumented its stores, not that it has none. Every read here also returns `shape`: a one-line sketch of the value's type plus, for each list in it, what its items look like. It is tiny and does not grow with the data, so it survives the 24,000-character cut when the data itself does not — read it before writing, and match it exactly. A list whose `item` is missing is EMPTY, which means nothing in the running app knows what belongs in it; anything you add there cannot be checked and will be accepted as-is, so say so rather than inventing fields.",
|
|
486
502
|
"requires": [
|
|
487
503
|
"@buoy-gg/zustand installed and the zustand tool registered with FloatingDevTools",
|
|
488
504
|
"the app calls watchStores({...}) or wraps stores with buoyDevTools() — otherwise the registry is empty"
|
|
@@ -490,13 +506,17 @@
|
|
|
490
506
|
},
|
|
491
507
|
{
|
|
492
508
|
"action": "getStoreState",
|
|
493
|
-
"summary": "Fetch one store's full current state object on demand, by store name.",
|
|
509
|
+
"summary": "Fetch one store's full current state object on demand, by store name. Send `path` to get back ONE value instead of the whole store (path:\"lines[lineId=seed-1].qty\") — do that whenever the store is big, because results are cut off at 24,000 characters.",
|
|
494
510
|
"params": {
|
|
495
511
|
"type": "object",
|
|
496
512
|
"properties": {
|
|
497
513
|
"storeName": {
|
|
498
514
|
"type": "string",
|
|
499
515
|
"description": "Registered store name exactly as listStores reports it (e.g. \"counterStore\", \"authStore\", \"cartStore\"). Case-sensitive; no fuzzy matching."
|
|
516
|
+
},
|
|
517
|
+
"path": {
|
|
518
|
+
"type": "string",
|
|
519
|
+
"description": "Return only the value at this path instead of the whole payload. Use it when you only need one field, and ALWAYS when the payload is big: results are cut off at 24,000 characters, and a field you never saw is a field you will guess the shape of. The path must match the CURRENT data — read `shape` first rather than assuming a wrapper. On a bad path it answers with the field names that do exist, and with the path that would have worked if that field lives somewhere else."
|
|
500
520
|
}
|
|
501
521
|
},
|
|
502
522
|
"required": [
|
|
@@ -506,7 +526,7 @@
|
|
|
506
526
|
},
|
|
507
527
|
"effect": "read",
|
|
508
528
|
"release": "works",
|
|
509
|
-
"description": "Use when listStores' compact `keys` view isn't enough, or when the streaming snapshot showed the {__buoyStateOnDevice:true} marker (states over 16KB are withheld from the per-snapshot wire). Returns {found:true, storeName, currentState} — or {found:false, reason:\"missing storeName\"} / {found:false, reason:\"unknown storeName\"}, which is a plain answer, not an error. currentState is read live via store.api.getState(); if that throws it comes back undefined. Over the 8MB detail cap it returns {__buoyTruncated:true, note} instead, or the STATE_ON_DEVICE marker when the state isn't JSON-serializable. Note that action functions living in state (increment, reset, …) do not survive the JSON wire — what you read back is the data half of the store only.",
|
|
529
|
+
"description": "Use when listStores' compact `keys` view isn't enough, or when the streaming snapshot showed the {__buoyStateOnDevice:true} marker (states over 16KB are withheld from the per-snapshot wire). Returns {found:true, storeName, currentState} — or {found:false, reason:\"missing storeName\"} / {found:false, reason:\"unknown storeName\"}, which is a plain answer, not an error. currentState is read live via store.api.getState(); if that throws it comes back undefined. Over the 8MB detail cap it returns {__buoyTruncated:true, note} instead, or the STATE_ON_DEVICE marker when the state isn't JSON-serializable. Note that action functions living in state (increment, reset, …) do not survive the JSON wire — what you read back is the data half of the store only. Every read here also returns `shape`: a one-line sketch of the value's type plus, for each list in it, what its items look like. It is tiny and does not grow with the data, so it survives the 24,000-character cut when the data itself does not — read it before writing, and match it exactly. A list whose `item` is missing is EMPTY, which means nothing in the running app knows what belongs in it; anything you add there cannot be checked and will be accepted as-is, so say so rather than inventing fields. Send `path` to get back ONE value instead of the whole store (path:\"lines[lineId=seed-1].qty\") — do that whenever the store is big, because results are cut off at 24,000 characters.",
|
|
510
530
|
"requires": [
|
|
511
531
|
"the store must already be registered — get the exact name from listStores"
|
|
512
532
|
]
|
|
@@ -536,7 +556,7 @@
|
|
|
536
556
|
},
|
|
537
557
|
{
|
|
538
558
|
"action": "setState",
|
|
539
|
-
"summary": "
|
|
559
|
+
"summary": "Change a live zustand store. Ask Buoy MERGES by default (replace:false): it shallow-merges the fields you send and KEEPS the store's action functions (setQty, removeLine, …) and untouched keys. Never send replace:true unless you mean to reset the whole store — replacing drops the functions (they can't cross the wire), and the app's buttons that call them then crash. To change a list in the store, ADDRESS ITEMS BY THEIR OWN id instead of resending the list: {\"lines\":{\"seed-1\":{\"qty\":5}}} changes one field of one item, {\"lines\":{\"seed-2\":null}} removes that item, and a key that isn't in the list yet appends a new item (send all its fields). Items you don't name are untouched, so this is the only safe form when you haven't seen the whole list — a capped read means you CANNOT resend it without deleting what you weren't shown. Sending a plain ARRAY still works and still replaces the whole list, which is how you deliberately empty it. On a merge it's a TYPED EDIT: you can only change a field that already exists, to the same type, and any list item you add must match the shape of the ones already there — a wrong type, an unknown field, or a malformed new item is refused with the exact path. force:true bypasses. To change a single value, `path` + `value` is the safest form (path:\"lines[lineId=seed-1].qty\", value:5): it is always a merge and there is no nesting to get wrong.",
|
|
540
560
|
"params": {
|
|
541
561
|
"type": "object",
|
|
542
562
|
"properties": {
|
|
@@ -553,17 +573,27 @@
|
|
|
553
573
|
"type": "boolean",
|
|
554
574
|
"description": "true replaces the whole state — including the store's action functions, which breaks every button wired to them until the app reloads. Buoy sends false for you unless you explicitly pass true, so a partial merge is the safe path: pass only the fields you intend to change (e.g. {\"count\": 5}). The ADAPTER's own default is true; this is a deliberately safer default for agent calls.",
|
|
555
575
|
"default": false
|
|
576
|
+
},
|
|
577
|
+
"force": {
|
|
578
|
+
"type": "boolean",
|
|
579
|
+
"description": "Bypass the typed-edit safety on a merge and write raw. Default false — a violating merge is refused."
|
|
580
|
+
},
|
|
581
|
+
"path": {
|
|
582
|
+
"type": "string",
|
|
583
|
+
"description": "Set ONE value, named by its full path in the CURRENT data: path:\"<the real path>\", value:<new value>. READ THE DATA FIRST AND COPY THE PATH FROM IT — `shape` on getStoreState/listStores prints it. A path is only safer than a hand-nested `data` if it matches the payload you are actually looking at; if the field is at the top level the path is just \"name\", and inventing a wrapper that is not there is refused. A list step is written [field=value] or [0] and resolves to that item's own id, so it still means the same row if the list changed. Always a merge, and always through the same typed-edit guard as `data`. Requires `value`; send `data` OR `path`+`value`, not both."
|
|
584
|
+
},
|
|
585
|
+
"value": {
|
|
586
|
+
"description": "The value `path` is set to. Required whenever `path` is sent. null is a real value (only allowed if the field is already nullable), not a delete."
|
|
556
587
|
}
|
|
557
588
|
},
|
|
558
589
|
"required": [
|
|
559
|
-
"storeName"
|
|
560
|
-
"state"
|
|
590
|
+
"storeName"
|
|
561
591
|
],
|
|
562
592
|
"additionalProperties": false
|
|
563
593
|
},
|
|
564
594
|
"effect": "destructive",
|
|
565
595
|
"release": "works",
|
|
566
|
-
"description": "Powers time-travel / reset / 'put the app in this state' from a dashboard. Calls the store's real setState(state, replace ?? true). THE DEFAULT IS DESTRUCTIVE: `replace` defaults to TRUE, so any key missing from `state` is deleted — and Zustand stores conventionally keep their action functions in state (increment, login, addToCart). Functions cannot cross the sync wire, so a JSON `state` object can never carry them back; a default-replace leaves every `useStore(s => s.increment)` call site reading undefined and the app broken until reload. Buoy's own Time Machine restore avoids this by re-grafting the live store's functions onto the snapshot before replacing (packages/zustand/src/zustand/utils/snapshotProvider.ts:8-12) — this raw action does NOT do that. For a QA-facing tweak always pass replace:false to merge just the fields you're changing. Returns {ok:true}, or {ok:false, error:\"Missing storeName.\"} / {ok:false, error:'No store named \"X\".'}. The write also lands in the change log as a normal recorded change.",
|
|
596
|
+
"description": "Powers time-travel / reset / 'put the app in this state' from a dashboard. Calls the store's real setState(state, replace ?? true). THE DEFAULT IS DESTRUCTIVE: `replace` defaults to TRUE, so any key missing from `state` is deleted — and Zustand stores conventionally keep their action functions in state (increment, login, addToCart). Functions cannot cross the sync wire, so a JSON `state` object can never carry them back; a default-replace leaves every `useStore(s => s.increment)` call site reading undefined and the app broken until reload. Buoy's own Time Machine restore avoids this by re-grafting the live store's functions onto the snapshot before replacing (packages/zustand/src/zustand/utils/snapshotProvider.ts:8-12) — this raw action does NOT do that. For a QA-facing tweak always pass replace:false to merge just the fields you're changing. Returns {ok:true}, or {ok:false, error:\"Missing storeName.\"} / {ok:false, error:'No store named \"X\".'}. The write also lands in the change log as a normal recorded change. A write that adds the first item to an EMPTY list comes back ok with `unchecked`: the list had no items, so nothing knew its item shape and yours was not verified. Treat that as a warning to check the screen, not as a pass. To change a single value, `path` + `value` is the safest form (path:\"lines[lineId=seed-1].qty\", value:5): it is always a merge and there is no nesting to get wrong.",
|
|
567
597
|
"requires": [
|
|
568
598
|
"the store must be registered — get the exact name from listStores",
|
|
569
599
|
"the store's registered setState handle: watchStores registers the raw setState, buoyDevTools registers its instrumented set (both apply the write)"
|
|
@@ -912,11 +942,11 @@
|
|
|
912
942
|
{
|
|
913
943
|
"toolId": "query",
|
|
914
944
|
"title": "React Query",
|
|
915
|
-
"summary": "
|
|
945
|
+
"summary": "THE way to change what a server-backed screen shows. Most screens that render API data render this cache, so \"edit what I'm looking at\" on such a screen means setQueryData on the query that is mounted (observers > 0), not a store and not the API. Also reads and mutates the rest of the app's live TanStack Query (React Query) cache on the device: list every query with status/staleness/observers/error, pull one query's real cached data, and then refetch / invalidate / reset / remove / overwrite it, simulate a query error or a perpetual loading state, clear the whole query or mutation cache, and flip TanStack's onlineManager to fake offline. Reach for it when data on screen is stale, wrong, or missing and you need to know whether the CACHE or the API is at fault (network.getSnapshot answers the API half), and for the QA moves — force the error view (triggerError), the loading view (triggerLoading), a specific payload (setQueryData). A cache edit lasts until the next successful refetch; when the change must survive a refetch or a reload, put a network override on the request instead and invalidate. Everything here is the current cache — for the history of query updates over time use the events tool with sources:['react-query'].",
|
|
916
946
|
"actions": [
|
|
917
947
|
{
|
|
918
948
|
"action": "listQueries",
|
|
919
|
-
"summary": "List every query in the cache — hash, key, status, staleness, observer count, last-updated, error message. The token-cheap cache reader; start here.",
|
|
949
|
+
"summary": "List every query in the cache — hash, key, status, staleness, observer count, last-updated, error message. The token-cheap cache reader; start here. Send `staleOnly:true` to see only stale queries. THIS IS A CACHE, NOT AN INVENTORY: it holds only what this app has already fetched in this session, so a short list means the user has not visited those screens yet, never that the data does not exist.",
|
|
920
950
|
"params": {
|
|
921
951
|
"type": "object",
|
|
922
952
|
"properties": {
|
|
@@ -937,7 +967,7 @@
|
|
|
937
967
|
},
|
|
938
968
|
"effect": "read",
|
|
939
969
|
"release": "works",
|
|
940
|
-
"description": "Projects each live query to light fields: queryHash, queryKey, status (one of fresh/stale/fetching/error/inactive/paused/disabled, from getQueryStatusLabel), fetchStatus, isStale, observers, updatedAt (state.dataUpdatedAt), and error.message when present. Returns {queries, total, returned, includedData}. Sorted most-recently-updated first. Safe to call repeatedly — unlike the full dehydrated snapshot, the heavy cached data stays on the device by default. TWO TRAPS: (1) the adapter has NO default limit — omit it and you get every query in the cache; (2) includeData:true returns q.state.data RAW and UNCAPPED (no 16KB wire marker, no 8MB cap like getQueryData), so it can blow the wire/token budget on a big cache — prefer getQueryData for one query's payload. The queryHash of each row is the handle every other action takes.",
|
|
970
|
+
"description": "Projects each live query to light fields: queryHash, queryKey, status (one of fresh/stale/fetching/error/inactive/paused/disabled, from getQueryStatusLabel), fetchStatus, isStale, observers, updatedAt (state.dataUpdatedAt), and error.message when present. Returns {queries, total, returned, includedData}. Sorted most-recently-updated first. Safe to call repeatedly — unlike the full dehydrated snapshot, the heavy cached data stays on the device by default. TWO TRAPS: (1) the adapter has NO default limit — omit it and you get every query in the cache; (2) includeData:true returns q.state.data RAW and UNCAPPED (no 16KB wire marker, no 8MB cap like getQueryData), so it can blow the wire/token budget on a big cache — prefer getQueryData for one query's payload. The queryHash of each row is the handle every other action takes. What is NOT here has not been fetched yet — the cache fills as the user visits screens — so treat a missing key as a screen to go to (route-events.navigate, then highlight-updates.waitFor, then read again), not as an absence to report. Every read here also returns `shape`: a one-line sketch of the value's type plus, for each list in it, what its items look like. It is tiny and does not grow with the data, so it survives the 24,000-character cut when the data itself does not — read it before writing, and match it exactly. A list whose `item` is missing is EMPTY, which means nothing in the running app knows what belongs in it; anything you add there cannot be checked and will be accepted as-is, so say so rather than inventing fields. Send `staleOnly:true` to see only stale queries.",
|
|
941
971
|
"requires": [
|
|
942
972
|
"QueryClientProvider above <FloatingDevTools/>",
|
|
943
973
|
"@tanstack/react-query v5"
|
|
@@ -945,13 +975,17 @@
|
|
|
945
975
|
},
|
|
946
976
|
{
|
|
947
977
|
"action": "getQueryData",
|
|
948
|
-
"summary": "Get ONE query's real cached data by queryHash — the size-guarded channel for a payload the snapshot replaced with a marker.",
|
|
978
|
+
"summary": "Get ONE query's real cached data by queryHash — the size-guarded channel for a payload the snapshot replaced with a marker. Send `path` to get back ONE value instead of the whole payload (path:\"item.name\", path:\"results[name=pikachu].url\") — do that whenever the payload is big, because results are cut off at 24,000 characters.",
|
|
949
979
|
"params": {
|
|
950
980
|
"type": "object",
|
|
951
981
|
"properties": {
|
|
952
982
|
"queryHash": {
|
|
953
983
|
"type": "string",
|
|
954
984
|
"description": "The target query's hash from listQueries — TanStack's default hash is the JSON-stringified key, e.g. '[\"todos\",{\"page\":1}]'. Tolerated if omitted (returns found:false) but then the call does nothing useful."
|
|
985
|
+
},
|
|
986
|
+
"path": {
|
|
987
|
+
"type": "string",
|
|
988
|
+
"description": "Return only the value at this path instead of the whole payload. Use it when you only need one field, and ALWAYS when the payload is big: results are cut off at 24,000 characters, and a field you never saw is a field you will guess the shape of. The path must match the CURRENT data — read `shape` first rather than assuming a wrapper. On a bad path it answers with the field names that do exist, and with the path that would have worked if that field lives somewhere else."
|
|
955
989
|
}
|
|
956
990
|
},
|
|
957
991
|
"required": [
|
|
@@ -961,7 +995,7 @@
|
|
|
961
995
|
},
|
|
962
996
|
"effect": "read",
|
|
963
997
|
"release": "works",
|
|
964
|
-
"description": "Returns {found:true, queryHash, data} where data is query.state.data capped at 8MB (over that you get {__buoyTruncated:true}; non-JSON-serializable values such as circular refs or bigint come back as {__buoyUnserializable:true}). Use this when a snapshot or detail pane shows the {__buoyDataOnDevice:true} marker — streamed snapshots strip anything over 16KB. Never throws: an unknown or missing hash returns {found:false, reason:'unknown queryHash'|'missing queryHash'}.",
|
|
998
|
+
"description": "Returns {found:true, queryHash, data} where data is query.state.data capped at 8MB (over that you get {__buoyTruncated:true}; non-JSON-serializable values such as circular refs or bigint come back as {__buoyUnserializable:true}). Use this when a snapshot or detail pane shows the {__buoyDataOnDevice:true} marker — streamed snapshots strip anything over 16KB. Never throws: an unknown or missing hash returns {found:false, reason:'unknown queryHash'|'missing queryHash'}. Every read here also returns `shape`: a one-line sketch of the value's type plus, for each list in it, what its items look like. It is tiny and does not grow with the data, so it survives the 24,000-character cut when the data itself does not — read it before writing, and match it exactly. A list whose `item` is missing is EMPTY, which means nothing in the running app knows what belongs in it; anything you add there cannot be checked and will be accepted as-is, so say so rather than inventing fields. Send `path` to get back ONE value instead of the whole payload (path:\"item.name\", path:\"results[name=pikachu].url\") — do that whenever the payload is big, because results are cut off at 24,000 characters.",
|
|
965
999
|
"requires": [
|
|
966
1000
|
"QueryClientProvider above <FloatingDevTools/>"
|
|
967
1001
|
]
|
|
@@ -992,7 +1026,7 @@
|
|
|
992
1026
|
},
|
|
993
1027
|
{
|
|
994
1028
|
"action": "invalidate",
|
|
995
|
-
"summary": "Mark a query stale and refetch it if it has active observers — the normal 'this data is out of date' fix.",
|
|
1029
|
+
"summary": "Mark a query stale and refetch it if it has active observers — the normal 'this data is out of date' fix. Takes the query's `queryHash`.",
|
|
996
1030
|
"params": {
|
|
997
1031
|
"type": "object",
|
|
998
1032
|
"properties": {
|
|
@@ -1008,14 +1042,14 @@
|
|
|
1008
1042
|
},
|
|
1009
1043
|
"effect": "write",
|
|
1010
1044
|
"release": "works",
|
|
1011
|
-
"description": "Looks the query up by hash, then passes the Query itself to queryClient.invalidateQueries() as the filter. Because the filter carries only queryKey with no exact:true, matching is a NON-EXACT prefix match: invalidating '[\\\"todos\\\"]' also invalidates '[\\\"todos\\\",{\\\"page\\\":1}]' and any other key that extends it. Mounted (observed) queries refetch immediately, so this can fire real API calls; inactive ones just go stale. Prefer this over refetch when you want the app's own screens to re-render with fresh data. THROWS for an unknown hash.",
|
|
1045
|
+
"description": "Looks the query up by hash, then passes the Query itself to queryClient.invalidateQueries() as the filter. Because the filter carries only queryKey with no exact:true, matching is a NON-EXACT prefix match: invalidating '[\\\"todos\\\"]' also invalidates '[\\\"todos\\\",{\\\"page\\\":1}]' and any other key that extends it. Mounted (observed) queries refetch immediately, so this can fire real API calls; inactive ones just go stale. Prefer this over refetch when you want the app's own screens to re-render with fresh data. THROWS for an unknown hash. Takes the query's `queryHash`.",
|
|
1012
1046
|
"requires": [
|
|
1013
1047
|
"QueryClientProvider above <FloatingDevTools/>"
|
|
1014
1048
|
]
|
|
1015
1049
|
},
|
|
1016
1050
|
{
|
|
1017
1051
|
"action": "reset",
|
|
1018
|
-
"summary": "Reset a query to its initial state — DISCARDS its cached data, then refetches if it is active.",
|
|
1052
|
+
"summary": "Reset a query to its initial state — DISCARDS its cached data, then refetches if it is active. Takes the query's `queryHash`.",
|
|
1019
1053
|
"params": {
|
|
1020
1054
|
"type": "object",
|
|
1021
1055
|
"properties": {
|
|
@@ -1031,14 +1065,14 @@
|
|
|
1031
1065
|
},
|
|
1032
1066
|
"effect": "destructive",
|
|
1033
1067
|
"release": "works",
|
|
1034
|
-
"description": "queryClient.resetQueries() with the Query as filter, so the same NON-EXACT prefix match as invalidate applies (resetting '[\\\"todos\\\"]' also resets deeper todos keys). Unlike invalidate this throws the cached value away and reverts to initialData/pending — screens bound to it will flash their loading state. There is no undo: the data only comes back if the query has a queryFn and an active observer to refetch it. THROWS for an unknown hash.",
|
|
1068
|
+
"description": "queryClient.resetQueries() with the Query as filter, so the same NON-EXACT prefix match as invalidate applies (resetting '[\\\"todos\\\"]' also resets deeper todos keys). Unlike invalidate this throws the cached value away and reverts to initialData/pending — screens bound to it will flash their loading state. There is no undo: the data only comes back if the query has a queryFn and an active observer to refetch it. THROWS for an unknown hash. Takes the query's `queryHash`.",
|
|
1035
1069
|
"requires": [
|
|
1036
1070
|
"QueryClientProvider above <FloatingDevTools/>"
|
|
1037
1071
|
]
|
|
1038
1072
|
},
|
|
1039
1073
|
{
|
|
1040
1074
|
"action": "remove",
|
|
1041
|
-
"summary": "Delete a query from the cache entirely — the entry, its data, and its state are gone.",
|
|
1075
|
+
"summary": "Delete a query from the cache entirely — the entry, its data, and its state are gone. Takes the query's `queryHash`.",
|
|
1042
1076
|
"params": {
|
|
1043
1077
|
"type": "object",
|
|
1044
1078
|
"properties": {
|
|
@@ -1054,14 +1088,14 @@
|
|
|
1054
1088
|
},
|
|
1055
1089
|
"effect": "destructive",
|
|
1056
1090
|
"release": "works",
|
|
1057
|
-
"description": "queryClient.removeQueries() with the Query as filter — again a NON-EXACT prefix match, so removing '[\\\"todos\\\"]' removes every key that extends it. Harsher than reset: the cache entry itself disappears rather than reverting to pending. Irreversible; a mounted component will create a brand-new entry and fetch from scratch on its next render. THROWS for an unknown hash. Returns nothing.",
|
|
1091
|
+
"description": "queryClient.removeQueries() with the Query as filter — again a NON-EXACT prefix match, so removing '[\\\"todos\\\"]' removes every key that extends it. Harsher than reset: the cache entry itself disappears rather than reverting to pending. Irreversible; a mounted component will create a brand-new entry and fetch from scratch on its next render. THROWS for an unknown hash. Returns nothing. Takes the query's `queryHash`.",
|
|
1058
1092
|
"requires": [
|
|
1059
1093
|
"QueryClientProvider above <FloatingDevTools/>"
|
|
1060
1094
|
]
|
|
1061
1095
|
},
|
|
1062
1096
|
{
|
|
1063
1097
|
"action": "setQueryData",
|
|
1064
|
-
"summary": "
|
|
1098
|
+
"summary": "Change a query's cached data — takes the queryKey ARRAY, not the hash. The instant, on-screen edit for anything a mounted query renders. To change some fields send merge:true and ONLY those fields (deep-merged into the cached value); never paste a whole payload back — large getQueryData results are truncated, and a replace that drops fields the screen renders is refused. Reverts on the next successful refetch — say so; use a network override when it must not. To change a single field, `path` + `value` is the safest form (path:\"item.name\", value:\"test123\") — always a merge, and no nesting to get wrong. Read the data first either way: the path has to match the shape that is actually there.",
|
|
1065
1099
|
"params": {
|
|
1066
1100
|
"type": "object",
|
|
1067
1101
|
"properties": {
|
|
@@ -1070,29 +1104,43 @@
|
|
|
1070
1104
|
"description": "The query's key ARRAY exactly as listQueries returns it, e.g. [\"todos\",{\"page\":1}] — NOT the queryHash string. An unknown key creates a new cache entry."
|
|
1071
1105
|
},
|
|
1072
1106
|
"data": {
|
|
1073
|
-
"description": "
|
|
1107
|
+
"description": "With merge:true: only the fields to change, nested to match the current shape. Without: the complete new value. If you are changing a single field, use `path`+`value` instead — it cannot be nested wrongly."
|
|
1074
1108
|
},
|
|
1075
1109
|
"queryHash": {
|
|
1076
1110
|
"type": "string",
|
|
1077
1111
|
"description": "Declared by the adapter's param type but never read by the handler — passing it has no effect."
|
|
1112
|
+
},
|
|
1113
|
+
"merge": {
|
|
1114
|
+
"type": "boolean",
|
|
1115
|
+
"description": "Deep-merge `data` into the cached value as a TYPED LEAF EDIT — like the React Query devtools editor. Change the value of a field that already exists, to the SAME type: no new object fields, no type changes, no null-ing a rendered list/object. A LIST may gain or lose items, but every item you send must have the same fields as the items already in it — to change one item, resend the WHOLE list with just that item changed. Refused with the exact field on a violation. Default false."
|
|
1116
|
+
},
|
|
1117
|
+
"force": {
|
|
1118
|
+
"type": "boolean",
|
|
1119
|
+
"description": "Bypass the typed-edit safety and write raw — can add/remove fields, change types, resize lists. This is what crashes screens; use ONLY when you deliberately mean to replace the whole shape. Default false."
|
|
1120
|
+
},
|
|
1121
|
+
"path": {
|
|
1122
|
+
"type": "string",
|
|
1123
|
+
"description": "Set ONE value, named by its full path in the CURRENT data: path:\"<the real path>\", value:<new value>. READ THE DATA FIRST AND COPY THE PATH FROM IT — `shape` on getQueryData/listQueries prints it. A path is only safer than a hand-nested `data` if it matches the payload you are actually looking at; if the field is at the top level the path is just \"name\", and inventing a wrapper that is not there is refused. A list step is written [field=value] or [0] and resolves to that item's own id, so it still means the same row if the list changed. Always a merge, and always through the same typed-edit guard as `data`. Requires `value`; send `data` OR `path`+`value`, not both."
|
|
1124
|
+
},
|
|
1125
|
+
"value": {
|
|
1126
|
+
"description": "The value `path` is set to. Required whenever `path` is sent. null is a real value (only allowed if the field is already nullable), not a delete."
|
|
1078
1127
|
}
|
|
1079
1128
|
},
|
|
1080
1129
|
"required": [
|
|
1081
|
-
"queryKey"
|
|
1082
|
-
"data"
|
|
1130
|
+
"queryKey"
|
|
1083
1131
|
],
|
|
1084
1132
|
"additionalProperties": false
|
|
1085
1133
|
},
|
|
1086
1134
|
"effect": "write",
|
|
1087
1135
|
"release": "works",
|
|
1088
|
-
"description": "queryClient.setQueryData(queryKey,
|
|
1136
|
+
"description": "queryClient.setQueryData(queryKey, value, {updatedAt: Date.now()}). With merge:true the value written is the CURRENT cached data deep-merged with `data` (plain objects merge key by key, arrays and primitives are replaced), so `{name:'test123'}` changes one field of a 30KB payload. TO CHANGE ONE ITEM IN A LIST, address it by its own id rather than resending the array: if rows carry an id, `{results:{pikachu:{name:'test123'}}}` edits that row and leaves every other row untouched. This is the ONLY correct form when the read was capped and you did not see every row — a plain array REPLACES the list, so a one-item array deletes the rest. Without merge it REPLACES — and if the cached value is an object whose top-level keys `data` lacks, the call is refused with {ok:false, error, missingKeys} unless force:true, because a screen that renders the dropped fields crashes. Returns {ok:true, mode:'merge'|'replace'}. Gotchas: (1) it keys off queryKey, the actual array from a listQueries row (e.g. ['todos',{page:1}]) — the declared param type also mentions queryHash but the handler IGNORES it; (2) if that key is not in the cache, setQueryData CREATES a new entry with no queryFn, which then errors if anything refetches it. The injected value is overwritten by the next successful refetch. When a merge is refused for writing at the wrong depth, the refusal carries `suggestedData`: the SAME edit rebuilt at the right depth. Send that back as `data` rather than re-deriving it. To change a single field, `path` + `value` is the safest form (path:\"item.name\", value:\"test123\") — always a merge, and no nesting to get wrong. Read the data first either way: the path has to match the shape that is actually there.",
|
|
1089
1137
|
"requires": [
|
|
1090
1138
|
"QueryClientProvider above <FloatingDevTools/>"
|
|
1091
1139
|
]
|
|
1092
1140
|
},
|
|
1093
1141
|
{
|
|
1094
1142
|
"action": "triggerError",
|
|
1095
|
-
"summary": "Force a query into error status with a fake Error, to exercise the app's error UI.",
|
|
1143
|
+
"summary": "Force a query into error status with a fake Error, to exercise the app's error UI. The QA move for 'show me this screen's error state'; undo with restoreError.",
|
|
1096
1144
|
"params": {
|
|
1097
1145
|
"type": "object",
|
|
1098
1146
|
"properties": {
|
|
@@ -1138,7 +1186,7 @@
|
|
|
1138
1186
|
},
|
|
1139
1187
|
{
|
|
1140
1188
|
"action": "triggerLoading",
|
|
1141
|
-
"summary": "Pin a query in a permanent loading/suspense state to exercise skeletons and spinners. MUST be undone.",
|
|
1189
|
+
"summary": "Pin a query in a permanent loading/suspense state to exercise skeletons and spinners. MUST be undone. The QA move for 'show me this screen's loading state'; undo with restoreLoading.",
|
|
1142
1190
|
"params": {
|
|
1143
1191
|
"type": "object",
|
|
1144
1192
|
"properties": {
|
|
@@ -1690,7 +1738,7 @@
|
|
|
1690
1738
|
},
|
|
1691
1739
|
{
|
|
1692
1740
|
"action": "upsertOverrideRule",
|
|
1693
|
-
"summary": "Create (or replace by id) a rule that forces matching requests to return a chosen status/body, fail outright, or arrive late.",
|
|
1741
|
+
"summary": "Create (or replace by id) a rule that forces matching requests to return a chosen status/body, fail outright, or arrive late. Pick this over query.setQueryData when the change must survive a refetch or reload, or when the ask is about what the API returns; follow it with query.invalidate so the mounted screen refetches through the rule. To change ONE value of the real response send fromRequestId plus `bodyPath` and `bodyValue` (bodyPath:\"sprites.front_default\", or \"stats[stat.name=hp].base_stat\" for one row of a list) — there is no nesting to get wrong, which matters most here because you read that body through a wire that truncates at 24,000 characters. For several fields use `bodyPatch` with ONLY those fields; never paste a whole captured body back. For \"just the NEXT call\" / \"once, then let it work again\", send rule.times:1 — the rule auto-disables after one match. Without times it overrides EVERY matching request until deleted. Never fake one-shot by triggering the request yourself and deleting the rule: that spends the failure the user wanted to see.",
|
|
1694
1742
|
"params": {
|
|
1695
1743
|
"type": "object",
|
|
1696
1744
|
"properties": {
|
|
@@ -1732,7 +1780,7 @@
|
|
|
1732
1780
|
"fail",
|
|
1733
1781
|
"delay"
|
|
1734
1782
|
],
|
|
1735
|
-
"description": "respond = return your status/body, never hitting the network (default). fail = transport failure, as if offline. delay = run the real request, just late. Anything else falls back to respond."
|
|
1783
|
+
"description": "respond = return your status/body, never hitting the network (default). fail = transport failure, as if offline (`fail:true` is accepted for this). delay = run the real request, just late. Anything else falls back to respond."
|
|
1736
1784
|
},
|
|
1737
1785
|
"status": {
|
|
1738
1786
|
"type": "number",
|
|
@@ -1747,7 +1795,7 @@
|
|
|
1747
1795
|
"description": "kind 'respond': response headers, string values."
|
|
1748
1796
|
},
|
|
1749
1797
|
"body": {
|
|
1750
|
-
"description": "kind 'respond': the response body. A string is used as-is; an object or array is JSON.stringify'd for you — do not pre-stringify."
|
|
1798
|
+
"description": "kind 'respond': the COMPLETE response body. A string is used as-is; an object or array is JSON.stringify'd for you — do not pre-stringify. To change a few fields of the real response use bodyPatch instead — bodies you read over the wire are truncated past 24K chars, and pasting a torn body back gives the app a response missing half its fields."
|
|
1751
1799
|
},
|
|
1752
1800
|
"failKind": {
|
|
1753
1801
|
"type": "string",
|
|
@@ -1762,7 +1810,7 @@
|
|
|
1762
1810
|
},
|
|
1763
1811
|
"times": {
|
|
1764
1812
|
"type": "number",
|
|
1765
|
-
"description": "Auto-disable after N matches.
|
|
1813
|
+
"description": "Auto-disable after N matches. **Set times:1 whenever the ask is about the NEXT call** — \"make the next request fail\", \"just once\", \"then let it work again\". Omitted = the rule overrides EVERY matching request until something deletes it, and the result will tell you so. `once:true`, `failOnce:true` and `maxHits:N` are accepted as ways of saying this."
|
|
1766
1814
|
},
|
|
1767
1815
|
"alternate": {
|
|
1768
1816
|
"type": "boolean",
|
|
@@ -1771,6 +1819,21 @@
|
|
|
1771
1819
|
"bodyOmitted": {
|
|
1772
1820
|
"type": "boolean",
|
|
1773
1821
|
"description": "Wire flag: send true WITH an `id` when re-saving a rule whose body the snapshot withheld, so the stored body is preserved instead of erased."
|
|
1822
|
+
},
|
|
1823
|
+
"bodyPatch": {
|
|
1824
|
+
"type": "object",
|
|
1825
|
+
"description": "Fields to change in the real captured response (needs fromRequestId, or the id of an existing rule) as a TYPED LEAF EDIT — like the devtools editor: same-type changes to existing fields, no new fields, no type changes, no null-ing a rendered list/object. A list may gain/lose items but each item must match the existing item shape (to change one item resend the whole list). Refused with the exact field on a violation. Ignored when `body` is sent. To change ONE ROW of a list in the response, address it by its own id instead of resending the array: {results:{pikachu:{name:'test123'}}} edits that row and leaves every other row exactly as the server sent it. That is the only correct form when you read the response through a view that truncated it."
|
|
1826
|
+
},
|
|
1827
|
+
"force": {
|
|
1828
|
+
"type": "boolean",
|
|
1829
|
+
"description": "Bypass the typed-edit safety on bodyPatch and write the patch raw. Default false — a violating patch is refused."
|
|
1830
|
+
},
|
|
1831
|
+
"bodyPath": {
|
|
1832
|
+
"type": "string",
|
|
1833
|
+
"description": "Set ONE value inside the real response, named by its path (\"sprites.front_default\", \"stats[stat.name=hp].base_stat\"). Needs fromRequestId or an existing rule id to address. Always goes through the same typed-edit guard as bodyPatch."
|
|
1834
|
+
},
|
|
1835
|
+
"bodyValue": {
|
|
1836
|
+
"description": "The value `bodyPath` is set to. Required whenever bodyPath is sent."
|
|
1774
1837
|
}
|
|
1775
1838
|
},
|
|
1776
1839
|
"additionalProperties": false
|
|
@@ -1780,7 +1843,7 @@
|
|
|
1780
1843
|
},
|
|
1781
1844
|
"effect": "write",
|
|
1782
1845
|
"release": "noop",
|
|
1783
|
-
"description": "
|
|
1846
|
+
"description": "Create (or replace by id) a rule that forces matching requests to return a chosen status/body, fail outright, or arrive late. Pick this over query.setQueryData when the change must survive a refetch or reload, or when the ask is about what the API returns; follow it with query.invalidate so the mounted screen refetches through the rule. To change ONE value of the real response send fromRequestId plus `bodyPath` and `bodyValue` (bodyPath:\"sprites.front_default\", or \"stats[stat.name=hp].base_stat\" for one row of a list) — there is no nesting to get wrong, which matters most here because you read that body through a wire that truncates at 24,000 characters. For several fields use `bodyPatch` with ONLY those fields; never paste a whole captured body back. For \"just the NEXT call\" / \"once, then let it work again\", send rule.times:1 — the rule auto-disables after one match. Without times it overrides EVERY matching request until deleted. Never fake one-shot by triggering the request yourself and deleting the rule: that spends the failure the user wanted to see.",
|
|
1784
1847
|
"releaseNote": "packages/network/src/network/overrides/engine.ts:74 — overrideForRequest returns null when __DEV__ === false. The rule is still created, persisted and listed, and this action still returns ok:true, but it is NEVER applied to real traffic in a release build. Confirm with debugOverrides → engine.devFlag before telling anyone the override took effect.",
|
|
1785
1848
|
"requires": [
|
|
1786
1849
|
"a __DEV__ build for the rule to actually apply",
|
|
@@ -2932,15 +2995,20 @@
|
|
|
2932
2995
|
"actions": [
|
|
2933
2996
|
{
|
|
2934
2997
|
"action": "describeScreen",
|
|
2935
|
-
"summary": "List every meaningful/interactive element currently on screen, with normalized tap points — the read half of driving the app.",
|
|
2998
|
+
"summary": "List every meaningful/interactive element currently on screen, with normalized tap points — the read half of driving the app. The ONLY way to read what is on screen but in no store: text, values and names held in component state, which no other Buoy tool can see.",
|
|
2936
2999
|
"params": {
|
|
2937
3000
|
"type": "object",
|
|
2938
|
-
"properties": {
|
|
3001
|
+
"properties": {
|
|
3002
|
+
"includeBuoy": {
|
|
3003
|
+
"type": "boolean",
|
|
3004
|
+
"description": "Also list Buoy's own overlay (the dial, tool sheets, Ask Buoy's own chat). Default false — those are the tool, not the app."
|
|
3005
|
+
}
|
|
3006
|
+
},
|
|
2939
3007
|
"additionalProperties": false
|
|
2940
3008
|
},
|
|
2941
3009
|
"effect": "read",
|
|
2942
3010
|
"release": "empty",
|
|
2943
|
-
"description": "Walks the live React fiber tree across ALL renderers and measures each candidate. Returns {screen:{width,height}, count, elements[]} where each element has: nativeTag, name (owning component, not the host View), role, testID, label, text, interactive, control ('toggle'|'slider'|'text', omitted for a plain press), value (current value for toggle/slider/text), longPressable, tap:{x,y} and frame:{x,y,width,height} both normalized to 0-1. Sorted top-to-bottom then left-to-right. Prunes offscreen/inactive react-native-screens and hidden Offscreen subtrees, so it reflects the CURRENT screen only — re-run after every navigation, positions and tags move. No screenshot and no tracking needed; it does not require the highlight overlay to be enabled. Call this before tapElement to get an exact testID/nativeTag instead of guessing a fuzzy query.",
|
|
3011
|
+
"description": "Walks the live React fiber tree across ALL renderers and measures each candidate. Returns {screen:{width,height}, count, elements[]} where each element has: nativeTag, name (owning component, not the host View), role, testID, label, text, interactive, control ('toggle'|'slider'|'text', omitted for a plain press), value (current value for toggle/slider/text), longPressable, tap:{x,y} and frame:{x,y,width,height} both normalized to 0-1. Sorted top-to-bottom then left-to-right. Prunes offscreen/inactive react-native-screens and hidden Offscreen subtrees, so it reflects the CURRENT screen only — re-run after every navigation, positions and tags move. No screenshot and no tracking needed; it does not require the highlight overlay to be enabled. Call this before tapElement to get an exact testID/nativeTag instead of guessing a fuzzy query. Buoy's OWN overlay is left out — the dial, tool sheets and your own chat sheet are the tool, not the app under test — and `hiddenBuoy` counts what was omitted; pass includeBuoy:true only when the task is about Buoy itself.",
|
|
2944
3012
|
"releaseNote": "packages/highlight-updates/src/highlight-updates/utils/screenElements.ts:57 — getReactDevToolsHook() reads __REACT_DEVTOOLS_GLOBAL_HOOK__, which RN installs only under __DEV__; getAllFiberRoots() then returns [] and collectCandidates() short-circuits at line 539, so the result is a valid-looking {count:0, elements:[]} rather than an error.",
|
|
2945
3013
|
"requires": [
|
|
2946
3014
|
"@buoy-gg/highlight-updates installed and registered with <FloatingDevTools>",
|
|
@@ -2949,7 +3017,7 @@
|
|
|
2949
3017
|
},
|
|
2950
3018
|
{
|
|
2951
3019
|
"action": "tapElement",
|
|
2952
|
-
"summary": "Interact with one on-screen element by invoking its real handler in JS: press, long-press, toggle/slider, or type into a text input.",
|
|
3020
|
+
"summary": "Interact with one on-screen element by invoking its real handler in JS: press, long-press, toggle/slider, or type into a text input. This is how you make the app FETCH MORE — press its own \"next\", \"load more\" or a list row, waitFor, then read: the app fetches through its own code, so the data arrives in the shape its own screens render.",
|
|
2953
3021
|
"params": {
|
|
2954
3022
|
"type": "object",
|
|
2955
3023
|
"properties": {
|
|
@@ -2983,13 +3051,17 @@
|
|
|
2983
3051
|
"scrollIntoView": {
|
|
2984
3052
|
"type": "boolean",
|
|
2985
3053
|
"description": "Scroll an ancestor ScrollView so the target is visible before acting. Default true — set false to avoid moving the user's screen."
|
|
3054
|
+
},
|
|
3055
|
+
"includeBuoy": {
|
|
3056
|
+
"type": "boolean",
|
|
3057
|
+
"description": "Let a fuzzy `query` match Buoy's own overlay. Default false. An exact nativeTag/testID always may."
|
|
2986
3058
|
}
|
|
2987
3059
|
},
|
|
2988
3060
|
"additionalProperties": false
|
|
2989
3061
|
},
|
|
2990
3062
|
"effect": "write",
|
|
2991
3063
|
"release": "empty",
|
|
2992
|
-
"description": "WARNING: this fires the app's ACTUAL handler, so it can trigger irreversible flows (delete, purchase, logout, submit). Matching by fuzzy `query` can select the wrong element — prefer an exact nativeTag or testID from describeScreen, and confirm the target before firing anything consequential. Resolution order: the element's OWN onValueChange (toggle/slider) or onChangeText (text input) wins; otherwise the nearest onPress walking up the fiber chain (up to 25 levels). Returns {tapped, reason?, scrolled?, matched:{nativeTag,name,testID,label,text,via,value}, candidates?} — `via` is onPress|onLongPress|onValueChange|onChangeText. Trap: passing `text` to something that is not a text input silently runs its onPress instead, so check `matched.via` in the result. Elements driven only by react-native-gesture-handler GestureDetector have no JS handler and return tapped:false with a clear reason. If the target is off-screen it is scrolled into view first (this moves the user's screen). Provide exactly one of nativeTag / testID / query.",
|
|
3064
|
+
"description": "WARNING: this fires the app's ACTUAL handler, so it can trigger irreversible flows (delete, purchase, logout, submit). Matching by fuzzy `query` can select the wrong element — prefer an exact nativeTag or testID from describeScreen, and confirm the target before firing anything consequential. Resolution order: the element's OWN onValueChange (toggle/slider) or onChangeText (text input) wins; otherwise the nearest onPress walking up the fiber chain (up to 25 levels). Returns {tapped, reason?, scrolled?, matched:{nativeTag,name,testID,label,text,via,value}, candidates?} — `via` is onPress|onLongPress|onValueChange|onChangeText. Trap: passing `text` to something that is not a text input silently runs its onPress instead, so check `matched.via` in the result. Elements driven only by react-native-gesture-handler GestureDetector have no JS handler and return tapped:false with a clear reason. If the target is off-screen it is scrolled into view first (this moves the user's screen). Provide exactly one of nativeTag / testID / query. A fuzzy query never matches Buoy's own overlay unless includeBuoy:true (your chat sheet echoes the words you search for; that echo used to be the best match). READ THE RESULT BEFORE TRUSTING THE TAP: `effect.commits` is how many React commits followed inside the settle window, and 0 means the handler ran but NOTHING re-rendered — the match was probably a screen still mounted underneath the current one (a stack keeps them), or an inert control — so treat it as not done and pick another target from describeScreen. `alsoMatched` lists elements that matched equally well; non-empty means the choice was a coin toss, so re-tap by nativeTag.",
|
|
2993
3065
|
"releaseNote": "packages/highlight-updates/src/highlight-updates/utils/screenElements.ts:793 — collectCandidates() returns [] with no DevTools hook, so it returns {tapped:false, reason:\"No React fiber roots / elements found on screen.\"}; it reports the failure honestly rather than claiming a tap.",
|
|
2994
3066
|
"requires": [
|
|
2995
3067
|
"@buoy-gg/highlight-updates installed and registered with <FloatingDevTools>",
|
|
@@ -2999,7 +3071,7 @@
|
|
|
2999
3071
|
{
|
|
3000
3072
|
"action": "waitFor",
|
|
3001
3073
|
"summary": "Block until an element is on screen (or gone), then report how long it took. Use it between navigating and tapping.",
|
|
3002
|
-
"description": "Returns {ok, waitedMs, polls, matched?, reason?}. USE THIS AFTER ANY ACTION THAT STARTS A LOAD — `route-events.navigate` returns the moment the route is pushed, not when the screen has data, so tapping straight after it hits a loading skeleton and fails for a reason that looks like a bad selector. Matching is `tapElement`'s exactly (same fields, same ranking), so a wait can never resolve on an element the tap then cannot find. Presence means ON SCREEN: a candidate matching by name is measured before it counts, so a previous screen still mounted behind this one does not satisfy the wait. `gone:true` waits for absence instead — and returns immediately when nothing matched in the first place, which is a real answer, not a failure. Defaults: timeoutMs 5000 (capped at 30000), pollMs 150 (floored at 50, because this polls on the JS thread the app renders on). Provide exactly one of nativeTag / testID / query.",
|
|
3074
|
+
"description": "Returns {ok, waitedMs, polls, matched?, reason?}. USE THIS AFTER ANY ACTION THAT STARTS A LOAD — `route-events.navigate` returns the moment the route is pushed, not when the screen has data, so tapping straight after it hits a loading skeleton and fails for a reason that looks like a bad selector. Matching is `tapElement`'s exactly (same fields, same ranking), so a wait can never resolve on an element the tap then cannot find. Presence means ON SCREEN: a candidate matching by name is measured before it counts, so a previous screen still mounted behind this one does not satisfy the wait. `gone:true` waits for absence instead — and returns immediately when nothing matched in the first place, which is a real answer, not a failure. Like tapElement, a fuzzy query ignores Buoy's own overlay unless includeBuoy:true; an exact nativeTag/testID always counts. Defaults: timeoutMs 5000 (capped at 30000), pollMs 150 (floored at 50, because this polls on the JS thread the app renders on). Provide exactly one of nativeTag / testID / query.",
|
|
3003
3075
|
"params": {
|
|
3004
3076
|
"type": "object",
|
|
3005
3077
|
"properties": {
|
|
@@ -3026,6 +3098,10 @@
|
|
|
3026
3098
|
"pollMs": {
|
|
3027
3099
|
"type": "number",
|
|
3028
3100
|
"description": "Gap between scans. Default 150, floored at 50."
|
|
3101
|
+
},
|
|
3102
|
+
"includeBuoy": {
|
|
3103
|
+
"type": "boolean",
|
|
3104
|
+
"description": "Let a fuzzy `query` match Buoy's own overlay. Default false. An exact nativeTag/testID always may."
|
|
3029
3105
|
}
|
|
3030
3106
|
},
|
|
3031
3107
|
"additionalProperties": false
|
|
@@ -4922,7 +4998,7 @@
|
|
|
4922
4998
|
{
|
|
4923
4999
|
"toolId": "ask-buoy",
|
|
4924
5000
|
"title": "Ask Buoy",
|
|
4925
|
-
"summary": "Ask Buoy's OWN session — the changes it has made in this conversation, and the undo for them. Call listChanges when the user asks \"what did you change?\"; call undoAll when they say \"undo that\" / \"put it back\" / \"clean up\". Undo restores exactly what the ledger captured at write time: override rules Ask Buoy created are deleted, impersonation is stopped, storage keys are restored to their pre-write values. Changes listed with reversible:false (state writes, wipes, one-shot actions) cannot be automatically reversed — say so honestly. NEVER improvise a reverse-write with a guessed shape instead of calling undoAll; a hand-rolled \"undo\" that writes invented data is worse than telling the user a change is permanent.",
|
|
5001
|
+
"summary": "Ask Buoy's OWN session — the changes it has made in this conversation, and the undo for them. Call listChanges when the user asks \"what did you change?\"; call undoAll when they say \"undo that\" / \"put it back\" / \"clean up\". Undo restores exactly what the ledger captured at write time: override rules Ask Buoy created are deleted, impersonation is stopped, storage keys are restored to their pre-write values, and a query cache edit (setQueryData) is put back to the data read just before the write — unless the app has refetched it since, in which case the server's data is already back and undo says so. Changes listed with reversible:false (state writes, wipes, one-shot actions) cannot be automatically reversed — say so honestly. NEVER improvise a reverse-write with a guessed shape instead of calling undoAll; a hand-rolled \"undo\" that writes invented data is worse than telling the user a change is permanent.",
|
|
4926
5002
|
"actions": [
|
|
4927
5003
|
{
|
|
4928
5004
|
"action": "listChanges",
|