@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.
- package/lib/commonjs/blocks/receipts.js +1 -761
- package/lib/commonjs/blocks/runId.js +1 -31
- package/lib/commonjs/blocks/types.js +1 -140
- package/lib/commonjs/blocks/uiTool.js +6 -1405
- package/lib/commonjs/catalog/catalog.g.js +5 -4556
- package/lib/commonjs/catalog/catalog.source.json +249 -0
- package/lib/commonjs/catalog/catalog.types.g.js +1 -46
- package/lib/commonjs/catalog/normalizeParams.js +2 -332
- package/lib/commonjs/catalog/signature.js +1 -68
- package/lib/commonjs/catalog/snapshotReads.js +1 -263
- package/lib/commonjs/catalog/toProviderTools.js +2 -145
- package/lib/commonjs/catalog/validateParams.js +1 -129
- package/lib/commonjs/context/buildContextPack.js +1 -489
- package/lib/commonjs/effects/digest.js +1 -34
- package/lib/commonjs/effects/ledger.js +1 -345
- package/lib/commonjs/engine/askGate.js +1 -287
- package/lib/commonjs/engine/effectFor.js +1 -223
- package/lib/commonjs/engine/evidence.js +3 -111
- package/lib/commonjs/engine/historyBudget.js +1 -363
- package/lib/commonjs/engine/retrieve.js +1 -214
- package/lib/commonjs/engine/runAgentTurn.js +8 -1836
- package/lib/commonjs/engine/systemPrompt.js +54 -163
- package/lib/commonjs/engine/textToolCalls.js +1 -276
- package/lib/commonjs/engine/tokenCalibration.js +1 -81
- package/lib/commonjs/engine/verify.js +4 -297
- package/lib/commonjs/index.js +1 -384
- package/lib/commonjs/policy/labels.js +1 -319
- package/lib/commonjs/policy/policy.js +1 -148
- package/lib/commonjs/policy/redact.js +1 -172
- package/lib/commonjs/providers/anthropic.js +3 -445
- package/lib/commonjs/providers/openai.js +4 -324
- package/lib/commonjs/providers/problem.js +1 -98
- package/lib/commonjs/providers/sse.js +1 -240
- package/lib/commonjs/providers/streamTimer.js +1 -123
- package/lib/commonjs/providers/transport.js +1 -123
- package/lib/commonjs/providers/types.js +1 -6
- package/lib/commonjs/providers/xhrStream.js +1 -212
- package/lib/commonjs/realNow.js +1 -0
- package/lib/commonjs/session.js +1 -393
- package/lib/commonjs/types.js +0 -1
- package/lib/module/blocks/receipts.js +1 -755
- package/lib/module/blocks/runId.js +1 -26
- package/lib/module/blocks/types.js +1 -134
- package/lib/module/blocks/uiTool.js +6 -1396
- package/lib/module/catalog/catalog.g.js +5 -4552
- package/lib/module/catalog/catalog.source.json +249 -0
- package/lib/module/catalog/catalog.types.g.js +1 -42
- package/lib/module/catalog/normalizeParams.js +2 -326
- package/lib/module/catalog/signature.js +1 -63
- package/lib/module/catalog/snapshotReads.js +1 -259
- package/lib/module/catalog/toProviderTools.js +2 -139
- package/lib/module/catalog/validateParams.js +1 -124
- package/lib/module/context/buildContextPack.js +1 -485
- package/lib/module/effects/digest.js +1 -29
- package/lib/module/effects/ledger.js +1 -339
- package/lib/module/engine/askGate.js +1 -280
- package/lib/module/engine/effectFor.js +1 -220
- package/lib/module/engine/evidence.js +3 -105
- package/lib/module/engine/historyBudget.js +1 -355
- package/lib/module/engine/retrieve.js +1 -210
- package/lib/module/engine/runAgentTurn.js +8 -1827
- package/lib/module/engine/systemPrompt.js +54 -157
- package/lib/module/engine/textToolCalls.js +1 -270
- package/lib/module/engine/tokenCalibration.js +1 -75
- package/lib/module/engine/verify.js +4 -289
- package/lib/module/index.js +1 -35
- package/lib/module/policy/labels.js +1 -312
- package/lib/module/policy/policy.js +1 -142
- package/lib/module/policy/redact.js +1 -165
- package/lib/module/providers/anthropic.js +3 -441
- package/lib/module/providers/openai.js +4 -320
- package/lib/module/providers/problem.js +1 -92
- package/lib/module/providers/sse.js +1 -231
- package/lib/module/providers/streamTimer.js +1 -118
- package/lib/module/providers/transport.js +1 -119
- package/lib/module/providers/types.js +1 -4
- package/lib/module/providers/xhrStream.js +1 -207
- package/lib/module/realNow.js +1 -0
- package/lib/module/session.js +1 -369
- package/lib/module/types.js +0 -1
- package/lib/typescript/catalog/catalog.g.d.ts +3 -3
- package/lib/typescript/catalog/catalog.types.g.d.ts +3 -2
- package/lib/typescript/providers/problem.d.ts +0 -20
- package/lib/typescript/providers/streamTimer.d.ts +0 -24
- package/lib/typescript/realNow.d.ts +18 -0
- package/lib/web/index.mjs +52 -53
- package/package.json +1 -1
- package/lib/commonjs/blocks/receipts.js.map +0 -1
- package/lib/commonjs/blocks/runId.js.map +0 -1
- package/lib/commonjs/blocks/types.js.map +0 -1
- package/lib/commonjs/blocks/uiTool.js.map +0 -1
- package/lib/commonjs/catalog/catalog.g.js.map +0 -1
- package/lib/commonjs/catalog/catalog.types.g.js.map +0 -1
- package/lib/commonjs/catalog/normalizeParams.js.map +0 -1
- package/lib/commonjs/catalog/signature.js.map +0 -1
- package/lib/commonjs/catalog/snapshotReads.js.map +0 -1
- package/lib/commonjs/catalog/toProviderTools.js.map +0 -1
- package/lib/commonjs/catalog/validateParams.js.map +0 -1
- package/lib/commonjs/context/buildContextPack.js.map +0 -1
- package/lib/commonjs/effects/digest.js.map +0 -1
- package/lib/commonjs/effects/ledger.js.map +0 -1
- package/lib/commonjs/engine/askGate.js.map +0 -1
- package/lib/commonjs/engine/effectFor.js.map +0 -1
- package/lib/commonjs/engine/evidence.js.map +0 -1
- package/lib/commonjs/engine/historyBudget.js.map +0 -1
- package/lib/commonjs/engine/retrieve.js.map +0 -1
- package/lib/commonjs/engine/runAgentTurn.js.map +0 -1
- package/lib/commonjs/engine/systemPrompt.js.map +0 -1
- package/lib/commonjs/engine/textToolCalls.js.map +0 -1
- package/lib/commonjs/engine/tokenCalibration.js.map +0 -1
- package/lib/commonjs/engine/verify.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/policy/labels.js.map +0 -1
- package/lib/commonjs/policy/policy.js.map +0 -1
- package/lib/commonjs/policy/redact.js.map +0 -1
- package/lib/commonjs/providers/anthropic.js.map +0 -1
- package/lib/commonjs/providers/openai.js.map +0 -1
- package/lib/commonjs/providers/problem.js.map +0 -1
- package/lib/commonjs/providers/sse.js.map +0 -1
- package/lib/commonjs/providers/streamTimer.js.map +0 -1
- package/lib/commonjs/providers/transport.js.map +0 -1
- package/lib/commonjs/providers/types.js.map +0 -1
- package/lib/commonjs/providers/xhrStream.js.map +0 -1
- package/lib/commonjs/session.js.map +0 -1
- package/lib/commonjs/types.js.map +0 -1
- package/lib/module/blocks/receipts.js.map +0 -1
- package/lib/module/blocks/runId.js.map +0 -1
- package/lib/module/blocks/types.js.map +0 -1
- package/lib/module/blocks/uiTool.js.map +0 -1
- package/lib/module/catalog/catalog.g.js.map +0 -1
- package/lib/module/catalog/catalog.types.g.js.map +0 -1
- package/lib/module/catalog/normalizeParams.js.map +0 -1
- package/lib/module/catalog/signature.js.map +0 -1
- package/lib/module/catalog/snapshotReads.js.map +0 -1
- package/lib/module/catalog/toProviderTools.js.map +0 -1
- package/lib/module/catalog/validateParams.js.map +0 -1
- package/lib/module/context/buildContextPack.js.map +0 -1
- package/lib/module/effects/digest.js.map +0 -1
- package/lib/module/effects/ledger.js.map +0 -1
- package/lib/module/engine/askGate.js.map +0 -1
- package/lib/module/engine/effectFor.js.map +0 -1
- package/lib/module/engine/evidence.js.map +0 -1
- package/lib/module/engine/historyBudget.js.map +0 -1
- package/lib/module/engine/retrieve.js.map +0 -1
- package/lib/module/engine/runAgentTurn.js.map +0 -1
- package/lib/module/engine/systemPrompt.js.map +0 -1
- package/lib/module/engine/textToolCalls.js.map +0 -1
- package/lib/module/engine/tokenCalibration.js.map +0 -1
- package/lib/module/engine/verify.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/policy/labels.js.map +0 -1
- package/lib/module/policy/policy.js.map +0 -1
- package/lib/module/policy/redact.js.map +0 -1
- package/lib/module/providers/anthropic.js.map +0 -1
- package/lib/module/providers/openai.js.map +0 -1
- package/lib/module/providers/problem.js.map +0 -1
- package/lib/module/providers/sse.js.map +0 -1
- package/lib/module/providers/streamTimer.js.map +0 -1
- package/lib/module/providers/transport.js.map +0 -1
- package/lib/module/providers/types.js.map +0 -1
- package/lib/module/providers/xhrStream.js.map +0 -1
- package/lib/module/session.js.map +0 -1
- package/lib/module/types.js.map +0 -1
- package/lib/typescript/blocks/receipts.d.ts.map +0 -1
- package/lib/typescript/blocks/runId.d.ts.map +0 -1
- package/lib/typescript/blocks/types.d.ts.map +0 -1
- package/lib/typescript/blocks/uiTool.d.ts.map +0 -1
- package/lib/typescript/catalog/catalog.g.d.ts.map +0 -1
- package/lib/typescript/catalog/catalog.types.g.d.ts.map +0 -1
- package/lib/typescript/catalog/normalizeParams.d.ts.map +0 -1
- package/lib/typescript/catalog/signature.d.ts.map +0 -1
- package/lib/typescript/catalog/snapshotReads.d.ts.map +0 -1
- package/lib/typescript/catalog/toProviderTools.d.ts.map +0 -1
- package/lib/typescript/catalog/validateParams.d.ts.map +0 -1
- package/lib/typescript/context/buildContextPack.d.ts.map +0 -1
- package/lib/typescript/effects/digest.d.ts.map +0 -1
- package/lib/typescript/effects/ledger.d.ts.map +0 -1
- package/lib/typescript/engine/askGate.d.ts.map +0 -1
- package/lib/typescript/engine/effectFor.d.ts.map +0 -1
- package/lib/typescript/engine/evidence.d.ts.map +0 -1
- package/lib/typescript/engine/historyBudget.d.ts.map +0 -1
- package/lib/typescript/engine/retrieve.d.ts.map +0 -1
- package/lib/typescript/engine/runAgentTurn.d.ts.map +0 -1
- package/lib/typescript/engine/systemPrompt.d.ts.map +0 -1
- package/lib/typescript/engine/textToolCalls.d.ts.map +0 -1
- package/lib/typescript/engine/tokenCalibration.d.ts.map +0 -1
- package/lib/typescript/engine/verify.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/policy/labels.d.ts.map +0 -1
- package/lib/typescript/policy/policy.d.ts.map +0 -1
- package/lib/typescript/policy/redact.d.ts.map +0 -1
- package/lib/typescript/providers/anthropic.d.ts.map +0 -1
- package/lib/typescript/providers/openai.d.ts.map +0 -1
- package/lib/typescript/providers/problem.d.ts.map +0 -1
- package/lib/typescript/providers/sse.d.ts.map +0 -1
- package/lib/typescript/providers/streamTimer.d.ts.map +0 -1
- package/lib/typescript/providers/transport.d.ts.map +0 -1
- package/lib/typescript/providers/types.d.ts.map +0 -1
- package/lib/typescript/providers/xhrStream.d.ts.map +0 -1
- package/lib/typescript/session.d.ts.map +0 -1
- package/lib/typescript/types.d.ts.map +0 -1
|
@@ -1,327 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";const f=i=>!!i&&typeof i==="object"&&!Array.isArray(i);export function hashQueryKey(i){return JSON.stringify(i,(e,t)=>f(t)?Object.keys(t).sort().reduce((a,r)=>{a[r]=t[r];return a},{}):t)}function n(i,e,t,a){if(i[t]!==void 0||i[e]===void 0)return false;i[t]=i[e];delete i[e];a.push(`Accepted \`${e}\` as \`${t}\` \u2014 the parameter is named \`${t}\`.`);return true}const o=new Set(["getQueryData","refetch","invalidate","reset","remove","triggerError","restoreError","triggerLoading","restoreLoading"]);const l=["id","urlPattern","enabled","name","methods","kind","status","statusText","headers","body","bodyPatch","bodyPath","bodyValue","failKind","delayMs","times","alternate","force","bodyOmitted"];function h(i,e,t){if(o.has(i)&&e.queryHash===void 0){const a=f(e.filters)?e.filters:void 0;const r=e.queryKey??e.key??e.hash??a?.queryKey??a?.queryHash;if(Array.isArray(r)){e.queryHash=hashQueryKey(r);t.push(`Accepted the queryKey array as the query's hash (${e.queryHash}) \u2014 the parameter is \`queryHash\`, the string from listQueries.`)}else if(typeof r==="string"){e.queryHash=r;t.push(`Accepted it as \`queryHash\` \u2014 that is the parameter name.`)}delete e.queryKey;delete e.key;delete e.hash;delete e.filters}if(i==="setQueryData"){if(f(e.data)){for(const a of["merge","force"]){if(typeof e.data[a]==="boolean"&&e[a]===void 0){e[a]=e.data[a];delete e.data[a];t.push(`Moved \`${a}\` out of \`data\` to the top level \u2014 it is a write flag, not part of the value.`)}}}n(e,"newData","data",t)||e.path===void 0&&n(e,"value","data",t)||n(e,"patch","data",t);if(e.queryKey===void 0&&typeof e.queryHash==="string"){try{const a=JSON.parse(e.queryHash);if(Array.isArray(a)){e.queryKey=a;t.push("Accepted the queryHash as the key \u2014 setQueryData takes `queryKey`, the array.")}}catch{}}if(e.patch!==void 0||e.merge===void 0){if(t.some(a=>a.includes("`patch`")))e.merge=true}}}function c(i,e,t){if(i==="getEventBody"||i==="setPinned"||i==="setSaved"){n(e,"requestId","id",t)||n(e,"eventId","id",t)}if(i==="deleteOverrideRule"||i==="setOverrideRuleEnabled"||i==="getOverrideRuleBody"){n(e,"ruleId","id",t)}if(i==="upsertOverrideRule"){const a=f(e.rule)?{...e.rule}:{};if(e.fromRequestId===void 0&&typeof a.fromRequestId==="string"){e.fromRequestId=a.fromRequestId;t.push("Accepted `rule.fromRequestId` \u2014 it is a top-level parameter, beside `rule`.")}delete a.fromRequestId;y(a,t);m(e,a,t);const r=[];for(const d of l){if(e[d]!==void 0&&a[d]===void 0){a[d]=e[d];r.push(d)}delete e[d]}if(r.length)t.push(`Accepted ${r.map(d=>`\`${d}\``).join(", ")} at the top level \u2014 rule fields belong inside \`rule\`.`);if(Object.keys(a).length)e.rule=a}}function y(i,e){for(const[t,a]of[["match",["urlPattern","methods","method","url","pattern","urlContains","contains"]],["response",["status","statusText","body","headers","bodyPatch"]]]){const r=i[t];if(!f(r))continue;const d=[];for(const s of a){if(r[s]===void 0)continue;const u=s==="method"?"methods":s==="url"||s==="pattern"||s==="urlContains"||s==="contains"?"urlPattern":s;if(i[u]!==void 0)continue;i[u]=u==="methods"&&!Array.isArray(r[s])?[r[s]]:r[s];delete r[s];d.push(s)}if(!d.length)continue;if(!Object.keys(r).length)delete i[t];e.push(`Read \`rule.${t}.{${d.join(", ")}}\` as top-level rule fields \u2014 this rule is flat, unlike msw/nock-style mocks.`)}}function m(i,e,t){if(e.bodyPath===void 0&&typeof e.path==="string"){e.bodyPath=e.path;if(e.bodyValue===void 0&&e.value!==void 0)e.bodyValue=e.value;delete e.path;delete e.value;t.push("Read `path`/`value` as `bodyPath`/`bodyValue` \u2014 on a rule they address the response body.")}const a=s=>e[s]!==void 0?e[s]:i[s];const r=s=>{delete e[s];delete i[s]};const d=a("mode");if(typeof d==="string"&&["respond","fail","delay"].includes(d)&&e.kind===void 0){e.kind=d;t.push(`Read \`mode: "${d}"\` as \`kind: "${d}"\`.`)}r("mode");if(a("fail")===true&&e.kind===void 0){e.kind="fail";t.push('Read `fail: true` as `kind: "fail"` \u2014 a transport failure, as if offline.')}r("fail");if(a("failOnce")===true){if(e.kind===void 0)e.kind="fail";if(e.times===void 0)e.times=1;t.push('Read `failOnce: true` as `kind: "fail"` with `times: 1` \u2014 it fires once, then the rule disables itself.')}r("failOnce");if(a("once")===true&&e.times===void 0){e.times=1;t.push("Read `once: true` as `times: 1` \u2014 the rule auto-disables after one match.")}r("once");for(const s of["maxHits","hits","count","maxTimes","applyTimes"]){const u=a(s);if(typeof u==="number"&&Number.isFinite(u)&&u>0&&e.times===void 0){e.times=u;t.push(`Read \`${s}: ${u}\` as \`times: ${u}\` \u2014 the rule auto-disables after ${u} match(es).`)}r(s)}}function b(i,e,t){n(e,"name","storeName",t)||n(e,"store","storeName",t);if(i==="setState"){n(e,"statePatch","state",t)||n(e,"partial","state",t)||n(e,"patch","state",t)||n(e,"newState","state",t)||n(e,"nextState","state",t)||e.path===void 0&&n(e,"value","state",t);if(e.replace===void 0){e.replace=false;t.push("Applied replace:false (merge) \u2014 a store keeps action functions that can't be re-sent, and replacing would wipe them (the buttons that call them would then crash). Send replace:true only to deliberately reset the whole store.")}}}function g(i,e,t){if(i==="navigate"){n(e,"route","path",t)||n(e,"to","path",t)||n(e,"url","path",t)||n(e,"pathname","path",t)}}export function normalizeParams(i,e,t){const a={...t};const r=[];switch(i){case"query":h(e,a,r);break;case"network":c(e,a,r);break;case"zustand":b(e,a,r);break;case"route-events":g(e,a,r);break;default:break}return{params:a,notes:r}}export function aliasNote(i){return i.length?`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* Parameter aliases — accept what models actually send.
|
|
5
|
-
*
|
|
6
|
-
* The provider sees ONE tool per Buoy tool with `params` as an open object
|
|
7
|
-
* (see toProviderTools.ts), so the per-action parameter names live in prose
|
|
8
|
-
* and every model has to reproduce them from memory. A sweep of 18 models
|
|
9
|
-
* against the live app showed the same handful of misses over and over, on
|
|
10
|
-
* cheap models AND on gpt-5 and Gemini 2.5 Pro:
|
|
11
|
-
*
|
|
12
|
-
* queryKey: [...] where the action wants queryHash (by far the #1 miss —
|
|
13
|
-
* the RIGHT NOW block shows keys as arrays, and
|
|
14
|
-
* setQueryData takes queryKey, so the model reuses it)
|
|
15
|
-
* requestId where network.getEventBody wants id
|
|
16
|
-
* newState/patch where zustand.setState wants state
|
|
17
|
-
* name where zustand wants storeName
|
|
18
|
-
* bodyPatch/body/… at the top level of upsertOverrideRule instead of inside `rule`
|
|
19
|
-
*
|
|
20
|
-
* A validation error names the right field, but a cheap model repeats the same
|
|
21
|
-
* wrong call or gives up, and the user sees "nothing happened". Every one of
|
|
22
|
-
* these is mechanically unambiguous, so it is fixed HERE, before validation,
|
|
23
|
-
* and the result carries a one-line note so the model learns the real name
|
|
24
|
-
* without failing the call. Nothing here guesses: an alias only fires when the
|
|
25
|
-
* canonical field is absent and the alias is present.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
const isPlainObject = v => !!v && typeof v === "object" && !Array.isArray(v);
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* TanStack's default `hashKey`: JSON.stringify with plain-object keys sorted,
|
|
32
|
-
* so `["todos", {page:1, sort:"a"}]` hashes the same whatever order the model
|
|
33
|
-
* wrote the object in.
|
|
34
|
-
*/
|
|
35
|
-
export function hashQueryKey(key) {
|
|
36
|
-
return JSON.stringify(key, (_, val) => isPlainObject(val) ? Object.keys(val).sort().reduce((acc, k) => {
|
|
37
|
-
acc[k] = val[k];
|
|
38
|
-
return acc;
|
|
39
|
-
}, {}) : val);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Rename `from` to `to` when `to` is absent and `from` is present. */
|
|
43
|
-
function alias(p, from, to, notes) {
|
|
44
|
-
if (p[to] !== undefined || p[from] === undefined) return false;
|
|
45
|
-
p[to] = p[from];
|
|
46
|
-
delete p[from];
|
|
47
|
-
notes.push(`Accepted \`${from}\` as \`${to}\` — the parameter is named \`${to}\`.`);
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
const QUERY_HASH_ACTIONS = new Set(["getQueryData", "refetch", "invalidate", "reset", "remove", "triggerError", "restoreError", "triggerLoading", "restoreLoading"]);
|
|
51
|
-
|
|
52
|
-
/** Fields that belong inside `rule` for network.upsertOverrideRule. */
|
|
53
|
-
const RULE_FIELDS = ["id", "urlPattern", "enabled", "name", "methods", "kind", "status", "statusText", "headers", "body", "bodyPatch", "bodyPath", "bodyValue", "failKind", "delayMs", "times", "alternate", "force", "bodyOmitted"];
|
|
54
|
-
function normalizeQuery(action, p, notes) {
|
|
55
|
-
if (QUERY_HASH_ACTIONS.has(action) && p.queryHash === undefined) {
|
|
56
|
-
// `filters: { queryKey }` — the TanStack client-API shape.
|
|
57
|
-
const filters = isPlainObject(p.filters) ? p.filters : undefined;
|
|
58
|
-
const key = p.queryKey ?? p.key ?? p.hash ?? filters?.queryKey ?? filters?.queryHash;
|
|
59
|
-
if (Array.isArray(key)) {
|
|
60
|
-
p.queryHash = hashQueryKey(key);
|
|
61
|
-
notes.push(`Accepted the queryKey array as the query's hash (${p.queryHash}) — the parameter is \`queryHash\`, the string from listQueries.`);
|
|
62
|
-
} else if (typeof key === "string") {
|
|
63
|
-
// A hash string under another name, or a loose "a,b,c" spelling the
|
|
64
|
-
// adapter resolves against the live cache.
|
|
65
|
-
p.queryHash = key;
|
|
66
|
-
notes.push(`Accepted it as \`queryHash\` — that is the parameter name.`);
|
|
67
|
-
}
|
|
68
|
-
delete p.queryKey;
|
|
69
|
-
delete p.key;
|
|
70
|
-
delete p.hash;
|
|
71
|
-
delete p.filters;
|
|
72
|
-
}
|
|
73
|
-
if (action === "setQueryData") {
|
|
74
|
-
// Models routinely tuck the write FLAGS inside `data` — `data: {item:{…},
|
|
75
|
-
// merge:true}` — which turns a merge into a replace and leaves a junk
|
|
76
|
-
// `merge`/`force` key in the cache. Lift a boolean flag out of `data` to
|
|
77
|
-
// the top level when the top level doesn't already set it. Guarded to
|
|
78
|
-
// booleans so a real data field named "merge" is never disturbed.
|
|
79
|
-
if (isPlainObject(p.data)) {
|
|
80
|
-
for (const flag of ["merge", "force"]) {
|
|
81
|
-
if (typeof p.data[flag] === "boolean" && p[flag] === undefined) {
|
|
82
|
-
p[flag] = p.data[flag];
|
|
83
|
-
delete p.data[flag];
|
|
84
|
-
notes.push(`Moved \`${flag}\` out of \`data\` to the top level — it is a write flag, not part of the value.`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// `value` normally means "the patch, spelled differently". But alongside
|
|
89
|
-
// `path` it means the OPPOSITE — the single value that path is set to — so
|
|
90
|
-
// rewriting it there would turn `path:"item.name", value:"x"` into a
|
|
91
|
-
// top-level patch of `"x"`, silently. Only the path form reserves it.
|
|
92
|
-
alias(p, "newData", "data", notes) || p.path === undefined && alias(p, "value", "data", notes) || alias(p, "patch", "data", notes);
|
|
93
|
-
if (p.queryKey === undefined && typeof p.queryHash === "string") {
|
|
94
|
-
try {
|
|
95
|
-
const parsed = JSON.parse(p.queryHash);
|
|
96
|
-
if (Array.isArray(parsed)) {
|
|
97
|
-
p.queryKey = parsed;
|
|
98
|
-
notes.push("Accepted the queryHash as the key — setQueryData takes `queryKey`, the array.");
|
|
99
|
-
}
|
|
100
|
-
} catch {
|
|
101
|
-
/* leave it; validation will say what is missing */
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (p.patch !== undefined || p.merge === undefined) {
|
|
105
|
-
// A caller that said "patch" meant merge.
|
|
106
|
-
if (notes.some(n => n.includes("`patch`"))) p.merge = true;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function normalizeNetwork(action, p, notes) {
|
|
111
|
-
if (action === "getEventBody" || action === "setPinned" || action === "setSaved") {
|
|
112
|
-
alias(p, "requestId", "id", notes) || alias(p, "eventId", "id", notes);
|
|
113
|
-
}
|
|
114
|
-
if (action === "deleteOverrideRule" || action === "setOverrideRuleEnabled" || action === "getOverrideRuleBody") {
|
|
115
|
-
alias(p, "ruleId", "id", notes);
|
|
116
|
-
}
|
|
117
|
-
if (action === "upsertOverrideRule") {
|
|
118
|
-
const rule = isPlainObject(p.rule) ? {
|
|
119
|
-
...p.rule
|
|
120
|
-
} : {};
|
|
121
|
-
// fromRequestId is a TOP-LEVEL field; models tuck it into the rule.
|
|
122
|
-
if (p.fromRequestId === undefined && typeof rule.fromRequestId === "string") {
|
|
123
|
-
p.fromRequestId = rule.fromRequestId;
|
|
124
|
-
notes.push("Accepted `rule.fromRequestId` — it is a top-level parameter, beside `rule`.");
|
|
125
|
-
}
|
|
126
|
-
delete rule.fromRequestId;
|
|
127
|
-
unwrapRuleSections(rule, notes);
|
|
128
|
-
ruleIntentWords(p, rule, notes);
|
|
129
|
-
// Rule fields sent flat at the top level move inside `rule`.
|
|
130
|
-
const moved = [];
|
|
131
|
-
for (const f of RULE_FIELDS) {
|
|
132
|
-
if (p[f] !== undefined && rule[f] === undefined) {
|
|
133
|
-
rule[f] = p[f];
|
|
134
|
-
moved.push(f);
|
|
135
|
-
}
|
|
136
|
-
delete p[f];
|
|
137
|
-
}
|
|
138
|
-
if (moved.length) notes.push(`Accepted ${moved.map(f => `\`${f}\``).join(", ")} at the top level — rule fields belong inside \`rule\`.`);
|
|
139
|
-
if (Object.keys(rule).length) p.rule = rule;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Flatten the `match` / `response` wrappers models borrow from other mocking
|
|
145
|
-
* libraries.
|
|
146
|
-
*
|
|
147
|
-
* Our rule is flat — `{urlPattern, methods, status, body, times}`. Nearly every
|
|
148
|
-
* HTTP-mocking library a model has read is not: msw, nock and WireMock all group
|
|
149
|
-
* the matcher and the reply into separate objects. So a model that has never
|
|
150
|
-
* seen our schema (and it has not — `params` is an unconstrained object) writes
|
|
151
|
-
* the convention it knows. Measured: Claude sent
|
|
152
|
-
* `{times: 1, match: {method, urlPattern}, response: {status, body}}` — every
|
|
153
|
-
* value correct, in the shape the rest of the world uses.
|
|
154
|
-
*
|
|
155
|
-
* Only these two wrapper names, and only fields that are really ours: an
|
|
156
|
-
* unrecognised key inside one is left where it is rather than hoisted, so this
|
|
157
|
-
* cannot quietly invent a rule field that does not exist.
|
|
158
|
-
*/
|
|
159
|
-
function unwrapRuleSections(rule, notes) {
|
|
160
|
-
for (const [wrapper, fields] of [["match", ["urlPattern", "methods", "method", "url", "pattern", "urlContains", "contains"]], ["response", ["status", "statusText", "body", "headers", "bodyPatch"]]]) {
|
|
161
|
-
const inner = rule[wrapper];
|
|
162
|
-
if (!isPlainObject(inner)) continue;
|
|
163
|
-
const moved = [];
|
|
164
|
-
for (const f of fields) {
|
|
165
|
-
if (inner[f] === undefined) continue;
|
|
166
|
-
// `method` and `url` are that library's spelling of ours.
|
|
167
|
-
const target = f === "method" ? "methods" : f === "url" || f === "pattern" || f === "urlContains" || f === "contains" ? "urlPattern" : f;
|
|
168
|
-
if (rule[target] !== undefined) continue;
|
|
169
|
-
rule[target] = target === "methods" && !Array.isArray(inner[f]) ? [inner[f]] : inner[f];
|
|
170
|
-
delete inner[f];
|
|
171
|
-
moved.push(f);
|
|
172
|
-
}
|
|
173
|
-
if (!moved.length) continue;
|
|
174
|
-
// Everything recognised moved out; drop the wrapper only if nothing is left,
|
|
175
|
-
// so a leftover key still surfaces as a schema error rather than vanishing.
|
|
176
|
-
if (!Object.keys(inner).length) delete rule[wrapper];
|
|
177
|
-
notes.push(`Read \`rule.${wrapper}.{${moved.join(", ")}}\` as top-level rule fields — this rule is flat, unlike msw/nock-style mocks.`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* The words models reach for when describing an override rule, mapped to the
|
|
183
|
-
* fields the rule actually has.
|
|
184
|
-
*
|
|
185
|
-
* MEASURED, not invented. On `api-sim/next-call-fails-once` — "make the next
|
|
186
|
-
* menu request fail, just the next one" — every model that acted picked the
|
|
187
|
-
* right tool and explained the right reason, then wrote the rule like this:
|
|
188
|
-
*
|
|
189
|
-
* grok ×3 {fail: true, once: true}
|
|
190
|
-
* composer {fail: true} and {maxHits: 1, fail: true}
|
|
191
|
-
* opus {status: 500, enabled: true}
|
|
192
|
-
*
|
|
193
|
-
* The schema wants `{kind: "fail", times: 1}`. Ten of fourteen attempts on that
|
|
194
|
-
* case graded `acceptable` rather than `best` for no reason except this — the
|
|
195
|
-
* budget was expressed as `once`/`maxHits` and silently dropped, leaving a rule
|
|
196
|
-
* that never expires.
|
|
197
|
-
*
|
|
198
|
-
* It is NOT a missing instruction. `times` is documented as "Use 1 for 'make the
|
|
199
|
-
* next call fail' so it cannot linger" — very nearly the user's own sentence —
|
|
200
|
-
* and every model read past it. That is the same finding as the nesting-depth
|
|
201
|
-
* work: the fix has to be structural, not a better sentence.
|
|
202
|
-
*
|
|
203
|
-
* Each mapping only ever makes a rule NARROWER (a budget where there was none,
|
|
204
|
-
* a transport failure where the kind was unstated), so a misread costs a rule
|
|
205
|
-
* that stops too early rather than one that runs forever.
|
|
206
|
-
*/
|
|
207
|
-
function ruleIntentWords(p, rule, notes) {
|
|
208
|
-
// `path`/`value` is what the state tools call this, and a model that learned
|
|
209
|
-
// it on `zustand.setState` or `query.setQueryData` writes the same words here.
|
|
210
|
-
// A rule has no other meaning for either name, so accept them.
|
|
211
|
-
if (rule.bodyPath === undefined && typeof rule.path === "string") {
|
|
212
|
-
rule.bodyPath = rule.path;
|
|
213
|
-
if (rule.bodyValue === undefined && rule.value !== undefined) rule.bodyValue = rule.value;
|
|
214
|
-
delete rule.path;
|
|
215
|
-
delete rule.value;
|
|
216
|
-
notes.push("Read `path`/`value` as `bodyPath`/`bodyValue` — on a rule they address the response body.");
|
|
217
|
-
}
|
|
218
|
-
const read = key => rule[key] !== undefined ? rule[key] : p[key];
|
|
219
|
-
const drop = key => {
|
|
220
|
-
delete rule[key];
|
|
221
|
-
delete p[key];
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
// `mode` is the other library's name for `kind`, and models use it verbatim:
|
|
225
|
-
// measured on `api-sim/api-down`, alongside `match.urlContains`.
|
|
226
|
-
const mode = read("mode");
|
|
227
|
-
if (typeof mode === "string" && ["respond", "fail", "delay"].includes(mode) && rule.kind === undefined) {
|
|
228
|
-
rule.kind = mode;
|
|
229
|
-
notes.push(`Read \`mode: "${mode}"\` as \`kind: "${mode}"\`.`);
|
|
230
|
-
}
|
|
231
|
-
drop("mode");
|
|
232
|
-
|
|
233
|
-
// "fail" as a flag, rather than kind:"fail".
|
|
234
|
-
if (read("fail") === true && rule.kind === undefined) {
|
|
235
|
-
rule.kind = "fail";
|
|
236
|
-
notes.push("Read `fail: true` as `kind: \"fail\"` — a transport failure, as if offline.");
|
|
237
|
-
}
|
|
238
|
-
drop("fail");
|
|
239
|
-
|
|
240
|
-
// "just once" / "only the next one", in the several ways it gets written.
|
|
241
|
-
if (read("failOnce") === true) {
|
|
242
|
-
if (rule.kind === undefined) rule.kind = "fail";
|
|
243
|
-
if (rule.times === undefined) rule.times = 1;
|
|
244
|
-
notes.push("Read `failOnce: true` as `kind: \"fail\"` with `times: 1` — it fires once, then the rule disables itself.");
|
|
245
|
-
}
|
|
246
|
-
drop("failOnce");
|
|
247
|
-
if (read("once") === true && rule.times === undefined) {
|
|
248
|
-
rule.times = 1;
|
|
249
|
-
notes.push("Read `once: true` as `times: 1` — the rule auto-disables after one match.");
|
|
250
|
-
}
|
|
251
|
-
drop("once");
|
|
252
|
-
|
|
253
|
-
// A numeric budget under any of its other names. `limit` is deliberately NOT
|
|
254
|
-
// here: it means "how many to list" on half the actions in this catalog, and
|
|
255
|
-
// a wrong guess about a budget is a rule that stops early on a request the
|
|
256
|
-
// caller still needed.
|
|
257
|
-
for (const key of ["maxHits", "hits", "count", "maxTimes", "applyTimes"]) {
|
|
258
|
-
const v = read(key);
|
|
259
|
-
if (typeof v === "number" && Number.isFinite(v) && v > 0 && rule.times === undefined) {
|
|
260
|
-
rule.times = v;
|
|
261
|
-
notes.push(`Read \`${key}: ${v}\` as \`times: ${v}\` — the rule auto-disables after ${v} match(es).`);
|
|
262
|
-
}
|
|
263
|
-
drop(key);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function normalizeZustand(action, p, notes) {
|
|
267
|
-
alias(p, "name", "storeName", notes) || alias(p, "store", "storeName", notes);
|
|
268
|
-
if (action === "setState") {
|
|
269
|
-
alias(p, "statePatch", "state", notes) || alias(p, "partial", "state", notes) || alias(p, "patch", "state", notes) || alias(p, "newState", "state", notes) || alias(p, "nextState", "state", notes) ||
|
|
270
|
-
// See the same guard on setQueryData: with `path`, `value` is the value
|
|
271
|
-
// being set, not another spelling of the patch.
|
|
272
|
-
p.path === undefined && alias(p, "value", "state", notes);
|
|
273
|
-
// ALWAYS merge, never replace, unless the caller explicitly said replace.
|
|
274
|
-
// The adapter defaults replace:true, which throws away everything not sent
|
|
275
|
-
// — including the store's ACTION FUNCTIONS (setQty, removeLine, …), which
|
|
276
|
-
// can't cross the JSON wire, so a remote caller never has them to re-send.
|
|
277
|
-
// The data then looks right but the buttons that call those methods crash.
|
|
278
|
-
// A merge (replace:false) shallow-merges the fields sent and keeps the
|
|
279
|
-
// functions and untouched keys intact.
|
|
280
|
-
if (p.replace === undefined) {
|
|
281
|
-
p.replace = false;
|
|
282
|
-
notes.push("Applied replace:false (merge) — a store keeps action functions that can't be re-sent, and replacing would wipe them (the buttons that call them would then crash). Send replace:true only to deliberately reset the whole store.");
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
function normalizeRoutes(action, p, notes) {
|
|
287
|
-
if (action === "navigate") {
|
|
288
|
-
alias(p, "route", "path", notes) || alias(p, "to", "path", notes) || alias(p, "url", "path", notes) || alias(p, "pathname", "path", notes);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Fix the unambiguous parameter-name misses before validation. Returns the
|
|
294
|
-
* same object shape validation expects plus the notes to append to the result.
|
|
295
|
-
*/
|
|
296
|
-
export function normalizeParams(toolId, action, raw) {
|
|
297
|
-
const p = {
|
|
298
|
-
...raw
|
|
299
|
-
};
|
|
300
|
-
const notes = [];
|
|
301
|
-
switch (toolId) {
|
|
302
|
-
case "query":
|
|
303
|
-
normalizeQuery(action, p, notes);
|
|
304
|
-
break;
|
|
305
|
-
case "network":
|
|
306
|
-
normalizeNetwork(action, p, notes);
|
|
307
|
-
break;
|
|
308
|
-
case "zustand":
|
|
309
|
-
normalizeZustand(action, p, notes);
|
|
310
|
-
break;
|
|
311
|
-
case "route-events":
|
|
312
|
-
normalizeRoutes(action, p, notes);
|
|
313
|
-
break;
|
|
314
|
-
default:
|
|
315
|
-
break;
|
|
316
|
-
}
|
|
317
|
-
return {
|
|
318
|
-
params: p,
|
|
319
|
-
notes
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/** The one-line trailer appended to a tool result when aliases fired. */
|
|
324
|
-
export function aliasNote(notes) {
|
|
325
|
-
return notes.length ? `\n\n[Buoy] ${notes.join(" ")}` : "";
|
|
326
|
-
}
|
|
327
|
-
//# sourceMappingURL=normalizeParams.js.map
|
|
3
|
+
[Buoy] ${i.join(" ")}`:""}
|
|
@@ -1,63 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Rendering a JSON Schema as the one-line signature a model reads.
|
|
5
|
-
*
|
|
6
|
-
* Lifted out of `blocks/uiTool.ts`, where it was written for `buoy_ui` and
|
|
7
|
-
* where its own comment records the result that justifies using it everywhere:
|
|
8
|
-
*
|
|
9
|
-
* > `uiProviderTool` used to send `params: { type: "object" }` and 26 lines
|
|
10
|
-
* > of prose about what each kind is FOR — and not one field name. The model
|
|
11
|
-
* > was being asked to guess `images` vs `items` and `chips` vs `options`,
|
|
12
|
-
* > and it guessed wrong three rounds running.
|
|
13
|
-
*
|
|
14
|
-
* The other 24 tools still send exactly that: an open `params` object and a
|
|
15
|
-
* sentence promising the error will explain the shape. `normalizeParams.ts`
|
|
16
|
-
* exists to catch the five misses that produced most often. This renders the
|
|
17
|
-
* field names instead, from the SAME schema `validateParams` enforces, so what
|
|
18
|
-
* the model reads can never drift from what the validator accepts.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/** `{ url*, alt?, caption? }` — `*` marks required. */
|
|
22
|
-
export function signature(schema, depth = 0) {
|
|
23
|
-
if (!schema || typeof schema !== "object") return "any";
|
|
24
|
-
const s = schema;
|
|
25
|
-
if (Array.isArray(s.anyOf)) {
|
|
26
|
-
return s.anyOf.map(b => signature(b, depth)).join(" | ");
|
|
27
|
-
}
|
|
28
|
-
if (s.type === "array") {
|
|
29
|
-
const cap = typeof s.maxItems === "number" ? ` ≤${s.maxItems}` : "";
|
|
30
|
-
return `[${signature(s.items, depth + 1)}${cap}]`;
|
|
31
|
-
}
|
|
32
|
-
if (s.type === "object" || s.properties) {
|
|
33
|
-
const props = s.properties ?? {};
|
|
34
|
-
const names = Object.keys(props);
|
|
35
|
-
if (!names.length) return "{…}";
|
|
36
|
-
// Three levels is every field a model has to name; below that lives the
|
|
37
|
-
// action's `dispatch`, which the prose already describes.
|
|
38
|
-
if (depth >= 3) return "{…}";
|
|
39
|
-
const required = new Set(s.required ?? []);
|
|
40
|
-
const fields = names.map(name => {
|
|
41
|
-
const inner = signature(props[name], depth + 1);
|
|
42
|
-
const mark = required.has(name) ? "*" : "";
|
|
43
|
-
return inner === "any" ? `${name}${mark}` : `${name}${mark}: ${inner}`;
|
|
44
|
-
});
|
|
45
|
-
return `{ ${fields.join(", ")} }`;
|
|
46
|
-
}
|
|
47
|
-
if (Array.isArray(s.enum)) return s.enum.map(v => `"${String(v)}"`).join("|");
|
|
48
|
-
return typeof s.type === "string" ? s.type : "any";
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* The `params:` line under an action, or "" when the action takes none.
|
|
53
|
-
*
|
|
54
|
-
* Actions with no declared properties (71 of 192) get nothing rather than an
|
|
55
|
-
* empty `{}` — a line that says the action takes no arguments is already said
|
|
56
|
-
* by its absence, and 71 of them would be pure noise.
|
|
57
|
-
*/
|
|
58
|
-
export function paramsLine(schema) {
|
|
59
|
-
const props = schema?.properties;
|
|
60
|
-
if (!props || Object.keys(props).length === 0) return "";
|
|
61
|
-
return `params: ${signature(schema)}`;
|
|
62
|
-
}
|
|
63
|
-
//# sourceMappingURL=signature.js.map
|
|
1
|
+
"use strict";export function signature(n,e=0){if(!n||typeof n!=="object")return"any";const r=n;if(Array.isArray(r.anyOf)){return r.anyOf.map(t=>signature(t,e)).join(" | ")}if(r.type==="array"){const t=typeof r.maxItems==="number"?` \u2264${r.maxItems}`:"";return`[${signature(r.items,e+1)}${t}]`}if(r.type==="object"||r.properties){const t=r.properties??{};const s=Object.keys(t);if(!s.length)return"{\u2026}";if(e>=3)return"{\u2026}";const u=new Set(r.required??[]);const a=s.map(i=>{const o=signature(t[i],e+1);const p=u.has(i)?"*":"";return o==="any"?`${i}${p}`:`${i}${p}: ${o}`});return`{ ${a.join(", ")} }`}if(Array.isArray(r.enum))return r.enum.map(t=>`"${String(t)}"`).join("|");return typeof r.type==="string"?r.type:"any"}export function paramsLine(n){const e=n?.properties;if(!e||Object.keys(e).length===0)return"";return`params: ${signature(n)}`}
|
|
@@ -1,259 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The other half of Buoy's read surface.
|
|
5
|
-
*
|
|
6
|
-
* Every Buoy adapter answers in two ways, and only one of them is an action.
|
|
7
|
-
* `listQueries`, `async.getAllKeys` and `getState` are actions; console
|
|
8
|
-
* entries, network events, the route sitemap and the env table are NOT — they
|
|
9
|
-
* exist only inside the adapter's `getSnapshot`, which the sync layer polls and
|
|
10
|
-
* puts on the wire for MCP's `get_snapshot` / `get_console` /
|
|
11
|
-
* `get_network_requests` / `get_routes`.
|
|
12
|
-
*
|
|
13
|
-
* `dispatchToolAction` reaches only the first half. So an agent driving tools
|
|
14
|
-
* on the device could read storage, stores and the query cache, and was blind
|
|
15
|
-
* to the app's logs, its HTTP traffic, its routes and its environment — the
|
|
16
|
-
* four things a QA person is most likely to ask about, and two of which the
|
|
17
|
-
* empty state advertises as suggestions. The catalog even tells the model to
|
|
18
|
-
* "use get_console", which is an MCP tool that does not exist on this path.
|
|
19
|
-
*
|
|
20
|
-
* This module closes that half. `getSnapshot` is a RESERVED action name: the
|
|
21
|
-
* engine intercepts it before dispatch and routes it to the registry's
|
|
22
|
-
* snapshot reader instead.
|
|
23
|
-
*
|
|
24
|
-
* WHY THE PROJECTIONS. A raw snapshot is the heaviest payload in the system —
|
|
25
|
-
* measured at ~1.1KB per console entry with up to 1000 retained, and network
|
|
26
|
-
* events carry bodies. Handing that to a token-metered model truncated at 24K
|
|
27
|
-
* chars produces a torn JSON object and a wasted turn. Each projector here
|
|
28
|
-
* does what the matching MCP tool does: newest-first, filtered, and only the
|
|
29
|
-
* fields worth spending tokens on. Bodies stay behind `network.getEventBody`,
|
|
30
|
-
* which already exists.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
import { isSelfTraffic } from "../policy/redact";
|
|
34
|
-
|
|
35
|
-
/** Reserved action name. Not a real adapter action anywhere. */
|
|
36
|
-
export const SNAPSHOT_ACTION = "getSnapshot";
|
|
37
|
-
const SEVERITY = {
|
|
38
|
-
verbose: 0,
|
|
39
|
-
debug: 0,
|
|
40
|
-
trace: 0,
|
|
41
|
-
log: 1,
|
|
42
|
-
info: 1,
|
|
43
|
-
warn: 2,
|
|
44
|
-
warning: 2,
|
|
45
|
-
error: 3
|
|
46
|
-
};
|
|
47
|
-
function asArray(value) {
|
|
48
|
-
return Array.isArray(value) ? value : [];
|
|
49
|
-
}
|
|
50
|
-
function rec(value) {
|
|
51
|
-
return value && typeof value === "object" ? value : {};
|
|
52
|
-
}
|
|
53
|
-
function num(value, fallback) {
|
|
54
|
-
return typeof value === "number" && value > 0 ? value : fallback;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* The newest N. Every Buoy event store (BaseEventStore, which network,
|
|
59
|
-
* console, sentry and route events all sit on) PREPENDS, so the list arrives
|
|
60
|
-
* newest-FIRST and the newest N are the first N. An earlier version took the
|
|
61
|
-
* LAST N here — the oldest — so once more than N were captured, RIGHT NOW
|
|
62
|
-
* showed the stalest requests and a crash that had just happened fell out of
|
|
63
|
-
* the console projection. Order is preserved: newest first.
|
|
64
|
-
*/
|
|
65
|
-
function newest(items, limit) {
|
|
66
|
-
return items.length <= limit ? items : items.slice(0, limit);
|
|
67
|
-
}
|
|
68
|
-
function projectConsole(payload, params) {
|
|
69
|
-
const entries = asArray(rec(payload).entries);
|
|
70
|
-
const minSeverity = typeof params.level === "string" ? SEVERITY[params.level] ?? 0 : 0;
|
|
71
|
-
const pattern = typeof params.pattern === "string" ? params.pattern.toLowerCase() : undefined;
|
|
72
|
-
const matched = entries.filter(raw => {
|
|
73
|
-
const e = rec(raw);
|
|
74
|
-
const level = String(e.level ?? "log");
|
|
75
|
-
if ((SEVERITY[level] ?? 1) < minSeverity) return false;
|
|
76
|
-
if (!pattern) return true;
|
|
77
|
-
return String(e.message ?? "").toLowerCase().includes(pattern);
|
|
78
|
-
});
|
|
79
|
-
const shown = newest(matched, num(params.limit, 40));
|
|
80
|
-
return {
|
|
81
|
-
totalCaptured: entries.length,
|
|
82
|
-
shown: shown.length,
|
|
83
|
-
// Pre-rendered message only. Full args and stacks stay on the device;
|
|
84
|
-
// `get_snapshot('console')` is the MCP escape hatch and has no equivalent
|
|
85
|
-
// here on purpose — it is the payload that blows the prompt.
|
|
86
|
-
entries: shown.map(raw => {
|
|
87
|
-
const e = rec(raw);
|
|
88
|
-
return {
|
|
89
|
-
at: e.timestamp,
|
|
90
|
-
level: e.level,
|
|
91
|
-
message: e.message
|
|
92
|
-
};
|
|
93
|
-
})
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
function projectNetwork(payload, params) {
|
|
97
|
-
const p = rec(payload);
|
|
98
|
-
// The agent's own POSTs to the model endpoint are dropped HERE, not only by
|
|
99
|
-
// the engine's later pass, so the counts the model is given match the rows
|
|
100
|
-
// it is given. Reporting "4 requests" above a list of 3 is its own small lie.
|
|
101
|
-
const events = asArray(p.events).filter(raw => !isSelfTraffic(rec(raw).url));
|
|
102
|
-
const failedOnly = params.failedOnly === true;
|
|
103
|
-
const pattern = typeof params.pattern === "string" ? params.pattern.toLowerCase() : undefined;
|
|
104
|
-
const matched = events.filter(raw => {
|
|
105
|
-
const e = rec(raw);
|
|
106
|
-
const status = typeof e.status === "number" ? e.status : undefined;
|
|
107
|
-
if (failedOnly && !(e.error || status !== undefined && status >= 400)) return false;
|
|
108
|
-
if (!pattern) return true;
|
|
109
|
-
return String(e.url ?? "").toLowerCase().includes(pattern);
|
|
110
|
-
});
|
|
111
|
-
const shown = newest(matched, num(params.limit, 25));
|
|
112
|
-
return {
|
|
113
|
-
totalCaptured: events.length,
|
|
114
|
-
shown: shown.length,
|
|
115
|
-
// No bodies. `network.getEventBody` fetches one on demand, which is the
|
|
116
|
-
// difference between a readable list and a truncated blob.
|
|
117
|
-
requests: shown.map(raw => {
|
|
118
|
-
const e = rec(raw);
|
|
119
|
-
return {
|
|
120
|
-
id: e.id,
|
|
121
|
-
method: e.method,
|
|
122
|
-
url: e.url,
|
|
123
|
-
status: e.status,
|
|
124
|
-
durationMs: e.duration,
|
|
125
|
-
error: e.error,
|
|
126
|
-
// An override rule served this response. Carried through so neither
|
|
127
|
-
// the model nor the receipt can present mocked data as live.
|
|
128
|
-
overridden: e.override ? true : undefined
|
|
129
|
-
};
|
|
130
|
-
}),
|
|
131
|
-
hint: "Read one response with network.getEventBody({ id })."
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* The sitemap arrives as a NESTED RouteInfo tree, not a list. A model asked
|
|
137
|
-
* "what screens exist" needs the leaf paths, so flatten and keep the paths.
|
|
138
|
-
*/
|
|
139
|
-
function flattenRoutes(nodes, out = [], cap = 80) {
|
|
140
|
-
for (const raw of asArray(nodes)) {
|
|
141
|
-
if (out.length >= cap) break;
|
|
142
|
-
const node = rec(raw);
|
|
143
|
-
if (node.isInternal !== true && typeof node.path === "string") out.push(node.path);
|
|
144
|
-
flattenRoutes(node.children, out, cap);
|
|
145
|
-
}
|
|
146
|
-
return out;
|
|
147
|
-
}
|
|
148
|
-
function projectRoutes(payload, params) {
|
|
149
|
-
const p = rec(payload);
|
|
150
|
-
const sitemap = rec(p.sitemap);
|
|
151
|
-
|
|
152
|
-
// Route events are stored newest-FIRST, and a stack item's screen path is
|
|
153
|
-
// `pathname` (`path` does not exist on it). Getting either wrong is not a
|
|
154
|
-
// crash, it is a confident wrong answer: an early version of this read told
|
|
155
|
-
// the user they were on the Pokedex while the shop was on screen.
|
|
156
|
-
const stackRaw = asArray(p.stack).map(rec);
|
|
157
|
-
const eventsRaw = asArray(p.events).map(rec);
|
|
158
|
-
const stack = stackRaw.map(s => ({
|
|
159
|
-
name: s.name,
|
|
160
|
-
path: s.pathname,
|
|
161
|
-
focused: s.isFocused === true ? true : undefined
|
|
162
|
-
}));
|
|
163
|
-
const recentNavigations = eventsRaw.slice(0, num(params.limit, 15)).map(e => ({
|
|
164
|
-
at: e.timestamp,
|
|
165
|
-
from: e.previousPathname,
|
|
166
|
-
to: e.pathname
|
|
167
|
-
}));
|
|
168
|
-
|
|
169
|
-
// Mirrors the adapter's own `getCurrentRoute`: the newest navigation event
|
|
170
|
-
// wins, and with no history yet — a freshly launched app — the FOCUSED stack
|
|
171
|
-
// item does, which is not always the last one in the array.
|
|
172
|
-
const focused = stackRaw.find(s => s.isFocused === true) ?? stackRaw[stackRaw.length - 1];
|
|
173
|
-
const currentRoute = eventsRaw.length ? {
|
|
174
|
-
path: eventsRaw[0].pathname,
|
|
175
|
-
source: "event"
|
|
176
|
-
} : typeof focused?.pathname === "string" ? {
|
|
177
|
-
path: focused.pathname,
|
|
178
|
-
source: "stack"
|
|
179
|
-
} : undefined;
|
|
180
|
-
return {
|
|
181
|
-
currentRoute,
|
|
182
|
-
stack,
|
|
183
|
-
routes: flattenRoutes(sitemap.routes),
|
|
184
|
-
sitemapSource: sitemap.source,
|
|
185
|
-
recentNavigations
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
function projectEnv(payload) {
|
|
189
|
-
const p = rec(payload);
|
|
190
|
-
return {
|
|
191
|
-
env: p.env ?? p.variables,
|
|
192
|
-
requiredEnvVars: p.requiredEnvVars
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Sentry envelopes are the fattest payloads any Buoy tool captures (a single
|
|
198
|
-
* event with breadcrumbs is tens of KB), so only the item SUMMARIES travel —
|
|
199
|
-
* the one-line human read the adapter already computes. The status field is
|
|
200
|
-
* load-bearing: an empty list with "sdk-not-found" means the app has no
|
|
201
|
-
* Sentry client, which is a different answer from "nothing errored".
|
|
202
|
-
*/
|
|
203
|
-
function projectSentry(payload, params) {
|
|
204
|
-
const p = rec(payload);
|
|
205
|
-
const envelopes = asArray(p.envelopes).map(rec);
|
|
206
|
-
const typeFilter = typeof params.type === "string" ? params.type : undefined;
|
|
207
|
-
const pattern = typeof params.pattern === "string" ? params.pattern.toLowerCase() : undefined;
|
|
208
|
-
const matched = envelopes.filter(e => {
|
|
209
|
-
const items = asArray(e.items).map(rec);
|
|
210
|
-
if (typeFilter && !items.some(i => i.type === typeFilter)) return false;
|
|
211
|
-
if (!pattern) return true;
|
|
212
|
-
return items.some(i => String(i.summary ?? "").toLowerCase().includes(pattern));
|
|
213
|
-
});
|
|
214
|
-
const shown = newest(matched, num(params.limit, 10));
|
|
215
|
-
return {
|
|
216
|
-
status: p.status,
|
|
217
|
-
totalCaptured: envelopes.length,
|
|
218
|
-
shown: shown.length,
|
|
219
|
-
envelopes: shown.map(e => ({
|
|
220
|
-
id: e.id,
|
|
221
|
-
at: e.timestamp,
|
|
222
|
-
origin: e.origin,
|
|
223
|
-
eventId: e.eventId,
|
|
224
|
-
totalBytes: e.totalBytes,
|
|
225
|
-
items: asArray(e.items).map(raw => {
|
|
226
|
-
const i = rec(raw);
|
|
227
|
-
return {
|
|
228
|
-
type: i.type,
|
|
229
|
-
summary: i.summary,
|
|
230
|
-
spanCount: i.spanCount,
|
|
231
|
-
bytes: i.bytes
|
|
232
|
-
};
|
|
233
|
-
})
|
|
234
|
-
})),
|
|
235
|
-
hint: "For crash stack traces, console's [FATAL] entries carry the component stacks."
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Trim one snapshot to something worth sending. Unknown tools pass through —
|
|
241
|
-
* the engine's 24K truncation is still the backstop.
|
|
242
|
-
*/
|
|
243
|
-
export function projectSnapshot(toolId, payload, params = {}) {
|
|
244
|
-
switch (toolId) {
|
|
245
|
-
case "console":
|
|
246
|
-
return projectConsole(payload, params);
|
|
247
|
-
case "network":
|
|
248
|
-
return projectNetwork(payload, params);
|
|
249
|
-
case "route-events":
|
|
250
|
-
return projectRoutes(payload, params);
|
|
251
|
-
case "env":
|
|
252
|
-
return projectEnv(payload);
|
|
253
|
-
case "sentry":
|
|
254
|
-
return projectSentry(payload, params);
|
|
255
|
-
default:
|
|
256
|
-
return payload;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
//# sourceMappingURL=snapshotReads.js.map
|
|
1
|
+
"use strict";import{isSelfTraffic as y}from"../policy/redact";export const SNAPSHOT_ACTION="getSnapshot";const m={verbose:0,debug:0,trace:0,log:1,info:1,warn:2,warning:2,error:3};function l(e){return Array.isArray(e)?e:[]}function u(e){return e&&typeof e==="object"?e:{}}function d(e,t){return typeof e==="number"&&e>0?e:t}function h(e,t){return e.length<=t?e:e.slice(0,t)}function v(e,t){const o=l(u(e).entries);const c=typeof t.level==="string"?m[t.level]??0:0;const i=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const a=o.filter(f=>{const n=u(f);const r=String(n.level??"log");if((m[r]??1)<c)return false;if(!i)return true;return String(n.message??"").toLowerCase().includes(i)});const p=h(a,d(t.limit,40));return{totalCaptured:o.length,shown:p.length,entries:p.map(f=>{const n=u(f);return{at:n.timestamp,level:n.level,message:n.message}})}}function w(e,t){const o=u(e);const c=l(o.events).filter(n=>!y(u(n).url));const i=t.failedOnly===true;const a=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const p=c.filter(n=>{const r=u(n);const s=typeof r.status==="number"?r.status:void 0;if(i&&!(r.error||s!==void 0&&s>=400))return false;if(!a)return true;return String(r.url??"").toLowerCase().includes(a)});const f=h(p,d(t.limit,25));return{totalCaptured:c.length,shown:f.length,requests:f.map(n=>{const r=u(n);return{id:r.id,method:r.method,url:r.url,status:r.status,durationMs:r.duration,error:r.error,overridden:r.override?true:void 0}}),hint:"Read one response with network.getEventBody({ id })."}}function g(e,t=[],o=80){for(const c of l(e)){if(t.length>=o)break;const i=u(c);if(i.isInternal!==true&&typeof i.path==="string")t.push(i.path);g(i.children,t,o)}return t}function C(e,t){const o=u(e);const c=u(o.sitemap);const i=l(o.stack).map(u);const a=l(o.events).map(u);const p=i.map(s=>({name:s.name,path:s.pathname,focused:s.isFocused===true?true:void 0}));const f=a.slice(0,d(t.limit,15)).map(s=>({at:s.timestamp,from:s.previousPathname,to:s.pathname}));const n=i.find(s=>s.isFocused===true)??i[i.length-1];const r=a.length?{path:a[0].pathname,source:"event"}:typeof n?.pathname==="string"?{path:n.pathname,source:"stack"}:void 0;return{currentRoute:r,stack:p,routes:g(c.routes),sitemapSource:c.source,recentNavigations:f}}function S(e){const t=u(e);return{env:t.env??t.variables,requiredEnvVars:t.requiredEnvVars}}function k(e,t){const o=u(e);const c=l(o.envelopes).map(u);const i=typeof t.type==="string"?t.type:void 0;const a=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const p=c.filter(n=>{const r=l(n.items).map(u);if(i&&!r.some(s=>s.type===i))return false;if(!a)return true;return r.some(s=>String(s.summary??"").toLowerCase().includes(a))});const f=h(p,d(t.limit,10));return{status:o.status,totalCaptured:c.length,shown:f.length,envelopes:f.map(n=>({id:n.id,at:n.timestamp,origin:n.origin,eventId:n.eventId,totalBytes:n.totalBytes,items:l(n.items).map(r=>{const s=u(r);return{type:s.type,summary:s.summary,spanCount:s.spanCount,bytes:s.bytes}})})),hint:"For crash stack traces, console's [FATAL] entries carry the component stacks."}}export function projectSnapshot(e,t,o={}){switch(e){case"console":return v(t,o);case"network":return w(t,o);case"route-events":return C(t,o);case"env":return S(t);case"sentry":return k(t,o);default:return t}}
|