@buoy-gg/agent-core 7.0.33
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/LICENSE +58 -0
- package/README.md +42 -0
- package/lib/commonjs/blocks/receipts.js +702 -0
- package/lib/commonjs/blocks/receipts.js.map +1 -0
- package/lib/commonjs/blocks/types.js +119 -0
- package/lib/commonjs/blocks/types.js.map +1 -0
- package/lib/commonjs/blocks/uiTool.js +994 -0
- package/lib/commonjs/blocks/uiTool.js.map +1 -0
- package/lib/commonjs/catalog/catalog.g.js +4044 -0
- package/lib/commonjs/catalog/catalog.g.js.map +1 -0
- package/lib/commonjs/catalog/catalog.source.json +4956 -0
- package/lib/commonjs/catalog/snapshotReads.js +256 -0
- package/lib/commonjs/catalog/snapshotReads.js.map +1 -0
- package/lib/commonjs/catalog/toProviderTools.js +107 -0
- package/lib/commonjs/catalog/toProviderTools.js.map +1 -0
- package/lib/commonjs/catalog/validateParams.js +129 -0
- package/lib/commonjs/catalog/validateParams.js.map +1 -0
- package/lib/commonjs/context/buildContextPack.js +141 -0
- package/lib/commonjs/context/buildContextPack.js.map +1 -0
- package/lib/commonjs/effects/ledger.js +229 -0
- package/lib/commonjs/effects/ledger.js.map +1 -0
- package/lib/commonjs/engine/effectFor.js +95 -0
- package/lib/commonjs/engine/effectFor.js.map +1 -0
- package/lib/commonjs/engine/runAgentTurn.js +667 -0
- package/lib/commonjs/engine/runAgentTurn.js.map +1 -0
- package/lib/commonjs/engine/systemPrompt.js +86 -0
- package/lib/commonjs/engine/systemPrompt.js.map +1 -0
- package/lib/commonjs/index.js +288 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/policy/labels.js +303 -0
- package/lib/commonjs/policy/labels.js.map +1 -0
- package/lib/commonjs/policy/policy.js +143 -0
- package/lib/commonjs/policy/policy.js.map +1 -0
- package/lib/commonjs/policy/redact.js +135 -0
- package/lib/commonjs/policy/redact.js.map +1 -0
- package/lib/commonjs/providers/anthropic.js +272 -0
- package/lib/commonjs/providers/anthropic.js.map +1 -0
- package/lib/commonjs/providers/openai.js +193 -0
- package/lib/commonjs/providers/openai.js.map +1 -0
- package/lib/commonjs/providers/sse.js +104 -0
- package/lib/commonjs/providers/sse.js.map +1 -0
- package/lib/commonjs/providers/types.js +6 -0
- package/lib/commonjs/providers/types.js.map +1 -0
- package/lib/commonjs/session.js +173 -0
- package/lib/commonjs/session.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/blocks/receipts.js +696 -0
- package/lib/module/blocks/receipts.js.map +1 -0
- package/lib/module/blocks/types.js +113 -0
- package/lib/module/blocks/types.js.map +1 -0
- package/lib/module/blocks/uiTool.js +987 -0
- package/lib/module/blocks/uiTool.js.map +1 -0
- package/lib/module/catalog/catalog.g.js +4040 -0
- package/lib/module/catalog/catalog.g.js.map +1 -0
- package/lib/module/catalog/catalog.source.json +4956 -0
- package/lib/module/catalog/snapshotReads.js +252 -0
- package/lib/module/catalog/snapshotReads.js.map +1 -0
- package/lib/module/catalog/toProviderTools.js +101 -0
- package/lib/module/catalog/toProviderTools.js.map +1 -0
- package/lib/module/catalog/validateParams.js +124 -0
- package/lib/module/catalog/validateParams.js.map +1 -0
- package/lib/module/context/buildContextPack.js +138 -0
- package/lib/module/context/buildContextPack.js.map +1 -0
- package/lib/module/effects/ledger.js +223 -0
- package/lib/module/effects/ledger.js.map +1 -0
- package/lib/module/engine/effectFor.js +91 -0
- package/lib/module/engine/effectFor.js.map +1 -0
- package/lib/module/engine/runAgentTurn.js +663 -0
- package/lib/module/engine/runAgentTurn.js.map +1 -0
- package/lib/module/engine/systemPrompt.js +82 -0
- package/lib/module/engine/systemPrompt.js.map +1 -0
- package/lib/module/index.js +30 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/policy/labels.js +296 -0
- package/lib/module/policy/labels.js.map +1 -0
- package/lib/module/policy/policy.js +137 -0
- package/lib/module/policy/policy.js.map +1 -0
- package/lib/module/policy/redact.js +128 -0
- package/lib/module/policy/redact.js.map +1 -0
- package/lib/module/providers/anthropic.js +268 -0
- package/lib/module/providers/anthropic.js.map +1 -0
- package/lib/module/providers/openai.js +189 -0
- package/lib/module/providers/openai.js.map +1 -0
- package/lib/module/providers/sse.js +99 -0
- package/lib/module/providers/sse.js.map +1 -0
- package/lib/module/providers/types.js +4 -0
- package/lib/module/providers/types.js.map +1 -0
- package/lib/module/session.js +168 -0
- package/lib/module/session.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/blocks/receipts.d.ts +32 -0
- package/lib/typescript/blocks/receipts.d.ts.map +1 -0
- package/lib/typescript/blocks/types.d.ts +576 -0
- package/lib/typescript/blocks/types.d.ts.map +1 -0
- package/lib/typescript/blocks/uiTool.d.ts +15 -0
- package/lib/typescript/blocks/uiTool.d.ts.map +1 -0
- package/lib/typescript/catalog/catalog.g.d.ts +11 -0
- package/lib/typescript/catalog/catalog.g.d.ts.map +1 -0
- package/lib/typescript/catalog/snapshotReads.d.ts +37 -0
- package/lib/typescript/catalog/snapshotReads.d.ts.map +1 -0
- package/lib/typescript/catalog/toProviderTools.d.ts +52 -0
- package/lib/typescript/catalog/toProviderTools.d.ts.map +1 -0
- package/lib/typescript/catalog/validateParams.d.ts +33 -0
- package/lib/typescript/catalog/validateParams.d.ts.map +1 -0
- package/lib/typescript/context/buildContextPack.d.ts +32 -0
- package/lib/typescript/context/buildContextPack.d.ts.map +1 -0
- package/lib/typescript/effects/ledger.d.ts +119 -0
- package/lib/typescript/effects/ledger.d.ts.map +1 -0
- package/lib/typescript/engine/effectFor.d.ts +21 -0
- package/lib/typescript/engine/effectFor.d.ts.map +1 -0
- package/lib/typescript/engine/runAgentTurn.d.ts +140 -0
- package/lib/typescript/engine/runAgentTurn.d.ts.map +1 -0
- package/lib/typescript/engine/systemPrompt.d.ts +49 -0
- package/lib/typescript/engine/systemPrompt.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +29 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/policy/labels.d.ts +10 -0
- package/lib/typescript/policy/labels.d.ts.map +1 -0
- package/lib/typescript/policy/policy.d.ts +106 -0
- package/lib/typescript/policy/policy.d.ts.map +1 -0
- package/lib/typescript/policy/redact.d.ts +37 -0
- package/lib/typescript/policy/redact.d.ts.map +1 -0
- package/lib/typescript/providers/anthropic.d.ts +22 -0
- package/lib/typescript/providers/anthropic.d.ts.map +1 -0
- package/lib/typescript/providers/openai.d.ts +19 -0
- package/lib/typescript/providers/openai.d.ts.map +1 -0
- package/lib/typescript/providers/sse.d.ts +18 -0
- package/lib/typescript/providers/sse.d.ts.map +1 -0
- package/lib/typescript/providers/types.d.ts +129 -0
- package/lib/typescript/providers/types.d.ts.map +1 -0
- package/lib/typescript/session.d.ts +58 -0
- package/lib/typescript/session.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +119 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Receipts — blocks the engine builds from tool results, deterministically.
|
|
5
|
+
*
|
|
6
|
+
* The model never authors these, so they cannot hallucinate, and a weak model
|
|
7
|
+
* cannot fail to produce them. They render the SAME redacted result the model
|
|
8
|
+
* gets, so what the user sees and what the model reasons about are one thing.
|
|
9
|
+
*
|
|
10
|
+
* Every projector is small and honest about caps: it truncates and records
|
|
11
|
+
* `total` so the renderer can say "+38 more" truthfully.
|
|
12
|
+
*/
|
|
13
|
+
import { CAPS } from "./types";
|
|
14
|
+
let seq = 0;
|
|
15
|
+
const id = () => `rc${++seq}`;
|
|
16
|
+
const rec = v => v && typeof v === "object" && !Array.isArray(v) ? v : undefined;
|
|
17
|
+
const arr = v => Array.isArray(v) ? v : [];
|
|
18
|
+
const str = v => typeof v === "string" ? v : undefined;
|
|
19
|
+
const num = v => typeof v === "number" ? v : undefined;
|
|
20
|
+
function pathOf(url) {
|
|
21
|
+
try {
|
|
22
|
+
const u = new URL(url);
|
|
23
|
+
return u.pathname + (u.search.length > 1 ? "?…" : "");
|
|
24
|
+
} catch {
|
|
25
|
+
return url;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function requestSummary(r) {
|
|
29
|
+
const rid = str(r.id);
|
|
30
|
+
const url = str(r.url);
|
|
31
|
+
if (!rid || !url) return undefined;
|
|
32
|
+
return {
|
|
33
|
+
id: rid,
|
|
34
|
+
method: str(r.method) ?? "GET",
|
|
35
|
+
url,
|
|
36
|
+
status: num(r.status),
|
|
37
|
+
durationMs: num(r.durationMs),
|
|
38
|
+
// Both spellings: the snapshot projection says `overridden`, a raw event
|
|
39
|
+
// carries the `override` mark object.
|
|
40
|
+
mocked: r.overridden === true || r.override != null && typeof r.override === "object" || undefined
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Shallow diff of two plain objects: top-level keys that differ. */
|
|
45
|
+
export function shallowDiff(before, after) {
|
|
46
|
+
const a = rec(before) ?? {};
|
|
47
|
+
const b = rec(after) ?? {};
|
|
48
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const k of keys) {
|
|
51
|
+
if (typeof a[k] === "function" || typeof b[k] === "function") continue;
|
|
52
|
+
if (JSON.stringify(a[k]) !== JSON.stringify(b[k])) {
|
|
53
|
+
out.push({
|
|
54
|
+
path: k,
|
|
55
|
+
before: a[k],
|
|
56
|
+
after: b[k]
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Group console entries by message, newest first, counting repeats. */
|
|
64
|
+
function groupLogs(entries) {
|
|
65
|
+
const map = new Map();
|
|
66
|
+
for (const e of entries) {
|
|
67
|
+
const r = rec(e);
|
|
68
|
+
if (!r) continue;
|
|
69
|
+
const message = str(r.message) ?? "";
|
|
70
|
+
const level = str(r.level) ?? "log";
|
|
71
|
+
const key = `${level}:${message}`;
|
|
72
|
+
const cur = map.get(key);
|
|
73
|
+
if (cur) cur.count += 1;else map.set(key, {
|
|
74
|
+
level,
|
|
75
|
+
message,
|
|
76
|
+
count: 1
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return [...map.values()];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A bag-line / product heuristic: an array of objects with a name and a price/qty/image. */
|
|
83
|
+
function looksLikeItems(v) {
|
|
84
|
+
const items = arr(v).map(rec).filter(Boolean);
|
|
85
|
+
if (items.length < 2) return undefined;
|
|
86
|
+
const ok = items.every(i => str(i.name) && (i.image !== undefined || i.qty !== undefined || i.price !== undefined || i.unitPrice !== undefined));
|
|
87
|
+
return ok ? items : undefined;
|
|
88
|
+
}
|
|
89
|
+
function money(cents) {
|
|
90
|
+
const n = num(cents);
|
|
91
|
+
return n === undefined ? undefined : `$${(n / 100).toFixed(2)}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The receipt for one successful call, or nothing. Adding a projector is the
|
|
95
|
+
* whole job of making a tool's result visible — no UI change needed.
|
|
96
|
+
*/
|
|
97
|
+
export function projectReceipt(input) {
|
|
98
|
+
const {
|
|
99
|
+
toolId,
|
|
100
|
+
action,
|
|
101
|
+
params,
|
|
102
|
+
result
|
|
103
|
+
} = input;
|
|
104
|
+
const r = rec(result);
|
|
105
|
+
const source = input.effect === "read" ? "live" : "changed";
|
|
106
|
+
|
|
107
|
+
// ---- network --------------------------------------------------------
|
|
108
|
+
if (toolId === "network" && action === "getSnapshot" && r) {
|
|
109
|
+
const requests = arr(r.requests).map(rec).filter(Boolean).map(x => requestSummary(x)).filter(Boolean);
|
|
110
|
+
if (requests.length === 0) return undefined;
|
|
111
|
+
return {
|
|
112
|
+
id: id(),
|
|
113
|
+
kind: "requestList",
|
|
114
|
+
requests: requests.slice(0, CAPS.listItems * 2),
|
|
115
|
+
total: num(r.totalCaptured) ?? requests.length,
|
|
116
|
+
source
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (toolId === "network" && action === "getEventBody" && r) {
|
|
120
|
+
const reqId = str(params.id) ?? "";
|
|
121
|
+
const wasMocked = r.override != null && typeof r.override === "object";
|
|
122
|
+
return {
|
|
123
|
+
id: id(),
|
|
124
|
+
kind: "requestCard",
|
|
125
|
+
request: {
|
|
126
|
+
id: reqId,
|
|
127
|
+
method: str(r.method) ?? "",
|
|
128
|
+
url: str(r.url) ?? reqId,
|
|
129
|
+
status: num(r.status),
|
|
130
|
+
mocked: wasMocked || undefined
|
|
131
|
+
},
|
|
132
|
+
requestBody: r.requestData ?? undefined,
|
|
133
|
+
responseBody: r.responseData ?? undefined,
|
|
134
|
+
// A body an override rule synthesized must never render as "live".
|
|
135
|
+
source: wasMocked ? "mocked" : source
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (toolId === "network" && (action === "upsertOverrideRule" || action === "getOverrideRuleBody") && r) {
|
|
139
|
+
const rule = rec(r.rule) ?? r;
|
|
140
|
+
const ruleId = str(rule.id);
|
|
141
|
+
const pattern = str(rule.urlPattern);
|
|
142
|
+
if (!ruleId || !pattern) return undefined;
|
|
143
|
+
const kind = str(rule.kind) ?? "respond";
|
|
144
|
+
const asked = rec(params.rule) ?? {};
|
|
145
|
+
return {
|
|
146
|
+
id: id(),
|
|
147
|
+
kind: "ruleCard",
|
|
148
|
+
rule: {
|
|
149
|
+
id: ruleId,
|
|
150
|
+
name: str(rule.name) ?? str(asked.name),
|
|
151
|
+
urlPattern: pattern,
|
|
152
|
+
methods: arr(rule.methods ?? asked.methods).map(String),
|
|
153
|
+
kind,
|
|
154
|
+
status: num(rule.status) ?? num(asked.status),
|
|
155
|
+
delayMs: num(rule.delayMs) ?? num(asked.delayMs),
|
|
156
|
+
times: num(rule.times) ?? num(asked.times),
|
|
157
|
+
enabled: rule.enabled !== false,
|
|
158
|
+
hits: num(rule.hits)
|
|
159
|
+
},
|
|
160
|
+
source: action === "upsertOverrideRule" ? "changed" : "live"
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (toolId === "network" && action === "listOverrideRules" && r) {
|
|
164
|
+
const rules = arr(r.rules).map(rec).filter(Boolean);
|
|
165
|
+
if (rules.length === 0) return undefined;
|
|
166
|
+
return {
|
|
167
|
+
id: id(),
|
|
168
|
+
kind: "list",
|
|
169
|
+
title: `${rules.length} network override${rules.length === 1 ? "" : "s"}`,
|
|
170
|
+
items: rules.slice(0, CAPS.listItems).map(x => ({
|
|
171
|
+
id: str(x.id),
|
|
172
|
+
title: str(x.name) ?? str(x.urlPattern) ?? "rule",
|
|
173
|
+
subtitle: `${arr(x.methods).join("/") || "ANY"} ${str(x.urlPattern) ?? ""} → ${str(x.kind) ?? ""}${x.status ? ` ${String(x.status)}` : ""}`,
|
|
174
|
+
trailing: x.enabled === false ? "off" : `on · ${num(x.hits) ?? 0} hit${num(x.hits) === 1 ? "" : "s"}`,
|
|
175
|
+
tone: x.enabled === false ? "muted" : "warning"
|
|
176
|
+
})),
|
|
177
|
+
total: rules.length,
|
|
178
|
+
source: "live"
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---- routes ---------------------------------------------------------
|
|
183
|
+
if (toolId === "route-events" && action === "getSnapshot" && r) {
|
|
184
|
+
const routes = [...new Set(arr(r.routes).map(String))];
|
|
185
|
+
if (routes.length === 0) return undefined;
|
|
186
|
+
const current = rec(r.currentRoute);
|
|
187
|
+
return {
|
|
188
|
+
id: id(),
|
|
189
|
+
kind: "routeChips",
|
|
190
|
+
routes: routes.map(p => ({
|
|
191
|
+
path: p,
|
|
192
|
+
dynamic: /\[.+\]/.test(p)
|
|
193
|
+
})),
|
|
194
|
+
current: str(current?.path),
|
|
195
|
+
total: routes.length,
|
|
196
|
+
source: "live"
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (toolId === "route-events" && action === "navigate" && r) {
|
|
200
|
+
const path = str(r.navigated) ?? str(params.path);
|
|
201
|
+
return path ? {
|
|
202
|
+
id: id(),
|
|
203
|
+
kind: "peek",
|
|
204
|
+
label: `Opened ${path}`,
|
|
205
|
+
path
|
|
206
|
+
} : undefined;
|
|
207
|
+
}
|
|
208
|
+
if (toolId === "route-events" && (action === "stackGoBack" || action === "stackPopToTop")) {
|
|
209
|
+
return {
|
|
210
|
+
id: id(),
|
|
211
|
+
kind: "peek",
|
|
212
|
+
label: action === "stackPopToTop" ? "Went back to the first screen" : "Went back"
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---- screen ---------------------------------------------------------
|
|
217
|
+
if (toolId === "highlight-updates" && action === "describeScreen" && r) {
|
|
218
|
+
const els = arr(r.elements).map(rec).filter(Boolean);
|
|
219
|
+
const interactive = els.filter(e => e.interactive === true && !String(str(e.testID) ?? "").startsWith("buoy-"));
|
|
220
|
+
if (interactive.length === 0) return undefined;
|
|
221
|
+
return {
|
|
222
|
+
id: id(),
|
|
223
|
+
kind: "screenElements",
|
|
224
|
+
elements: interactive.slice(0, CAPS.chips).map(e => ({
|
|
225
|
+
nativeTag: num(e.nativeTag) ?? 0,
|
|
226
|
+
name: str(e.name) ?? "",
|
|
227
|
+
label: str(e.label) ?? str(e.text),
|
|
228
|
+
testID: str(e.testID),
|
|
229
|
+
interactive: true
|
|
230
|
+
})),
|
|
231
|
+
total: interactive.length,
|
|
232
|
+
source: "live"
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (toolId === "highlight-updates" && action === "tapElement" && r?.tapped === true) {
|
|
236
|
+
const m = rec(r.matched);
|
|
237
|
+
const what = str(m?.label) ?? str(m?.text) ?? str(m?.testID) ?? "element";
|
|
238
|
+
return {
|
|
239
|
+
id: id(),
|
|
240
|
+
kind: "peek",
|
|
241
|
+
label: `Tapped ${what}`
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---- console --------------------------------------------------------
|
|
246
|
+
if (toolId === "console" && action === "getSnapshot" && r) {
|
|
247
|
+
const entries = arr(r.entries);
|
|
248
|
+
if (entries.length === 0) return undefined;
|
|
249
|
+
const grouped = groupLogs(entries);
|
|
250
|
+
return {
|
|
251
|
+
id: id(),
|
|
252
|
+
kind: "logList",
|
|
253
|
+
entries: grouped.slice(0, CAPS.listItems * 2),
|
|
254
|
+
total: num(r.totalCaptured) ?? entries.length,
|
|
255
|
+
source: "live"
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ---- state stores ---------------------------------------------------
|
|
260
|
+
if (toolId === "zustand" && action === "getStoreState" && r?.found !== false) {
|
|
261
|
+
const state = rec(r?.currentState);
|
|
262
|
+
if (!state) return undefined;
|
|
263
|
+
const items = looksLikeItems(state.lines ?? state.items);
|
|
264
|
+
if (items) {
|
|
265
|
+
return {
|
|
266
|
+
id: id(),
|
|
267
|
+
kind: "list",
|
|
268
|
+
title: str(params.storeName),
|
|
269
|
+
items: items.slice(0, CAPS.listItems).map(i => ({
|
|
270
|
+
id: str(i.lineId) ?? str(i.id),
|
|
271
|
+
title: str(i.name) ?? "",
|
|
272
|
+
subtitle: [str(i.sizeName), i.qty !== undefined ? `×${String(i.qty)}` : undefined].filter(Boolean).join(" · "),
|
|
273
|
+
trailing: money(i.unitPrice ?? i.price),
|
|
274
|
+
image: str(i.image)
|
|
275
|
+
})),
|
|
276
|
+
total: items.length,
|
|
277
|
+
source: "live"
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const pairs = Object.entries(state).filter(([, v]) => typeof v !== "function").slice(0, CAPS.keyValuePairs).map(([key, value]) => ({
|
|
281
|
+
key,
|
|
282
|
+
value
|
|
283
|
+
}));
|
|
284
|
+
return {
|
|
285
|
+
id: id(),
|
|
286
|
+
kind: "keyValue",
|
|
287
|
+
title: str(params.storeName),
|
|
288
|
+
pairs,
|
|
289
|
+
source: "live"
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (toolId === "zustand" && action === "setState" && r?.ok !== false) {
|
|
293
|
+
const changes = shallowDiff(rec(input.before)?.currentState ?? input.before, params.state);
|
|
294
|
+
if (changes.length === 0) return undefined;
|
|
295
|
+
return {
|
|
296
|
+
id: id(),
|
|
297
|
+
kind: "diff",
|
|
298
|
+
title: `${str(params.storeName) ?? "store"} — what changed`,
|
|
299
|
+
changes,
|
|
300
|
+
source: "changed"
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
if (toolId === "zustand" && action === "listStores" && r) {
|
|
304
|
+
const stores = arr(r.stores).map(rec).filter(Boolean);
|
|
305
|
+
if (stores.length === 0) return undefined;
|
|
306
|
+
return {
|
|
307
|
+
id: id(),
|
|
308
|
+
kind: "list",
|
|
309
|
+
title: `${stores.length} store${stores.length === 1 ? "" : "s"}`,
|
|
310
|
+
items: stores.slice(0, CAPS.listItems * 2).map(s => ({
|
|
311
|
+
id: str(s.name),
|
|
312
|
+
title: str(s.name) ?? "",
|
|
313
|
+
subtitle: arr(s.keys).slice(0, 6).join(", "),
|
|
314
|
+
trailing: s.isPersisted ? "persisted" : undefined
|
|
315
|
+
})),
|
|
316
|
+
total: stores.length,
|
|
317
|
+
source: "live"
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
if (toolId === "redux" && action === "getState" && r?.available !== false) {
|
|
321
|
+
const state = rec(r?.state) ?? r;
|
|
322
|
+
if (!state) return undefined;
|
|
323
|
+
const pairs = Object.entries(state).slice(0, CAPS.keyValuePairs).map(([key, value]) => ({
|
|
324
|
+
key,
|
|
325
|
+
value
|
|
326
|
+
}));
|
|
327
|
+
return {
|
|
328
|
+
id: id(),
|
|
329
|
+
kind: "keyValue",
|
|
330
|
+
title: "Redux state",
|
|
331
|
+
pairs,
|
|
332
|
+
source: "live"
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ---- storage --------------------------------------------------------
|
|
337
|
+
if (toolId === "storage" && (action === "async.getItem" || action === "mmkv.get" || action === "secure.get")) {
|
|
338
|
+
const key = str(params.key) ?? "";
|
|
339
|
+
let value = r && "value" in r ? r.value : result;
|
|
340
|
+
if (r?.found === false || value === null || value === undefined) return undefined;
|
|
341
|
+
if (typeof value === "string") {
|
|
342
|
+
try {
|
|
343
|
+
value = JSON.parse(value);
|
|
344
|
+
} catch {
|
|
345
|
+
/* plain string */
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const obj = rec(value);
|
|
349
|
+
const items = looksLikeItems(rec(obj?.state)?.lines ?? obj?.lines ?? obj?.items);
|
|
350
|
+
if (items) {
|
|
351
|
+
return {
|
|
352
|
+
id: id(),
|
|
353
|
+
kind: "list",
|
|
354
|
+
title: key,
|
|
355
|
+
items: items.slice(0, CAPS.listItems).map(i => ({
|
|
356
|
+
id: str(i.lineId) ?? str(i.id),
|
|
357
|
+
title: str(i.name) ?? "",
|
|
358
|
+
subtitle: [str(i.sizeName), i.qty !== undefined ? `×${String(i.qty)}` : undefined].filter(Boolean).join(" · "),
|
|
359
|
+
trailing: money(i.unitPrice ?? i.price),
|
|
360
|
+
image: str(i.image)
|
|
361
|
+
})),
|
|
362
|
+
total: items.length,
|
|
363
|
+
source: "live"
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
if (obj) {
|
|
367
|
+
const pairs = Object.entries(obj).slice(0, CAPS.keyValuePairs).map(([k, v]) => ({
|
|
368
|
+
key: k,
|
|
369
|
+
value: v
|
|
370
|
+
}));
|
|
371
|
+
return {
|
|
372
|
+
id: id(),
|
|
373
|
+
kind: "keyValue",
|
|
374
|
+
title: key,
|
|
375
|
+
pairs,
|
|
376
|
+
source: "live"
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
id: id(),
|
|
381
|
+
kind: "keyValue",
|
|
382
|
+
title: key,
|
|
383
|
+
pairs: [{
|
|
384
|
+
key: "value",
|
|
385
|
+
value
|
|
386
|
+
}],
|
|
387
|
+
source: "live"
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (toolId === "storage" && action === "async.getAllKeys") {
|
|
391
|
+
const keys = arr(result).map(String);
|
|
392
|
+
if (keys.length === 0) return undefined;
|
|
393
|
+
return {
|
|
394
|
+
id: id(),
|
|
395
|
+
kind: "list",
|
|
396
|
+
title: `${keys.length} stored key${keys.length === 1 ? "" : "s"}`,
|
|
397
|
+
items: keys.slice(0, CAPS.listItems * 2).map(k => ({
|
|
398
|
+
id: k,
|
|
399
|
+
title: k,
|
|
400
|
+
action: {
|
|
401
|
+
label: "Read",
|
|
402
|
+
dispatch: {
|
|
403
|
+
toolId: "storage",
|
|
404
|
+
action: "async.getItem",
|
|
405
|
+
params: {
|
|
406
|
+
key: k
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
})),
|
|
411
|
+
total: keys.length,
|
|
412
|
+
source: "live"
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (toolId === "storage" && (action === "async.setItem" || action === "mmkv.set" || action === "secure.set")) {
|
|
416
|
+
const key = str(params.key) ?? "";
|
|
417
|
+
const b = rec(input.before);
|
|
418
|
+
const beforeVal = b?.had ? b.value : undefined;
|
|
419
|
+
return {
|
|
420
|
+
id: id(),
|
|
421
|
+
kind: "diff",
|
|
422
|
+
title: `${key} — saved`,
|
|
423
|
+
changes: [{
|
|
424
|
+
path: key,
|
|
425
|
+
before: beforeVal,
|
|
426
|
+
after: params.value
|
|
427
|
+
}],
|
|
428
|
+
source: "changed"
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ---- env ------------------------------------------------------------
|
|
433
|
+
if (toolId === "env" && action === "getSnapshot" && r) {
|
|
434
|
+
const env = rec(r.env);
|
|
435
|
+
if (!env || Object.keys(env).length === 0) return undefined;
|
|
436
|
+
const pairs = Object.entries(env).slice(0, CAPS.keyValuePairs).map(([key, value]) => ({
|
|
437
|
+
key,
|
|
438
|
+
value
|
|
439
|
+
}));
|
|
440
|
+
return {
|
|
441
|
+
id: id(),
|
|
442
|
+
kind: "keyValue",
|
|
443
|
+
title: `Environment — ${Object.keys(env).length} var${Object.keys(env).length === 1 ? "" : "s"}`,
|
|
444
|
+
pairs,
|
|
445
|
+
source: "live"
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ---- sentry ---------------------------------------------------------
|
|
450
|
+
if (toolId === "sentry" && action === "getSnapshot" && r) {
|
|
451
|
+
const envelopes = arr(r.envelopes).map(rec).filter(Boolean);
|
|
452
|
+
const status = str(r.status);
|
|
453
|
+
if (envelopes.length === 0) {
|
|
454
|
+
// The status IS the answer when nothing was captured: "no client" and
|
|
455
|
+
// "nothing errored" are different findings and must not look alike.
|
|
456
|
+
if (status === "sdk-not-found" || status === "no-client") {
|
|
457
|
+
return {
|
|
458
|
+
id: id(),
|
|
459
|
+
kind: "notice",
|
|
460
|
+
tone: "warning",
|
|
461
|
+
text: "This app has no live Sentry client, so nothing can ever be captured here — that's different from \"nothing was sent\"."
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
id: id(),
|
|
468
|
+
kind: "list",
|
|
469
|
+
title: `${num(r.totalCaptured) ?? envelopes.length} envelope${(num(r.totalCaptured) ?? envelopes.length) === 1 ? "" : "s"} sent to Sentry`,
|
|
470
|
+
items: envelopes.slice(0, CAPS.listItems * 2).map(e => {
|
|
471
|
+
const items = arr(e.items).map(rec).filter(Boolean);
|
|
472
|
+
const first = items[0];
|
|
473
|
+
const isError = items.some(i => i.type === "event");
|
|
474
|
+
return {
|
|
475
|
+
id: e.id !== undefined ? String(e.id) : undefined,
|
|
476
|
+
title: str(first?.summary) ?? str(first?.type) ?? "envelope",
|
|
477
|
+
subtitle: items.map(i => str(i.type)).filter(Boolean).join(" · "),
|
|
478
|
+
trailing: str(e.origin),
|
|
479
|
+
tone: isError ? "danger" : undefined
|
|
480
|
+
};
|
|
481
|
+
}),
|
|
482
|
+
total: num(r.totalCaptured) ?? envelopes.length,
|
|
483
|
+
source: "live"
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ---- impersonate ----------------------------------------------------
|
|
488
|
+
if (toolId === "impersonate" && action === "searchUsers") {
|
|
489
|
+
const users = arr(result).map(rec).filter(Boolean);
|
|
490
|
+
if (users.length === 0) return undefined;
|
|
491
|
+
return {
|
|
492
|
+
id: id(),
|
|
493
|
+
kind: "list",
|
|
494
|
+
title: `${users.length} user${users.length === 1 ? "" : "s"} found`,
|
|
495
|
+
items: users.slice(0, CAPS.listItems).map(u => ({
|
|
496
|
+
id: str(u.id),
|
|
497
|
+
title: str(u.displayName) ?? str(u.email) ?? str(u.id) ?? "user",
|
|
498
|
+
subtitle: [str(u.email), str(rec(u.metadata)?.role)].filter(Boolean).join(" · "),
|
|
499
|
+
image: str(u.avatarUrl),
|
|
500
|
+
// The tap goes through runGatedAction, so it carries the same policy,
|
|
501
|
+
// approval and ledger path a model call would.
|
|
502
|
+
action: {
|
|
503
|
+
label: "Impersonate",
|
|
504
|
+
dispatch: {
|
|
505
|
+
toolId: "impersonate",
|
|
506
|
+
action: "startImpersonation",
|
|
507
|
+
params: {
|
|
508
|
+
user: u
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
})),
|
|
513
|
+
total: users.length,
|
|
514
|
+
source: "live"
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (toolId === "impersonate" && action === "startImpersonation" && r?.ok !== false) {
|
|
518
|
+
const u = rec(params.user);
|
|
519
|
+
const who = str(u?.displayName) ?? str(u?.email) ?? str(u?.id) ?? "that user";
|
|
520
|
+
return {
|
|
521
|
+
id: id(),
|
|
522
|
+
kind: "notice",
|
|
523
|
+
tone: "warning",
|
|
524
|
+
text: `Now viewing the app as ${who}. Cached data was cleared so everything refetches as them — stopping clears caches again.`
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
if (toolId === "impersonate" && action === "stopImpersonation") {
|
|
528
|
+
return {
|
|
529
|
+
id: id(),
|
|
530
|
+
kind: "notice",
|
|
531
|
+
tone: "info",
|
|
532
|
+
text: "Stopped impersonating — back to the real account. Cached app data was cleared again."
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ---- scenarios ------------------------------------------------------
|
|
537
|
+
if (toolId === "scenarios" && action === "listScenarios" && r) {
|
|
538
|
+
const all = [...arr(r.code), ...arr(r.device), ...arr(r.drafts)].map(rec).filter(Boolean);
|
|
539
|
+
if (all.length === 0) return undefined;
|
|
540
|
+
return {
|
|
541
|
+
id: id(),
|
|
542
|
+
kind: "list",
|
|
543
|
+
title: `${all.length} scenario${all.length === 1 ? "" : "s"}`,
|
|
544
|
+
items: all.slice(0, CAPS.listItems * 2).map(s => ({
|
|
545
|
+
id: str(s.id),
|
|
546
|
+
title: str(s.name) ?? str(s.id) ?? "scenario",
|
|
547
|
+
subtitle: str(s.description),
|
|
548
|
+
trailing: str(s.source) === "draft" ? "draft — needs accepting" : num(s.stepCount) !== undefined ? `${String(num(s.stepCount))} step${num(s.stepCount) === 1 ? "" : "s"}` : undefined,
|
|
549
|
+
tone: str(s.source) === "draft" ? "muted" : undefined
|
|
550
|
+
})),
|
|
551
|
+
total: all.length,
|
|
552
|
+
source: "live"
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
if (toolId === "scenarios" && action === "run" && r) {
|
|
556
|
+
if (r.ok === false) {
|
|
557
|
+
const errs = arr(r.preflightErrors).map(String).join("; ");
|
|
558
|
+
return {
|
|
559
|
+
id: id(),
|
|
560
|
+
kind: "notice",
|
|
561
|
+
tone: "warning",
|
|
562
|
+
text: `The scenario did not run — pre-flight failed${errs ? `: ${errs}` : ""}. Nothing was applied.`
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
const steps = arr(r.steps).map(rec).filter(Boolean);
|
|
566
|
+
if (steps.length === 0) return undefined;
|
|
567
|
+
return {
|
|
568
|
+
id: id(),
|
|
569
|
+
kind: "steps",
|
|
570
|
+
title: `Ran ${str(r.name) ?? "the scenario"}`,
|
|
571
|
+
steps: steps.slice(0, CAPS.listItems * 2).map(s => ({
|
|
572
|
+
text: str(s.label) ?? "step",
|
|
573
|
+
state: s.skipped === true ? "todo" : s.ok === false ? "failed" : "done"
|
|
574
|
+
}))
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ---- time machine ---------------------------------------------------
|
|
579
|
+
if (toolId === "time-machine" && action === "list" && r) {
|
|
580
|
+
const snaps = arr(r.snapshots).map(rec).filter(Boolean);
|
|
581
|
+
if (snaps.length === 0) return undefined;
|
|
582
|
+
return {
|
|
583
|
+
id: id(),
|
|
584
|
+
kind: "list",
|
|
585
|
+
title: `${snaps.length} restore point${snaps.length === 1 ? "" : "s"}`,
|
|
586
|
+
items: snaps.slice(0, CAPS.listItems * 2).map(s => ({
|
|
587
|
+
id: str(s.id),
|
|
588
|
+
title: str(s.name) ?? str(s.id) ?? "snapshot",
|
|
589
|
+
subtitle: str(rec(s.route)?.pathname),
|
|
590
|
+
trailing: s.baseline === true ? "baseline" : undefined
|
|
591
|
+
})),
|
|
592
|
+
total: snaps.length,
|
|
593
|
+
source: "live"
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
if (toolId === "time-machine" && action === "capture" && r) {
|
|
597
|
+
return {
|
|
598
|
+
id: id(),
|
|
599
|
+
kind: "notice",
|
|
600
|
+
tone: "info",
|
|
601
|
+
text: `Saved a restore point${typeof params.name === "string" ? ` — "${params.name}"` : ""}. The app can be brought back to this exact state any time.`
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
if (toolId === "time-machine" && action === "restore" && r) {
|
|
605
|
+
return {
|
|
606
|
+
id: id(),
|
|
607
|
+
kind: "notice",
|
|
608
|
+
tone: "warning",
|
|
609
|
+
text: "Restored the snapshot — the app's state just jumped back. What's on screen now reflects the restore point, not what happened since."
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// ---- jotai ----------------------------------------------------------
|
|
614
|
+
if (toolId === "jotai" && action === "listAtoms" && r) {
|
|
615
|
+
const atoms = arr(r.atoms).map(rec).filter(Boolean);
|
|
616
|
+
if (atoms.length === 0) return undefined;
|
|
617
|
+
return {
|
|
618
|
+
id: id(),
|
|
619
|
+
kind: "list",
|
|
620
|
+
title: `${num(r.total) ?? atoms.length} atom${(num(r.total) ?? atoms.length) === 1 ? "" : "s"}`,
|
|
621
|
+
items: atoms.slice(0, CAPS.listItems * 2).map(a => ({
|
|
622
|
+
id: str(a.label),
|
|
623
|
+
title: str(a.label) ?? "atom",
|
|
624
|
+
subtitle: a.writable === true ? "writable" : "read-only (derived)",
|
|
625
|
+
trailing: num(a.changes) !== undefined ? `${String(num(a.changes))} change${num(a.changes) === 1 ? "" : "s"}` : undefined
|
|
626
|
+
})),
|
|
627
|
+
total: num(r.total) ?? atoms.length,
|
|
628
|
+
source: "live"
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// ---- images ---------------------------------------------------------
|
|
633
|
+
if (toolId === "images" && action === "list" && r) {
|
|
634
|
+
const records = arr(r.records).map(rec).filter(Boolean);
|
|
635
|
+
const images = records.map(x => ({
|
|
636
|
+
url: str(x.uri) ?? "",
|
|
637
|
+
alt: str(x.uri)?.split("/").pop() ?? "image",
|
|
638
|
+
caption: x.ms !== undefined ? `${String(x.ms)} ms · ${str(x.cache) ?? ""}` : undefined
|
|
639
|
+
})).filter(i => i.url);
|
|
640
|
+
if (images.length === 0) return undefined;
|
|
641
|
+
return {
|
|
642
|
+
id: id(),
|
|
643
|
+
kind: "imageGrid",
|
|
644
|
+
title: "Images the app has loaded",
|
|
645
|
+
images: images.slice(0, CAPS.gridMax),
|
|
646
|
+
total: num(r.total) ?? images.length,
|
|
647
|
+
source: "live"
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// ---- react-query ----------------------------------------------------
|
|
652
|
+
if (toolId === "query" && action === "listQueries" && r) {
|
|
653
|
+
const qs = arr(r.queries).map(rec).filter(Boolean);
|
|
654
|
+
if (qs.length === 0) return undefined;
|
|
655
|
+
return {
|
|
656
|
+
id: id(),
|
|
657
|
+
kind: "list",
|
|
658
|
+
title: `${qs.length} cached quer${qs.length === 1 ? "y" : "ies"}`,
|
|
659
|
+
items: qs.slice(0, CAPS.listItems * 2).map(q => {
|
|
660
|
+
const key = arr(q.queryKey).filter(k => typeof k === "string").join(" › ") || str(q.queryHash) || "query";
|
|
661
|
+
const stale = q.isStale === true;
|
|
662
|
+
return {
|
|
663
|
+
id: str(q.queryHash),
|
|
664
|
+
title: key,
|
|
665
|
+
subtitle: `${str(q.status) ?? ""}${q.fetchStatus && q.fetchStatus !== "idle" ? ` · ${String(q.fetchStatus)}` : ""}`,
|
|
666
|
+
trailing: stale ? "stale" : "fresh",
|
|
667
|
+
tone: stale ? "warning" : "accent"
|
|
668
|
+
};
|
|
669
|
+
}),
|
|
670
|
+
total: qs.length,
|
|
671
|
+
source: "live"
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ---- render measurement → chart --------------------------------------
|
|
676
|
+
if (toolId === "highlight-updates" && action === "endMeasurement" && r) {
|
|
677
|
+
const comps = arr(r.components ?? r.renders).map(rec).filter(Boolean);
|
|
678
|
+
const bars = comps.map(c => ({
|
|
679
|
+
label: str(c.name) ?? str(c.componentName) ?? "?",
|
|
680
|
+
value: num(c.renders) ?? num(c.count) ?? 0
|
|
681
|
+
})).filter(b => b.value > 0).sort((a, b) => b.value - a.value).slice(0, CAPS.chartBars);
|
|
682
|
+
if (bars.length === 0) return undefined;
|
|
683
|
+
return {
|
|
684
|
+
id: id(),
|
|
685
|
+
kind: "chart",
|
|
686
|
+
title: "Renders during the measurement",
|
|
687
|
+
bars: bars.map(b => ({
|
|
688
|
+
...b,
|
|
689
|
+
unit: "renders"
|
|
690
|
+
})),
|
|
691
|
+
source: "live"
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
return undefined;
|
|
695
|
+
}
|
|
696
|
+
//# sourceMappingURL=receipts.js.map
|