@buoy-gg/agent-core 7.0.41 → 7.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/lib/commonjs/blocks/receipts.js +1 -761
  2. package/lib/commonjs/blocks/runId.js +1 -31
  3. package/lib/commonjs/blocks/types.js +1 -140
  4. package/lib/commonjs/blocks/uiTool.js +6 -1405
  5. package/lib/commonjs/catalog/catalog.g.js +5 -4556
  6. package/lib/commonjs/catalog/catalog.source.json +249 -0
  7. package/lib/commonjs/catalog/catalog.types.g.js +1 -46
  8. package/lib/commonjs/catalog/normalizeParams.js +2 -332
  9. package/lib/commonjs/catalog/signature.js +1 -68
  10. package/lib/commonjs/catalog/snapshotReads.js +1 -263
  11. package/lib/commonjs/catalog/toProviderTools.js +2 -145
  12. package/lib/commonjs/catalog/validateParams.js +1 -129
  13. package/lib/commonjs/context/buildContextPack.js +1 -489
  14. package/lib/commonjs/effects/digest.js +1 -34
  15. package/lib/commonjs/effects/ledger.js +1 -345
  16. package/lib/commonjs/engine/askGate.js +1 -287
  17. package/lib/commonjs/engine/effectFor.js +1 -223
  18. package/lib/commonjs/engine/evidence.js +3 -111
  19. package/lib/commonjs/engine/historyBudget.js +1 -363
  20. package/lib/commonjs/engine/retrieve.js +1 -214
  21. package/lib/commonjs/engine/runAgentTurn.js +8 -1836
  22. package/lib/commonjs/engine/systemPrompt.js +54 -163
  23. package/lib/commonjs/engine/textToolCalls.js +1 -276
  24. package/lib/commonjs/engine/tokenCalibration.js +1 -81
  25. package/lib/commonjs/engine/verify.js +4 -297
  26. package/lib/commonjs/index.js +1 -384
  27. package/lib/commonjs/policy/labels.js +1 -319
  28. package/lib/commonjs/policy/policy.js +1 -148
  29. package/lib/commonjs/policy/redact.js +1 -172
  30. package/lib/commonjs/providers/anthropic.js +3 -445
  31. package/lib/commonjs/providers/openai.js +4 -324
  32. package/lib/commonjs/providers/problem.js +1 -98
  33. package/lib/commonjs/providers/sse.js +1 -240
  34. package/lib/commonjs/providers/streamTimer.js +1 -123
  35. package/lib/commonjs/providers/transport.js +1 -123
  36. package/lib/commonjs/providers/types.js +1 -6
  37. package/lib/commonjs/providers/xhrStream.js +1 -212
  38. package/lib/commonjs/realNow.js +1 -0
  39. package/lib/commonjs/session.js +1 -393
  40. package/lib/commonjs/types.js +0 -1
  41. package/lib/module/blocks/receipts.js +1 -755
  42. package/lib/module/blocks/runId.js +1 -26
  43. package/lib/module/blocks/types.js +1 -134
  44. package/lib/module/blocks/uiTool.js +6 -1396
  45. package/lib/module/catalog/catalog.g.js +5 -4552
  46. package/lib/module/catalog/catalog.source.json +249 -0
  47. package/lib/module/catalog/catalog.types.g.js +1 -42
  48. package/lib/module/catalog/normalizeParams.js +2 -326
  49. package/lib/module/catalog/signature.js +1 -63
  50. package/lib/module/catalog/snapshotReads.js +1 -259
  51. package/lib/module/catalog/toProviderTools.js +2 -139
  52. package/lib/module/catalog/validateParams.js +1 -124
  53. package/lib/module/context/buildContextPack.js +1 -485
  54. package/lib/module/effects/digest.js +1 -29
  55. package/lib/module/effects/ledger.js +1 -339
  56. package/lib/module/engine/askGate.js +1 -280
  57. package/lib/module/engine/effectFor.js +1 -220
  58. package/lib/module/engine/evidence.js +3 -105
  59. package/lib/module/engine/historyBudget.js +1 -355
  60. package/lib/module/engine/retrieve.js +1 -210
  61. package/lib/module/engine/runAgentTurn.js +8 -1827
  62. package/lib/module/engine/systemPrompt.js +54 -157
  63. package/lib/module/engine/textToolCalls.js +1 -270
  64. package/lib/module/engine/tokenCalibration.js +1 -75
  65. package/lib/module/engine/verify.js +4 -289
  66. package/lib/module/index.js +1 -35
  67. package/lib/module/policy/labels.js +1 -312
  68. package/lib/module/policy/policy.js +1 -142
  69. package/lib/module/policy/redact.js +1 -165
  70. package/lib/module/providers/anthropic.js +3 -441
  71. package/lib/module/providers/openai.js +4 -320
  72. package/lib/module/providers/problem.js +1 -92
  73. package/lib/module/providers/sse.js +1 -231
  74. package/lib/module/providers/streamTimer.js +1 -118
  75. package/lib/module/providers/transport.js +1 -119
  76. package/lib/module/providers/types.js +1 -4
  77. package/lib/module/providers/xhrStream.js +1 -207
  78. package/lib/module/realNow.js +1 -0
  79. package/lib/module/session.js +1 -369
  80. package/lib/module/types.js +0 -1
  81. package/lib/typescript/catalog/catalog.g.d.ts +3 -3
  82. package/lib/typescript/catalog/catalog.types.g.d.ts +3 -2
  83. package/lib/typescript/providers/problem.d.ts +0 -20
  84. package/lib/typescript/providers/streamTimer.d.ts +0 -24
  85. package/lib/typescript/realNow.d.ts +18 -0
  86. package/lib/web/index.mjs +52 -53
  87. package/package.json +1 -1
  88. package/lib/commonjs/blocks/receipts.js.map +0 -1
  89. package/lib/commonjs/blocks/runId.js.map +0 -1
  90. package/lib/commonjs/blocks/types.js.map +0 -1
  91. package/lib/commonjs/blocks/uiTool.js.map +0 -1
  92. package/lib/commonjs/catalog/catalog.g.js.map +0 -1
  93. package/lib/commonjs/catalog/catalog.types.g.js.map +0 -1
  94. package/lib/commonjs/catalog/normalizeParams.js.map +0 -1
  95. package/lib/commonjs/catalog/signature.js.map +0 -1
  96. package/lib/commonjs/catalog/snapshotReads.js.map +0 -1
  97. package/lib/commonjs/catalog/toProviderTools.js.map +0 -1
  98. package/lib/commonjs/catalog/validateParams.js.map +0 -1
  99. package/lib/commonjs/context/buildContextPack.js.map +0 -1
  100. package/lib/commonjs/effects/digest.js.map +0 -1
  101. package/lib/commonjs/effects/ledger.js.map +0 -1
  102. package/lib/commonjs/engine/askGate.js.map +0 -1
  103. package/lib/commonjs/engine/effectFor.js.map +0 -1
  104. package/lib/commonjs/engine/evidence.js.map +0 -1
  105. package/lib/commonjs/engine/historyBudget.js.map +0 -1
  106. package/lib/commonjs/engine/retrieve.js.map +0 -1
  107. package/lib/commonjs/engine/runAgentTurn.js.map +0 -1
  108. package/lib/commonjs/engine/systemPrompt.js.map +0 -1
  109. package/lib/commonjs/engine/textToolCalls.js.map +0 -1
  110. package/lib/commonjs/engine/tokenCalibration.js.map +0 -1
  111. package/lib/commonjs/engine/verify.js.map +0 -1
  112. package/lib/commonjs/index.js.map +0 -1
  113. package/lib/commonjs/policy/labels.js.map +0 -1
  114. package/lib/commonjs/policy/policy.js.map +0 -1
  115. package/lib/commonjs/policy/redact.js.map +0 -1
  116. package/lib/commonjs/providers/anthropic.js.map +0 -1
  117. package/lib/commonjs/providers/openai.js.map +0 -1
  118. package/lib/commonjs/providers/problem.js.map +0 -1
  119. package/lib/commonjs/providers/sse.js.map +0 -1
  120. package/lib/commonjs/providers/streamTimer.js.map +0 -1
  121. package/lib/commonjs/providers/transport.js.map +0 -1
  122. package/lib/commonjs/providers/types.js.map +0 -1
  123. package/lib/commonjs/providers/xhrStream.js.map +0 -1
  124. package/lib/commonjs/session.js.map +0 -1
  125. package/lib/commonjs/types.js.map +0 -1
  126. package/lib/module/blocks/receipts.js.map +0 -1
  127. package/lib/module/blocks/runId.js.map +0 -1
  128. package/lib/module/blocks/types.js.map +0 -1
  129. package/lib/module/blocks/uiTool.js.map +0 -1
  130. package/lib/module/catalog/catalog.g.js.map +0 -1
  131. package/lib/module/catalog/catalog.types.g.js.map +0 -1
  132. package/lib/module/catalog/normalizeParams.js.map +0 -1
  133. package/lib/module/catalog/signature.js.map +0 -1
  134. package/lib/module/catalog/snapshotReads.js.map +0 -1
  135. package/lib/module/catalog/toProviderTools.js.map +0 -1
  136. package/lib/module/catalog/validateParams.js.map +0 -1
  137. package/lib/module/context/buildContextPack.js.map +0 -1
  138. package/lib/module/effects/digest.js.map +0 -1
  139. package/lib/module/effects/ledger.js.map +0 -1
  140. package/lib/module/engine/askGate.js.map +0 -1
  141. package/lib/module/engine/effectFor.js.map +0 -1
  142. package/lib/module/engine/evidence.js.map +0 -1
  143. package/lib/module/engine/historyBudget.js.map +0 -1
  144. package/lib/module/engine/retrieve.js.map +0 -1
  145. package/lib/module/engine/runAgentTurn.js.map +0 -1
  146. package/lib/module/engine/systemPrompt.js.map +0 -1
  147. package/lib/module/engine/textToolCalls.js.map +0 -1
  148. package/lib/module/engine/tokenCalibration.js.map +0 -1
  149. package/lib/module/engine/verify.js.map +0 -1
  150. package/lib/module/index.js.map +0 -1
  151. package/lib/module/policy/labels.js.map +0 -1
  152. package/lib/module/policy/policy.js.map +0 -1
  153. package/lib/module/policy/redact.js.map +0 -1
  154. package/lib/module/providers/anthropic.js.map +0 -1
  155. package/lib/module/providers/openai.js.map +0 -1
  156. package/lib/module/providers/problem.js.map +0 -1
  157. package/lib/module/providers/sse.js.map +0 -1
  158. package/lib/module/providers/streamTimer.js.map +0 -1
  159. package/lib/module/providers/transport.js.map +0 -1
  160. package/lib/module/providers/types.js.map +0 -1
  161. package/lib/module/providers/xhrStream.js.map +0 -1
  162. package/lib/module/session.js.map +0 -1
  163. package/lib/module/types.js.map +0 -1
  164. package/lib/typescript/blocks/receipts.d.ts.map +0 -1
  165. package/lib/typescript/blocks/runId.d.ts.map +0 -1
  166. package/lib/typescript/blocks/types.d.ts.map +0 -1
  167. package/lib/typescript/blocks/uiTool.d.ts.map +0 -1
  168. package/lib/typescript/catalog/catalog.g.d.ts.map +0 -1
  169. package/lib/typescript/catalog/catalog.types.g.d.ts.map +0 -1
  170. package/lib/typescript/catalog/normalizeParams.d.ts.map +0 -1
  171. package/lib/typescript/catalog/signature.d.ts.map +0 -1
  172. package/lib/typescript/catalog/snapshotReads.d.ts.map +0 -1
  173. package/lib/typescript/catalog/toProviderTools.d.ts.map +0 -1
  174. package/lib/typescript/catalog/validateParams.d.ts.map +0 -1
  175. package/lib/typescript/context/buildContextPack.d.ts.map +0 -1
  176. package/lib/typescript/effects/digest.d.ts.map +0 -1
  177. package/lib/typescript/effects/ledger.d.ts.map +0 -1
  178. package/lib/typescript/engine/askGate.d.ts.map +0 -1
  179. package/lib/typescript/engine/effectFor.d.ts.map +0 -1
  180. package/lib/typescript/engine/evidence.d.ts.map +0 -1
  181. package/lib/typescript/engine/historyBudget.d.ts.map +0 -1
  182. package/lib/typescript/engine/retrieve.d.ts.map +0 -1
  183. package/lib/typescript/engine/runAgentTurn.d.ts.map +0 -1
  184. package/lib/typescript/engine/systemPrompt.d.ts.map +0 -1
  185. package/lib/typescript/engine/textToolCalls.d.ts.map +0 -1
  186. package/lib/typescript/engine/tokenCalibration.d.ts.map +0 -1
  187. package/lib/typescript/engine/verify.d.ts.map +0 -1
  188. package/lib/typescript/index.d.ts.map +0 -1
  189. package/lib/typescript/policy/labels.d.ts.map +0 -1
  190. package/lib/typescript/policy/policy.d.ts.map +0 -1
  191. package/lib/typescript/policy/redact.d.ts.map +0 -1
  192. package/lib/typescript/providers/anthropic.d.ts.map +0 -1
  193. package/lib/typescript/providers/openai.d.ts.map +0 -1
  194. package/lib/typescript/providers/problem.d.ts.map +0 -1
  195. package/lib/typescript/providers/sse.d.ts.map +0 -1
  196. package/lib/typescript/providers/streamTimer.d.ts.map +0 -1
  197. package/lib/typescript/providers/transport.d.ts.map +0 -1
  198. package/lib/typescript/providers/types.d.ts.map +0 -1
  199. package/lib/typescript/providers/xhrStream.d.ts.map +0 -1
  200. package/lib/typescript/session.d.ts.map +0 -1
  201. package/lib/typescript/types.d.ts.map +0 -1
@@ -1,345 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.EffectLedger = void 0;
7
- exports.isReversible = isReversible;
8
- var _digest = require("./digest");
9
- /**
10
- * The effect ledger — what the agent changed, and how to put it back.
11
- *
12
- * Ask Buoy runs mutations immediately rather than asking first, so "undo" has
13
- * to be real. The model is Scenarios' `AppliedEffect` / `revertEffects`
14
- * (packages/scenarios/src/engine.ts:238-272), with two deliberate departures,
15
- * both fixing gaps that engine documents in itself:
16
- *
17
- * 1. STORAGE IS ACTUALLY REVERSIBLE HERE. Scenarios classifies storage writes
18
- * as unrecoverable because it never captured the prior value. An agent can:
19
- * it reads the key before it writes, so undo restores the old value exactly,
20
- * and removes the key when it did not exist before.
21
- * 2. THE COUNT TELLS THE TRUTH. Scenarios' banner counts transients as
22
- * "changes" and then silently no-ops on them at revert. Here reversible and
23
- * one-shot effects are counted separately and the banner says both, because
24
- * a user who taps Undo and sees "4 changes" become nothing changed learns
25
- * not to trust the banner.
26
- *
27
- * Undo runs in REVERSE order — a later write to the same key must be undone
28
- * before the earlier one, or the earlier value is the one that survives.
29
- */
30
-
31
- /**
32
- * Whether Undo can actually put this effect back. THE one place the answer
33
- * lives: the bar, the sync snapshot and `revertOne` all agree through it.
34
- * `state-write` is a wipe or a store write with no captured prior state —
35
- * the ledger records it so the user can see it happened, and it is
36
- * PERMANENT. Counting it as undoable made the bar promise "1 undoable" and
37
- * then fail with "can't be undone automatically" on tap.
38
- */
39
- function isReversible(effect) {
40
- return effect.kind !== "transient" && effect.kind !== "state-write";
41
- }
42
- let seq = 0;
43
- class EffectLedger {
44
- entries = [];
45
- listeners = new Set();
46
- gen = 0;
47
-
48
- /**
49
- * Which conversation this ledger is holding, bumped by `clear`.
50
- *
51
- * A turn captures it when it starts and hands it back on every `record`.
52
- * Nothing about a dispatch can be pulled back once it is in flight, so a
53
- * write that settles after the user has already tapped "New conversation"
54
- * still lands in the app — but it must not land in the NEW conversation's
55
- * undo bar, which is what happened: reset cleared the ledger, the old
56
- * write finished a moment later, and an empty chat came up owning a change
57
- * nobody in it had made. Recording it here would be worse than dropping it,
58
- * because Undo would then offer to reverse something the user cannot see.
59
- */
60
- get generation() {
61
- return this.gen;
62
- }
63
-
64
- /**
65
- * @param generation The value of `generation` when the turn began. Omit for
66
- * a human-initiated action, which is by definition happening now — and which
67
- * therefore always returns an entry.
68
- */
69
-
70
- record(toolId, action, effect, generation) {
71
- if (generation !== undefined && generation !== this.gen) return undefined;
72
- const entry = {
73
- id: `fx-${++seq}-${Date.now()}`,
74
- at: Date.now(),
75
- toolId,
76
- action,
77
- effect
78
- };
79
- // Copy-on-write: `list()` is a useSyncExternalStore snapshot, and React
80
- // bails out of the re-render when the snapshot is reference-equal. A
81
- // push would leave the bar showing a stale count until something else
82
- // happened to re-render it.
83
- this.entries = [...this.entries, entry];
84
- this.emit();
85
- return entry;
86
- }
87
- list() {
88
- return this.entries;
89
- }
90
-
91
- /** Tool-call ids of changes still applied — the rounds history budgeting keeps longest. */
92
- liveCallIds() {
93
- const out = new Set();
94
- for (const e of this.entries) if (!e.undoneAt && e.callId) out.add(e.callId);
95
- return out;
96
- }
97
-
98
- /**
99
- * Put back the entries of a conversation that outlived the app.
100
- *
101
- * The ledger is the ONLY undo surface, and it used to die with the JS
102
- * context: after a reload the transcript came back — receipt cards, "saved"
103
- * lines and all — while the changes bar quietly vanished and the changes
104
- * themselves stayed applied. "New conversation" warns before losing that
105
- * ("Clearing the conversation loses the only automatic undo for it"); a
106
- * reload took the same thing without asking.
107
- *
108
- * Refuses once anything has been recorded, for the same reason `hydrate`
109
- * refuses a live transcript: a late disk read must not shuffle old entries
110
- * in among changes made since.
111
- */
112
- restore(entries) {
113
- if (this.entries.length > 0 || entries.length === 0) return false;
114
- this.entries = [...entries];
115
- this.emit();
116
- return true;
117
- }
118
-
119
- /** New array identity for the snapshot after entries were mutated in place. */
120
- touch() {
121
- this.entries = [...this.entries];
122
- this.emit();
123
- }
124
-
125
- /** Not-yet-undone entries that Undo can actually deliver on. Agrees with
126
- * `isReversible` — it used to include state-writes, which revertOne refuses,
127
- * so the two disagreed about the one fact they both exist to answer. */
128
- reversible() {
129
- return this.entries.filter(e => !e.undoneAt && isReversible(e.effect));
130
- }
131
-
132
- /** Not-yet-undone one-shots. Counted separately so the banner is honest. */
133
- transients() {
134
- return this.entries.filter(e => !e.undoneAt && e.effect.kind === "transient");
135
- }
136
- isEmpty() {
137
- return this.entries.every(e => e.undoneAt !== undefined);
138
- }
139
- subscribe(fn) {
140
- this.listeners.add(fn);
141
- return () => this.listeners.delete(fn);
142
- }
143
- clear() {
144
- this.entries = [];
145
- // Anything a turn already in flight is about to record belongs to the
146
- // conversation being thrown away, not to the one replacing it.
147
- this.gen += 1;
148
- this.emit();
149
- }
150
-
151
- /**
152
- * Reconcile the ledger after a change was reverted OUTSIDE `revertOne` —
153
- * the model deleting the override it created, a block button's Delete, a
154
- * stopImpersonation call. Without this the create entry stays "undoable"
155
- * and Undo later fails against a rule that no longer exists, which reads
156
- * as the banner lying.
157
- *
158
- * Returns true when the call undid something, so the caller does not also
159
- * record the undo itself as a new change.
160
- */
161
- noteExternalRevert(toolId, action, params) {
162
- let changed = false;
163
- const mark = match => {
164
- for (const entry of this.entries) {
165
- if (!entry.undoneAt && match(entry)) {
166
- entry.undoneAt = Date.now();
167
- entry.undoError = undefined;
168
- changed = true;
169
- }
170
- }
171
- };
172
- if (toolId === "network" && action === "deleteOverrideRule") {
173
- mark(e => e.effect.kind === "network-override" && e.effect.ruleId === params.id);
174
- } else if (toolId === "network" && action === "clearOverrideRules") {
175
- mark(e => e.effect.kind === "network-override");
176
- } else if (toolId === "impersonate" && action === "stopImpersonation") {
177
- mark(e => e.effect.kind === "impersonation");
178
- } else {
179
- // Calling the inverse by hand — restoreLoading after triggerLoading,
180
- // switching a rule back — is the undo too.
181
- mark(e => {
182
- const fx = e.effect;
183
- if (fx.kind !== "inverse-call" || fx.toolId !== toolId || fx.action !== action) return false;
184
- return Object.entries(fx.params).every(([k, v]) => JSON.stringify(params[k] ?? (k === "enabled" ? false : undefined)) === JSON.stringify(v));
185
- });
186
- }
187
- if (changed) this.touch();
188
- return changed;
189
- }
190
- emit() {
191
- for (const fn of this.listeners) {
192
- try {
193
- fn();
194
- } catch {
195
- // A throwing listener must not take the ledger down.
196
- }
197
- }
198
- }
199
-
200
- /** Undo one entry. Returns an error string, or undefined on success. */
201
- async revertOne(entry, dispatch) {
202
- if (entry.undoneAt) return undefined;
203
- const fx = entry.effect;
204
- try {
205
- switch (fx.kind) {
206
- case "network-override":
207
- await dispatch("network", "deleteOverrideRule", {
208
- id: fx.ruleId
209
- });
210
- break;
211
- case "impersonation":
212
- await dispatch("impersonate", "stopImpersonation", {});
213
- break;
214
- case "storage-write":
215
- {
216
- // `instanceId` rides along for MMKV and is absent for AsyncStorage,
217
- // whose actions take the key alone.
218
- const scope = fx.instanceId ? {
219
- instanceId: fx.instanceId
220
- } : {};
221
-
222
- // Concurrent-writer guard: if the key no longer holds the value WE
223
- // wrote, someone else wrote after us and a blind restore would
224
- // destroy their newer value. Best-effort — an unreadable key still
225
- // restores, because a failed read must not strand the undo.
226
- if (fx.wrote !== undefined) {
227
- const readAction = fx.action === "mmkv.set" ? "mmkv.get" : "async.getItem";
228
- try {
229
- const current = await dispatch(fx.toolId, readAction, {
230
- ...scope,
231
- key: fx.key
232
- });
233
- const rec = current && typeof current === "object" ? current : undefined;
234
- const value = rec ? rec.value : current;
235
- if (rec?.found !== false && value !== undefined && value !== fx.wrote) {
236
- return "This value was changed again after Ask Buoy wrote it — left the newer value alone";
237
- }
238
- } catch {
239
- /* unreadable now; proceed with the restore */
240
- }
241
- }
242
- if (fx.before === undefined) {
243
- if (!fx.removeAction) return "No remove action known for this key";
244
- await dispatch(fx.toolId, fx.removeAction, {
245
- ...scope,
246
- key: fx.key
247
- });
248
- } else {
249
- await dispatch(fx.toolId, fx.action, {
250
- ...scope,
251
- key: fx.key,
252
- value: fx.before
253
- });
254
- }
255
- break;
256
- }
257
- case "query-write":
258
- {
259
- // Same concurrent-writer guard as storage, but the usual "other
260
- // writer" here is the app itself refetching — in which case the
261
- // server's data is already back and there is nothing to undo.
262
- if (fx.wroteDigest !== undefined) {
263
- try {
264
- const current = await dispatch("query", "getQueryData", {
265
- queryHash: fx.queryHash
266
- });
267
- if (current?.found === true) {
268
- const now = current.hasData === false ? undefined : (0, _digest.digestOf)(current.data);
269
- if (now !== fx.wroteDigest) {
270
- return "The app has refetched or changed this query since Ask Buoy wrote it — left the newer data alone";
271
- }
272
- }
273
- } catch {
274
- /* unreadable now; proceed with the restore */
275
- }
276
- }
277
- const outcome = await (fx.before === undefined ? dispatch("query", "invalidate", {
278
- queryHash: fx.queryHash
279
- }) : dispatch("query", "setQueryData", {
280
- queryHash: fx.queryHash,
281
- data: fx.before,
282
- // The whole prior value, verbatim: the drop-a-field guard is
283
- // for edits, and this is a restore of what the guard protects.
284
- force: true
285
- }));
286
- if (outcome && typeof outcome === "object" && outcome.ok === false) {
287
- return typeof outcome.error === "string" ? outcome.error : "The query could not be restored";
288
- }
289
- break;
290
- }
291
- case "inverse-call":
292
- {
293
- const outcome = await dispatch(fx.toolId, fx.action, fx.params);
294
- if (outcome && typeof outcome === "object" && outcome.ok === false) {
295
- return typeof outcome.error === "string" ? outcome.error : "The change could not be undone";
296
- }
297
- break;
298
- }
299
- case "state-write":
300
- return "This change can't be undone automatically";
301
- case "transient":
302
- return undefined;
303
- }
304
- entry.undoneAt = Date.now();
305
- entry.undoError = undefined;
306
- this.touch();
307
- return undefined;
308
- } catch (err) {
309
- const msg = err instanceof Error ? err.message : String(err);
310
- entry.undoError = msg;
311
- this.touch();
312
- return msg;
313
- }
314
- }
315
-
316
- /** Undo everything still outstanding, newest first. */
317
- async revertAll(dispatch) {
318
- const outcome = {
319
- reverted: 0,
320
- failed: [],
321
- skippedTransients: 0,
322
- permanent: 0
323
- };
324
- const pending = [...this.entries].filter(e => !e.undoneAt).reverse();
325
- for (const entry of pending) {
326
- if (entry.effect.kind === "transient") {
327
- outcome.skippedTransients += 1;
328
- continue;
329
- }
330
- if (!isReversible(entry.effect)) {
331
- outcome.permanent += 1;
332
- continue;
333
- }
334
- const error = await this.revertOne(entry, dispatch);
335
- if (error) outcome.failed.push({
336
- label: entry.effect.label,
337
- error
338
- });else outcome.reverted += 1;
339
- }
340
- this.emit();
341
- return outcome;
342
- }
343
- }
344
- exports.EffectLedger = EffectLedger;
345
- //# sourceMappingURL=ledger.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.EffectLedger=void 0;exports.isReversible=isReversible;var _digest=require("./digest");function isReversible(f){return f.kind!=="transient"&&f.kind!=="state-write"}let seq=0;class EffectLedger{entries=[];listeners=new Set;gen=0;get generation(){return this.gen}record(t,n,e,r){if(r!==void 0&&r!==this.gen)return void 0;const i={id:`fx-${++seq}-${Date.now()}`,at:Date.now(),toolId:t,action:n,effect:e};this.entries=[...this.entries,i];this.emit();return i}list(){return this.entries}liveCallIds(){const t=new Set;for(const n of this.entries)if(!n.undoneAt&&n.callId)t.add(n.callId);return t}restore(t){if(this.entries.length>0||t.length===0)return false;this.entries=[...t];this.emit();return true}touch(){this.entries=[...this.entries];this.emit()}reversible(){return this.entries.filter(t=>!t.undoneAt&&isReversible(t.effect))}transients(){return this.entries.filter(t=>!t.undoneAt&&t.effect.kind==="transient")}isEmpty(){return this.entries.every(t=>t.undoneAt!==void 0)}subscribe(t){this.listeners.add(t);return()=>this.listeners.delete(t)}clear(){this.entries=[];this.gen+=1;this.emit()}noteExternalRevert(t,n,e){let r=false;const i=s=>{for(const o of this.entries){if(!o.undoneAt&&s(o)){o.undoneAt=Date.now();o.undoError=void 0;r=true}}};if(t==="network"&&n==="deleteOverrideRule"){i(s=>s.effect.kind==="network-override"&&s.effect.ruleId===e.id)}else if(t==="network"&&n==="clearOverrideRules"){i(s=>s.effect.kind==="network-override")}else if(t==="impersonate"&&n==="stopImpersonation"){i(s=>s.effect.kind==="impersonation")}else{i(s=>{const o=s.effect;if(o.kind!=="inverse-call"||o.toolId!==t||o.action!==n)return false;return Object.entries(o.params).every(([a,u])=>JSON.stringify(e[a]??(a==="enabled"?false:void 0))===JSON.stringify(u))})}if(r)this.touch();return r}emit(){for(const t of this.listeners){try{t()}catch{}}}async revertOne(t,n){if(t.undoneAt)return void 0;const e=t.effect;try{switch(e.kind){case"network-override":await n("network","deleteOverrideRule",{id:e.ruleId});break;case"impersonation":await n("impersonate","stopImpersonation",{});break;case"storage-write":{const r=e.instanceId?{instanceId:e.instanceId}:{};if(e.wrote!==void 0){const i=e.action==="mmkv.set"?"mmkv.get":"async.getItem";try{const s=await n(e.toolId,i,{...r,key:e.key});const o=s&&typeof s==="object"?s:void 0;const a=o?o.value:s;if(o?.found!==false&&a!==void 0&&a!==e.wrote){return"This value was changed again after Ask Buoy wrote it \u2014 left the newer value alone"}}catch{}}if(e.before===void 0){if(!e.removeAction)return"No remove action known for this key";await n(e.toolId,e.removeAction,{...r,key:e.key})}else{await n(e.toolId,e.action,{...r,key:e.key,value:e.before})}break}case"query-write":{if(e.wroteDigest!==void 0){try{const i=await n("query","getQueryData",{queryHash:e.queryHash});if(i?.found===true){const s=i.hasData===false?void 0:(0,_digest.digestOf)(i.data);if(s!==e.wroteDigest){return"The app has refetched or changed this query since Ask Buoy wrote it \u2014 left the newer data alone"}}}catch{}}const r=await(e.before===void 0?n("query","invalidate",{queryHash:e.queryHash}):n("query","setQueryData",{queryHash:e.queryHash,data:e.before,force:true}));if(r&&typeof r==="object"&&r.ok===false){return typeof r.error==="string"?r.error:"The query could not be restored"}break}case"inverse-call":{const r=await n(e.toolId,e.action,e.params);if(r&&typeof r==="object"&&r.ok===false){return typeof r.error==="string"?r.error:"The change could not be undone"}break}case"state-write":return"This change can't be undone automatically";case"transient":return void 0}t.undoneAt=Date.now();t.undoError=void 0;this.touch();return void 0}catch(r){const i=r instanceof Error?r.message:String(r);t.undoError=i;this.touch();return i}}async revertAll(t){const n={reverted:0,failed:[],skippedTransients:0,permanent:0};const e=[...this.entries].filter(r=>!r.undoneAt).reverse();for(const r of e){if(r.effect.kind==="transient"){n.skippedTransients+=1;continue}if(!isReversible(r.effect)){n.permanent+=1;continue}const i=await this.revertOne(r,t);if(i)n.failed.push({label:r.effect.label,error:i});else n.reverted+=1}this.emit();return n}}exports.EffectLedger=EffectLedger;
@@ -1,287 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.ACQUISITION_CALLS = void 0;
7
- exports.synthesizeAskBlock = synthesizeAskBlock;
8
- exports.synthesizeShortfallBlock = synthesizeShortfallBlock;
9
- var _runId = require("../blocks/runId");
10
- /**
11
- * The last guarantee of a turn: when the turn ends WAITING ON THE USER, the
12
- * user gets something to tap.
13
- *
14
- * `buoy_ui` has had choice/confirm/input since the start and the prompt has
15
- * asked for them since the start, and models still end turns waiting in prose.
16
- * Two off a real device, a day apart:
17
- *
18
- * Want me to add an Eevee line to the bag?
19
- * If you want a Blastoise in there, tell me the level
20
- * (Lv. 5, Lv. 25 or Lv. 50) and how many, and I'll add it.
21
- *
22
- * The first is a question. The SECOND IS NOT — no question mark anywhere, the
23
- * ask is an imperative, and the model had already written the options out. A
24
- * gate that keys on "?" catches one of these, which is why this one keys on
25
- * the ask instead: an offer to act, or a request for a value.
26
- *
27
- * Deliberately narrow, in this order:
28
- *
29
- * - It reads only the LAST sentence of the turn. A question the model asked
30
- * and then answered itself is not waiting on anybody.
31
- * - Options the model ALREADY enumerated win. "(Lv. 5, Lv. 25 or Lv. 50)" is
32
- * the model doing the work and writing it in the wrong place.
33
- * - Then a clean either-or, split into its two halves.
34
- * - Then Yes/No, but only for an offer to ACT ("I can add it if you'd like").
35
- * A request for a value it could not enumerate — "tell me the level" —
36
- * produces NOTHING. Yes/No against "tell me the level" is worse than the
37
- * sentence it replaced, and the composer is already a text field.
38
- *
39
- * ENGLISH ONLY, AND DELIBERATELY FAIL-SAFE. Both patterns are English, tuned
40
- * to sentences observed in an English demo app, and there is no honest way to
41
- * make a regex multilingual. What saves this from being an overfit that ships
42
- * to everyone is the direction it fails in: no match returns null, the model's
43
- * own prose question stands, and the user answers it in the composer exactly
44
- * as they would have. A non-English app loses the extra card; it does not get
45
- * a wrong one. Do not "fix" that by loosening the patterns — a false positive
46
- * turns a finished answer into a spurious question, which is the one outcome
47
- * worse than no card at all.
48
- *
49
- * It emits a `choice`, never a `confirm`: choice carries the question in the
50
- * field `questionBehind` reads after a restore, renders in the neutral tone
51
- * rather than the warning one, and keeps the "or just type your answer"
52
- * escape. A synthesized card must never look like an approval.
53
- *
54
- * The block is authored AFTER the assistant message and carries no tool call,
55
- * so nothing enters the provider history: there is no `tool_use` to answer and
56
- * no `tool_result` to orphan. The user's tap becomes their next message
57
- * exactly as a model-authored block's would.
58
- */
59
-
60
- const nextId = (0, _runId.idFactory)("ask");
61
-
62
- /**
63
- * An offer to DO something, waiting on yes or no. Includes the trailing
64
- * conditional models close with — "I can add it **if you'd like**" — which is
65
- * the same offer with the question mark filed off.
66
- */
67
- const OFFER = /\b(?:want me to|would you like me to|would you like|do you want me to|do you want|should i|shall i|ok(?:ay)? if i|say the word|let me know if|if you'?d (?:like|prefer)|if you want)\b/i;
68
-
69
- /**
70
- * A request for a VALUE the engine cannot invent: "tell me the level", "just
71
- * say which one". Distinguished from "let me know IF you want…", which is an
72
- * offer, by what follows the verb.
73
- */
74
- const VALUE_ASK = /\b(?:tell me|let me know|just say|say|give me|pick|choose)\s+(?:the|which|what|how|your|one)\b/i;
75
- const ASK = new RegExp(`${OFFER.source}|${VALUE_ASK.source}`, "i");
76
-
77
- /** Markdown and quoting the model wrapped the sentence in. */
78
- const TRAILING_MARKUP = /[*_`"'’”\s]+$/;
79
- const LEADING_MARKUP = /^[*_`>\-\s]+/;
80
-
81
- /**
82
- * A sentence boundary that survives "Lv. 5".
83
- *
84
- * A terminator only ends a sentence when what follows starts one — a capital
85
- * or an opening quote. `.` before a digit or a lowercase word is an
86
- * abbreviation, and splitting there tore the Blastoise sentence into five.
87
- */
88
- const SENTENCE_END = /[.!?]["'’”)\]]*\s+(?=[A-Z("'“])|\n+/;
89
-
90
- /** "add it to the cart, or just show you the item" → two halves. */
91
- const OR_SPLIT = /,?\s+or\s+/i;
92
-
93
- /** A list the model already wrote out: "(Lv. 5, Lv. 25 or Lv. 50)". */
94
- const PARENTHETICAL = /\(([^()]{3,140})\)/;
95
- const LIST_SPLIT = /\s*,\s*|\s+or\s+/i;
96
-
97
- /**
98
- * A half that reads as a button. Clause punctuation means the split landed
99
- * inside a list ("a, b or c") rather than between two real alternatives.
100
- */
101
- const CLEAN_LABEL = /^[A-Za-z0-9][^,;:—–]*$/;
102
- /** Looser: an enumerated option was already a list item, so commas are gone. */
103
- const CLEAN_OPTION = /^[A-Za-z0-9$][^;:—–()]*$/;
104
- const YES_NO = [{
105
- id: "yes",
106
- label: "Yes, go ahead"
107
- }, {
108
- id: "no",
109
- label: "No thanks"
110
- }];
111
-
112
- /**
113
- * The block a turn should have ended with, or null to leave the prose alone.
114
- *
115
- * `text` is everything the model said this turn, rounds joined in order.
116
- */
117
- function synthesizeAskBlock(text) {
118
- const sentence = lastSentence(text);
119
- if (!sentence || sentence.length < 8 || sentence.length > 240) return null;
120
- const ask = ASK.exec(sentence);
121
- if (!ask) return null;
122
-
123
- // 1. The model already wrote the options; they were just in the wrong place.
124
- const listed = enumerated(sentence);
125
- if (listed) return {
126
- id: nextId(),
127
- kind: "choice",
128
- question: sentence,
129
- options: listed
130
- };
131
-
132
- // 2. Two real alternatives, after the verb that offered them.
133
- const after = sentence.slice(ask.index + ask[0].length).replace(/[?.!,]+$/, "").trim();
134
- const halves = after ? after.split(OR_SPLIT) : [];
135
- if (halves.length > 2) return null;
136
- if (halves.length === 2) {
137
- const alt = alternatives(halves);
138
- return alt ? {
139
- id: nextId(),
140
- kind: "choice",
141
- question: sentence,
142
- options: alt
143
- } : null;
144
- }
145
-
146
- // 3. Yes or no — but only for an offer to act. A value it could not
147
- // enumerate stays prose; the composer is already a text field.
148
- if (VALUE_ASK.test(sentence) || !OFFER.test(sentence)) return null;
149
- return {
150
- id: nextId(),
151
- kind: "choice",
152
- question: sentence,
153
- options: YES_NO
154
- };
155
- }
156
- function lastSentence(text) {
157
- const trimmed = text.replace(TRAILING_MARKUP, "").trim();
158
- if (!trimmed) return null;
159
- const parts = trimmed.split(SENTENCE_END);
160
- const last = parts[parts.length - 1]?.replace(LEADING_MARKUP, "").trim();
161
- return last || null;
162
- }
163
- function enumerated(sentence) {
164
- const inside = PARENTHETICAL.exec(sentence)?.[1];
165
- // `or` is what makes it a list of choices rather than an aside
166
- // ("(Lv25, quantity 6, $9.99 each)").
167
- if (!inside || !/\bor\b/i.test(inside)) return null;
168
- const labels = inside.split(LIST_SPLIT).map(p => p.trim()).filter(Boolean);
169
- if (labels.length < 2 || labels.length > 5) return null;
170
- for (const label of labels) {
171
- if (label.length < 1 || label.length > 48 || !CLEAN_OPTION.test(label)) return null;
172
- }
173
- return labels.map((label, i) => ({
174
- id: `o${i + 1}`,
175
- label: capitalize(label)
176
- }));
177
- }
178
- function alternatives(halves) {
179
- const labels = halves.map(h => h.trim().replace(/[?.!]+$/, "").trim());
180
- for (const label of labels) {
181
- if (label.length < 2 || label.length > 48 || !CLEAN_LABEL.test(label)) return null;
182
- }
183
- return labels.map((label, i) => ({
184
- id: i === 0 ? "a" : "b",
185
- label: capitalize(label)
186
- }));
187
- }
188
- function capitalize(label) {
189
- return /^[a-z]/.test(label) ? label[0].toUpperCase() + label.slice(1) : label;
190
- }
191
-
192
- /* ------------------------------------------------------------------------ *
193
- * The OTHER way a turn ends badly: it answers a smaller question than the
194
- * one it was asked, and hands the rest back to the user.
195
- *
196
- * Off a real device: "Show me all the pokemon from our poke mart app." The
197
- * app's home screen holds ten and fetches one per card as you swipe; the user
198
- * had swiped four. The agent read the cache, found four, showed four, and
199
- * closed with "that's everything currently in the app's cache, so if you visit
200
- * other Pokémon in the app I can show those too" — while holding
201
- * route-events.navigate, describeScreen and tapElement, and having called none
202
- * of them.
203
- *
204
- * `synthesizeAskBlock` above should have caught that last clause — "if you
205
- * visit … I can" is exactly its OFFER shape. It did not, because the turn had
206
- * already shipped a `suggestions` block, so `offeredTap` was true. The chips
207
- * were "Compare their HP", "Show me the bag", "Change Pikachu's stats". The
208
- * user had something to tap; none of it was the thing the agent had just
209
- * failed to do.
210
- *
211
- * So this gate asks a different question from that one. Not "does the user
212
- * have something to tap" but "can the user tap THE GAP" — and it fires even
213
- * when the turn offered chips, because that is the case it exists for.
214
- *
215
- * It stays quiet when the agent actually TRIED (see `attempted`). An agent
216
- * that navigated, tapped and re-read and still came up short has earned its
217
- * "that's all there is", and nagging it would be worse than silence.
218
- * ------------------------------------------------------------------------ */
219
-
220
- /**
221
- * A limit on what the answer covers: "that's all", "only", "so far".
222
- * Meaningless alone — half the sentences in English contain "only".
223
- */
224
- const LIMITED = /\b(?:that'?s (?:all|everything|it)|only|just|so far|nothing else|no others?|the rest (?:are|is)?n'?t)\b/i;
225
-
226
- /**
227
- * …AND a word that says the limit is about LOADING rather than about the
228
- * world. "Only four are cached" is a shortfall; "only four are in stock" is an
229
- * answer. Both halves have to be present.
230
- */
231
- const NOT_LOADED = /\b(?:cache[ds]?|cached|loaded|fetched|in memory|visited|opened|rendered|recorded|captured)\b/i;
232
-
233
- /**
234
- * The offer that hands the work back, which needs no second half: the user is
235
- * being asked to go and do the thing the agent could have done itself.
236
- */
237
- const GO_YOURSELF = /\b(?:if|once|when|after) you (?:visit|open|go to|navigate|browse|load|tap|swipe|scroll|look at)\b/i;
238
-
239
- /** How much of the turn to read. The concession is usually the closing line. */
240
- const TAIL_SENTENCES = 3;
241
-
242
- /**
243
- * `attempted` — did this turn call anything that GOES AND GETS data?
244
- * Keyed on the actions the prompt's ladder names, so the two move together.
245
- */
246
- const ACQUISITION_CALLS = exports.ACQUISITION_CALLS = new Set(["route-events.navigate", "route-events.stackGoBack", "route-events.stackNavigateToIndex", "highlight-updates.describeScreen", "highlight-updates.tapElement", "highlight-updates.waitFor", "query.refetch", "query.invalidate"]);
247
- /**
248
- * The block a turn should have ended with when it conceded a gap and never
249
- * tried to close it, or null to leave the prose alone.
250
- *
251
- * An `actions` block, not a `choice`: nothing is being ASKED here. The turn is
252
- * over and the answer stands; this is one button that says the gap is closable
253
- * and closes it. So it must not end the turn or set `awaiting-user` — a
254
- * synthesized card must never look like it is blocking on a reply.
255
- */
256
- function synthesizeShortfallBlock({
257
- text,
258
- attempted
259
- }) {
260
- if (attempted) return null;
261
- const tail = lastSentences(text, TAIL_SENTENCES);
262
- if (!tail) return null;
263
- const conceded = GO_YOURSELF.test(tail) || LIMITED.test(tail) && NOT_LOADED.test(tail);
264
- if (!conceded) return null;
265
- return {
266
- id: nextShortfallId(),
267
- kind: "actions",
268
- actions: [{
269
- label: "Go get the rest",
270
- primary: true,
271
- // Phrased as the user would phrase it, because that is what it becomes:
272
- // the tap is sent as their next message.
273
- send: "Don't stop at what's already loaded. Drive the app to get the rest — check the other layers, use the sitemap to navigate to the screen that loads it, wait for it, then answer in full. If it truly isn't reachable, tell me exactly what you tried."
274
- }]
275
- };
276
- }
277
- const nextShortfallId = (0, _runId.idFactory)("gap");
278
-
279
- /** The last `count` sentences, joined — see `lastSentence` for the split. */
280
- function lastSentences(text, count) {
281
- const trimmed = text.replace(TRAILING_MARKUP, "").trim();
282
- if (!trimmed) return null;
283
- const parts = trimmed.split(SENTENCE_END).filter(p => p.trim());
284
- const tail = parts.slice(-count).join(" ").replace(LEADING_MARKUP, "").trim();
285
- return tail || null;
286
- }
287
- //# sourceMappingURL=askGate.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.ACQUISITION_CALLS=void 0;exports.synthesizeAskBlock=synthesizeAskBlock;exports.synthesizeShortfallBlock=synthesizeShortfallBlock;var _runId=require("../blocks/runId");const nextId=(0,_runId.idFactory)("ask");const OFFER=/\b(?:want me to|would you like me to|would you like|do you want me to|do you want|should i|shall i|ok(?:ay)? if i|say the word|let me know if|if you'?d (?:like|prefer)|if you want)\b/i;const VALUE_ASK=/\b(?:tell me|let me know|just say|say|give me|pick|choose)\s+(?:the|which|what|how|your|one)\b/i;const ASK=new RegExp(`${OFFER.source}|${VALUE_ASK.source}`,"i");const TRAILING_MARKUP=/[*_`"'’”\s]+$/;const LEADING_MARKUP=/^[*_`>\-\s]+/;const SENTENCE_END=/[.!?]["'’”)\]]*\s+(?=[A-Z("'“])|\n+/;const OR_SPLIT=/,?\s+or\s+/i;const PARENTHETICAL=/\(([^()]{3,140})\)/;const LIST_SPLIT=/\s*,\s*|\s+or\s+/i;const CLEAN_LABEL=/^[A-Za-z0-9][^,;:—–]*$/;const CLEAN_OPTION=/^[A-Za-z0-9$][^;:—–()]*$/;const YES_NO=[{id:"yes",label:"Yes, go ahead"},{id:"no",label:"No thanks"}];function synthesizeAskBlock(o){const e=lastSentence(o);if(!e||e.length<8||e.length>240)return null;const t=ASK.exec(e);if(!t)return null;const n=enumerated(e);if(n)return{id:nextId(),kind:"choice",question:e,options:n};const s=e.slice(t.index+t[0].length).replace(/[?.!,]+$/,"").trim();const i=s?s.split(OR_SPLIT):[];if(i.length>2)return null;if(i.length===2){const r=alternatives(i);return r?{id:nextId(),kind:"choice",question:e,options:r}:null}if(VALUE_ASK.test(e)||!OFFER.test(e))return null;return{id:nextId(),kind:"choice",question:e,options:YES_NO}}function lastSentence(o){const e=o.replace(TRAILING_MARKUP,"").trim();if(!e)return null;const t=e.split(SENTENCE_END);const n=t[t.length-1]?.replace(LEADING_MARKUP,"").trim();return n||null}function enumerated(o){const e=PARENTHETICAL.exec(o)?.[1];if(!e||!/\bor\b/i.test(e))return null;const t=e.split(LIST_SPLIT).map(n=>n.trim()).filter(Boolean);if(t.length<2||t.length>5)return null;for(const n of t){if(n.length<1||n.length>48||!CLEAN_OPTION.test(n))return null}return t.map((n,s)=>({id:`o${s+1}`,label:capitalize(n)}))}function alternatives(o){const e=o.map(t=>t.trim().replace(/[?.!]+$/,"").trim());for(const t of e){if(t.length<2||t.length>48||!CLEAN_LABEL.test(t))return null}return e.map((t,n)=>({id:n===0?"a":"b",label:capitalize(t)}))}function capitalize(o){return/^[a-z]/.test(o)?o[0].toUpperCase()+o.slice(1):o}const LIMITED=/\b(?:that'?s (?:all|everything|it)|only|just|so far|nothing else|no others?|the rest (?:are|is)?n'?t)\b/i;const NOT_LOADED=/\b(?:cache[ds]?|cached|loaded|fetched|in memory|visited|opened|rendered|recorded|captured)\b/i;const GO_YOURSELF=/\b(?:if|once|when|after) you (?:visit|open|go to|navigate|browse|load|tap|swipe|scroll|look at)\b/i;const TAIL_SENTENCES=3;const ACQUISITION_CALLS=exports.ACQUISITION_CALLS=new Set(["route-events.navigate","route-events.stackGoBack","route-events.stackNavigateToIndex","highlight-updates.describeScreen","highlight-updates.tapElement","highlight-updates.waitFor","query.refetch","query.invalidate"]);function synthesizeShortfallBlock({text:o,attempted:e}){if(e)return null;const t=lastSentences(o,TAIL_SENTENCES);if(!t)return null;const n=GO_YOURSELF.test(t)||LIMITED.test(t)&&NOT_LOADED.test(t);if(!n)return null;return{id:nextShortfallId(),kind:"actions",actions:[{label:"Go get the rest",primary:true,send:"Don't stop at what's already loaded. Drive the app to get the rest \u2014 check the other layers, use the sitemap to navigate to the screen that loads it, wait for it, then answer in full. If it truly isn't reachable, tell me exactly what you tried."}]}}const nextShortfallId=(0,_runId.idFactory)("gap");function lastSentences(o,e){const t=o.replace(TRAILING_MARKUP,"").trim();if(!t)return null;const n=t.split(SENTENCE_END).filter(i=>i.trim());const s=n.slice(-e).join(" ").replace(LEADING_MARKUP,"").trim();return s||null}