@buoy-gg/agent-core 7.0.62 → 7.0.64
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 -1
- package/lib/commonjs/catalog/catalog.g.js +2 -2
- package/lib/commonjs/catalog/catalog.source.json +85 -1
- package/lib/commonjs/catalog/catalog.types.g.js +1 -1
- package/lib/commonjs/catalog/snapshotReads.js +1 -1
- package/lib/commonjs/context/buildContextPack.js +1 -1
- package/lib/commonjs/engine/emptyTap.test.js +1 -0
- package/lib/commonjs/engine/historyBudget.js +1 -1
- package/lib/commonjs/engine/idGrounding.js +2 -0
- package/lib/commonjs/engine/planOnlyStop.js +1 -1
- package/lib/commonjs/engine/runAgentTurn.js +9 -8
- package/lib/commonjs/engine/stepBudget.test.js +1 -0
- package/lib/commonjs/engine/systemPrompt.js +5 -2
- package/lib/commonjs/engine/verify.js +2 -2
- package/lib/commonjs/feeds/claude.js +23 -0
- package/lib/commonjs/feeds/gpt.js +5 -0
- package/lib/commonjs/feeds/index.js +1 -0
- package/lib/commonjs/policy/policy.js +1 -1
- package/lib/commonjs/policy/redact.js +1 -1
- package/lib/commonjs/policy/turnScope.js +1 -0
- package/lib/commonjs/providers/anthropic.js +1 -5
- package/lib/commonjs/providers/openai.js +1 -5
- package/lib/commonjs/providers/types.js +1 -1
- package/lib/commonjs/session.js +1 -1
- package/lib/module/blocks/receipts.js +1 -1
- package/lib/module/catalog/catalog.g.js +2 -2
- package/lib/module/catalog/catalog.source.json +85 -1
- package/lib/module/catalog/catalog.types.g.js +1 -1
- package/lib/module/catalog/snapshotReads.js +1 -1
- package/lib/module/context/buildContextPack.js +1 -1
- package/lib/module/engine/emptyTap.test.js +1 -0
- package/lib/module/engine/historyBudget.js +1 -1
- package/lib/module/engine/idGrounding.js +2 -0
- package/lib/module/engine/planOnlyStop.js +1 -1
- package/lib/module/engine/runAgentTurn.js +9 -8
- package/lib/module/engine/stepBudget.test.js +1 -0
- package/lib/module/engine/systemPrompt.js +5 -2
- package/lib/module/engine/verify.js +2 -2
- package/lib/module/feeds/claude.js +23 -0
- package/lib/module/feeds/gpt.js +5 -0
- package/lib/module/feeds/index.js +1 -0
- package/lib/module/policy/policy.js +1 -1
- package/lib/module/policy/redact.js +1 -1
- package/lib/module/policy/turnScope.js +1 -0
- package/lib/module/providers/anthropic.js +1 -5
- package/lib/module/providers/openai.js +1 -5
- package/lib/module/providers/types.js +1 -1
- package/lib/module/session.js +1 -1
- package/lib/typescript/catalog/catalog.g.d.ts +3 -3
- package/lib/typescript/catalog/catalog.types.g.d.ts +4 -3
- package/lib/typescript/engine/emptyTap.test.d.ts +2 -0
- package/lib/typescript/engine/historyBudget.d.ts +1 -1
- package/lib/typescript/engine/idGrounding.d.ts +8 -0
- package/lib/typescript/engine/planOnlyStop.d.ts +2 -2
- package/lib/typescript/engine/runAgentTurn.d.ts +2 -0
- package/lib/typescript/engine/stepBudget.test.d.ts +2 -0
- package/lib/typescript/engine/systemPrompt.d.ts +1 -1
- package/lib/typescript/engine/verify.d.ts +2 -1
- package/lib/typescript/feeds/claude.d.ts +67 -0
- package/lib/typescript/feeds/gpt.d.ts +41 -0
- package/lib/typescript/feeds/index.d.ts +27 -0
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/policy/policy.d.ts +8 -3
- package/lib/typescript/policy/redact.d.ts +1 -0
- package/lib/typescript/policy/turnScope.d.ts +13 -0
- package/lib/typescript/providers/anthropic.d.ts +4 -0
- package/lib/typescript/providers/openai.d.ts +29 -0
- package/lib/typescript/providers/types.d.ts +32 -1
- package/lib/web/index.mjs +77 -38
- package/package.json +1 -1
|
@@ -831,8 +831,20 @@
|
|
|
831
831
|
{
|
|
832
832
|
"toolId": "impersonate",
|
|
833
833
|
"title": "Impersonate",
|
|
834
|
-
"summary": "Become another user inside the running app without logging out: it injects an impersonation header (default `x-impersonate-user-id: <user.id>`) into every outgoing globalThis.fetch and XMLHttpRequest, so the backend returns that user's data. Reach for it to reproduce a specific customer's bug (\"show me what account 8812 sees\"), then stop/pause to return to the real login.
|
|
834
|
+
"summary": "Become another user inside the running app without logging out: it injects an impersonation header (default `x-impersonate-user-id: <user.id>`) into every outgoing globalThis.fetch and XMLHttpRequest, so the backend returns that user's data. Reach for it to reproduce a specific customer's bug (\"show me what account 8812 sees\"), then stop/pause to return to the real login. Read impersonate.getSnapshot to check the switch. It shows isActive, isPaused, currentUser, and history. Note that switching or stopping also CLEARS app caches per dataNukeSettings (react-query + redux on by default), so it is not a passive read-only view.",
|
|
835
835
|
"actions": [
|
|
836
|
+
{
|
|
837
|
+
"action": "getSnapshot",
|
|
838
|
+
"summary": "Read the user, pause state, and past users. Read settings too.",
|
|
839
|
+
"description": "Returns isActive, isPaused, currentUser, headerKey, ignorePatterns, dataNukeSettings, showBanner, and history. Users have id, displayName, and email. Each history row has user and lastUsedAt. Settings show what should clear, not proof it cleared.",
|
|
840
|
+
"params": {
|
|
841
|
+
"type": "object",
|
|
842
|
+
"properties": {},
|
|
843
|
+
"additionalProperties": false
|
|
844
|
+
},
|
|
845
|
+
"effect": "read",
|
|
846
|
+
"release": "works"
|
|
847
|
+
},
|
|
836
848
|
{
|
|
837
849
|
"action": "searchUsers",
|
|
838
850
|
"summary": "Search the app's own user directory and get back User objects you can feed to startImpersonation.",
|
|
@@ -6748,5 +6760,77 @@
|
|
|
6748
6760
|
}
|
|
6749
6761
|
],
|
|
6750
6762
|
"unavailableWhen": "The native Swift image-overlay adapter is not registered. React Native does not expose these actions."
|
|
6763
|
+
},
|
|
6764
|
+
{
|
|
6765
|
+
"toolId": "three",
|
|
6766
|
+
"title": "Scene",
|
|
6767
|
+
"summary": "Read and edit the 3D scene. Counts are not bytes.",
|
|
6768
|
+
"unavailableWhen": "Needs @buoy-gg/three and a scene in the app.",
|
|
6769
|
+
"actions": [
|
|
6770
|
+
{
|
|
6771
|
+
"action": "getScene",
|
|
6772
|
+
"summary": "Read up to 500 nodes per scene.",
|
|
6773
|
+
"description": "Read up to 500 nodes per scene.",
|
|
6774
|
+
"effect": "read",
|
|
6775
|
+
"release": "works",
|
|
6776
|
+
"params": {
|
|
6777
|
+
"type": "object",
|
|
6778
|
+
"properties": {},
|
|
6779
|
+
"additionalProperties": false
|
|
6780
|
+
}
|
|
6781
|
+
},
|
|
6782
|
+
{
|
|
6783
|
+
"action": "getStats",
|
|
6784
|
+
"summary": "Read the last render counts.",
|
|
6785
|
+
"description": "Read the last render counts.",
|
|
6786
|
+
"effect": "read",
|
|
6787
|
+
"release": "works",
|
|
6788
|
+
"params": {
|
|
6789
|
+
"type": "object",
|
|
6790
|
+
"properties": {},
|
|
6791
|
+
"additionalProperties": false
|
|
6792
|
+
}
|
|
6793
|
+
},
|
|
6794
|
+
{
|
|
6795
|
+
"action": "action",
|
|
6796
|
+
"summary": "Set rendererId and id from getScene. Choose an action. Pass value for edits.",
|
|
6797
|
+
"description": "Use node and scene IDs from getScene. Turn values use radians. Colors use six digit hex.",
|
|
6798
|
+
"effect": "write",
|
|
6799
|
+
"release": "works",
|
|
6800
|
+
"params": {
|
|
6801
|
+
"type": "object",
|
|
6802
|
+
"properties": {
|
|
6803
|
+
"rendererId": {
|
|
6804
|
+
"type": "string",
|
|
6805
|
+
"description": "Scene group ID."
|
|
6806
|
+
},
|
|
6807
|
+
"id": {
|
|
6808
|
+
"type": "string",
|
|
6809
|
+
"description": "Node ID. Omit for clearSelection."
|
|
6810
|
+
},
|
|
6811
|
+
"action": {
|
|
6812
|
+
"type": "string",
|
|
6813
|
+
"enum": [
|
|
6814
|
+
"select",
|
|
6815
|
+
"clearSelection",
|
|
6816
|
+
"visible",
|
|
6817
|
+
"position",
|
|
6818
|
+
"rotation",
|
|
6819
|
+
"scale",
|
|
6820
|
+
"color"
|
|
6821
|
+
]
|
|
6822
|
+
},
|
|
6823
|
+
"value": {
|
|
6824
|
+
"description": "Boolean for visible. Three numbers for a transform. Hex string for color."
|
|
6825
|
+
}
|
|
6826
|
+
},
|
|
6827
|
+
"required": [
|
|
6828
|
+
"rendererId",
|
|
6829
|
+
"action"
|
|
6830
|
+
],
|
|
6831
|
+
"additionalProperties": false
|
|
6832
|
+
}
|
|
6833
|
+
}
|
|
6834
|
+
]
|
|
6751
6835
|
}
|
|
6752
6836
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";export const CATALOG_ACTIONS={"env":["getSnapshot"],"console":["getSnapshot","clearEntries"],"sentry":["getSnapshot","clearEnvelopes"],"jotai":["getSnapshot","listAtoms","getAtomValue","getChangeDetail","setAtom","clearEvents"],"route-events":["getSnapshot","getCurrentRoute","navigate","stackGoBack","stackNavigateToIndex","stackPopToIndex","stackPopToTop","clearEvents"],"debug-borders":["cycleMode","setMode"],"zustand":["getSnapshot","listStores","getStoreState","getChangeDetail","setState","rehydrate","clearEvents"],"redux":["getSnapshot","getState","getActionDetail","dispatch","setState","clearEvents"],"impersonate":["searchUsers","startImpersonation","stopImpersonation","pauseImpersonation","resumeImpersonation","updateSettings","removeFromHistory","clearHistory"],"query":["listQueries","getQueryData","refetch","invalidate","reset","remove","setQueryData","triggerError","restoreError","triggerLoading","restoreLoading","clearQueryCache","clearMutationCache","setOnline"],"events":["exportEvents","setEnabledSources","clearEvents"],"network":["getSnapshot","getCaptureStatus","getEventBody","setPinned","setSaved","removeSavedRecord","clearSavedRequests","clearPinnedRequests","clearEvents","listOverrideRules","getOverrideRuleBody","debugOverrides","upsertOverrideRule","setOverrideRuleEnabled","setOverridesEnabled","deleteOverrideRule","clearOverrideRules","getNetworkConditions","setNetworkConditions"],"js-top":["sample","getOriginDetail","setEnabled","pause","resume","clear"],"app":["ping","reloadApp"],"time-machine":["list","capture","captureBaseline","wipeAll","restore","preview","inspect","delete","rename","duplicate","setExclusions","setScope","setRestoreRoute"],"clock":["getState","setTime","shift","freeze","resume","setRate","reset","updateSettings","jumpToTokenExpiry","failNextRequest","stopTokenCheck"],"lifecycle":["getState","background","interrupt","returnToApp","memoryWarning","openUrl","pressBack","setColorScheme","setPower","resetPower","relaunch","runRecipe","clearReport","reset"],"location":["getState","setLocation","startRoute","pauseRoute","resumeRoute","stopRoute","seekRoute","setSpeed","enterRegion","exitRegion","setConditions","setSignal","setServices","tune","useRealLocation","reset","updateSettings","clearLog"],"permissions":["getState","setOverride","clearOverride","resetAll","requestReal","updateSettings","simulateReturnFromSettings","clearLog"],"storage":["getSnapshot","getRequiredKeys","async.getAllKeys","async.multiGet","async.getItem","async.setItem","async.removeItem","async.multiRemove","async.multiSet","async.clear","clearAppStorage","getEventDetail","clearEvents","timeTravel.undo","timeTravel.jump","mmkv.snapshot","mmkv.get","mmkv.set","mmkv.remove","secure.keys","secure.snapshot","secure.get","secure.set","secure.delete"],"highlight-updates":["describeScreen","tapElement","waitFor","beginMeasurement","endMeasurement","locateComponent","getRenderDetail","setEnabled","toggle","setSilentTracking","toggleFreeze","setSpotlight","clearRenderCounts","startTouchCapture","stopTouchCapture","clearTouchCapture","readTouchCapture"],"scenarios":["listScenarios","getScenario","save","acceptDraft","discardDraft","preview","run","deactivate","delete","setFolder","listFolders","getActive","export"],"perf-monitor":["setEnabled","startRecording","stopRecording","savePending","discardPending","mark","startAutomation","cancelAutomation","acknowledgeAutomation","refreshIndex","getAutomationConfig","setAutomationConfig","loadReport","deleteReport","deleteBatch","clearAll"],"assets":["list","getDetail","rescan","measureSizes","saveBaseline","clearBaseline","getDiff","clearRecords","getScanStatus"],"tv-remote":["arm","getEventsSince","setMenuCapture","disarm","clear"],"focus-inspector":["rescan","focusElement","setTracking","clearHistory"],"images":["list","getDetail","getCaptureStatus","retry","flash","proveSavings","setOverride","clearOverride","setNetworkMode","setBlankImages","hardReload","evictDisk","massAction","clearRecords","clearExpoCaches"],"ask-buoy":["listChanges","undoChange","undoAll","retrieve","openProcedure"],"push-notifications":["getSnapshot","getCapabilities","getEvent","getPermissions","listPresented","refreshToken","setCaptureSession","clearCapturedEvents","scheduleLocal"],"image-overlay":["getSnapshot","listTargets","selectTarget","loadImage","setSettings","fitToScreen","resetSettings","remove"]};
|
|
1
|
+
"use strict";export const CATALOG_ACTIONS={"env":["getSnapshot"],"console":["getSnapshot","clearEntries"],"sentry":["getSnapshot","clearEnvelopes"],"jotai":["getSnapshot","listAtoms","getAtomValue","getChangeDetail","setAtom","clearEvents"],"route-events":["getSnapshot","getCurrentRoute","navigate","stackGoBack","stackNavigateToIndex","stackPopToIndex","stackPopToTop","clearEvents"],"debug-borders":["cycleMode","setMode"],"zustand":["getSnapshot","listStores","getStoreState","getChangeDetail","setState","rehydrate","clearEvents"],"redux":["getSnapshot","getState","getActionDetail","dispatch","setState","clearEvents"],"impersonate":["getSnapshot","searchUsers","startImpersonation","stopImpersonation","pauseImpersonation","resumeImpersonation","updateSettings","removeFromHistory","clearHistory"],"query":["listQueries","getQueryData","refetch","invalidate","reset","remove","setQueryData","triggerError","restoreError","triggerLoading","restoreLoading","clearQueryCache","clearMutationCache","setOnline"],"events":["exportEvents","setEnabledSources","clearEvents"],"network":["getSnapshot","getCaptureStatus","getEventBody","setPinned","setSaved","removeSavedRecord","clearSavedRequests","clearPinnedRequests","clearEvents","listOverrideRules","getOverrideRuleBody","debugOverrides","upsertOverrideRule","setOverrideRuleEnabled","setOverridesEnabled","deleteOverrideRule","clearOverrideRules","getNetworkConditions","setNetworkConditions"],"js-top":["sample","getOriginDetail","setEnabled","pause","resume","clear"],"app":["ping","reloadApp"],"time-machine":["list","capture","captureBaseline","wipeAll","restore","preview","inspect","delete","rename","duplicate","setExclusions","setScope","setRestoreRoute"],"clock":["getState","setTime","shift","freeze","resume","setRate","reset","updateSettings","jumpToTokenExpiry","failNextRequest","stopTokenCheck"],"lifecycle":["getState","background","interrupt","returnToApp","memoryWarning","openUrl","pressBack","setColorScheme","setPower","resetPower","relaunch","runRecipe","clearReport","reset"],"location":["getState","setLocation","startRoute","pauseRoute","resumeRoute","stopRoute","seekRoute","setSpeed","enterRegion","exitRegion","setConditions","setSignal","setServices","tune","useRealLocation","reset","updateSettings","clearLog"],"permissions":["getState","setOverride","clearOverride","resetAll","requestReal","updateSettings","simulateReturnFromSettings","clearLog"],"storage":["getSnapshot","getRequiredKeys","async.getAllKeys","async.multiGet","async.getItem","async.setItem","async.removeItem","async.multiRemove","async.multiSet","async.clear","clearAppStorage","getEventDetail","clearEvents","timeTravel.undo","timeTravel.jump","mmkv.snapshot","mmkv.get","mmkv.set","mmkv.remove","secure.keys","secure.snapshot","secure.get","secure.set","secure.delete"],"highlight-updates":["describeScreen","tapElement","waitFor","beginMeasurement","endMeasurement","locateComponent","getRenderDetail","setEnabled","toggle","setSilentTracking","toggleFreeze","setSpotlight","clearRenderCounts","startTouchCapture","stopTouchCapture","clearTouchCapture","readTouchCapture"],"scenarios":["listScenarios","getScenario","save","acceptDraft","discardDraft","preview","run","deactivate","delete","setFolder","listFolders","getActive","export"],"perf-monitor":["setEnabled","startRecording","stopRecording","savePending","discardPending","mark","startAutomation","cancelAutomation","acknowledgeAutomation","refreshIndex","getAutomationConfig","setAutomationConfig","loadReport","deleteReport","deleteBatch","clearAll"],"assets":["list","getDetail","rescan","measureSizes","saveBaseline","clearBaseline","getDiff","clearRecords","getScanStatus"],"tv-remote":["arm","getEventsSince","setMenuCapture","disarm","clear"],"focus-inspector":["rescan","focusElement","setTracking","clearHistory"],"images":["list","getDetail","getCaptureStatus","retry","flash","proveSavings","setOverride","clearOverride","setNetworkMode","setBlankImages","hardReload","evictDisk","massAction","clearRecords","clearExpoCaches"],"ask-buoy":["listChanges","undoChange","undoAll","retrieve","openProcedure"],"push-notifications":["getSnapshot","getCapabilities","getEvent","getPermissions","listPresented","refreshToken","setCaptureSession","clearCapturedEvents","scheduleLocal"],"image-overlay":["getSnapshot","listTargets","selectTarget","loadImage","setSettings","fitToScreen","resetSettings","remove"],"three":["getScene","getStats","action"]};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{isSelfTraffic as
|
|
1
|
+
"use strict";import{isSecretKey as L,isSelfTraffic as A,redact as O}from"../policy/redact";export const SNAPSHOT_ACTION="getSnapshot";const C={verbose:0,debug:0,trace:0,log:1,info:1,warn:2,warning:2,error:3};function p(a){return Array.isArray(a)?a:[]}function d(a){return a&&typeof a==="object"?a:{}}function w(a,t){return typeof a==="number"&&a>0?a:t}function v(a,t){const s=o=>{const r=d(o).timestamp;const u=typeof r==="number"?r:typeof r==="string"?Date.parse(r):NaN;return Number.isFinite(u)?u:0};return[...a].sort((o,r)=>s(r)-s(o)).slice(0,t)}function k(a,t){const s=[];for(const o of a){const r=JSON.stringify(o).length+1;if(r>t)continue;s.push(o);t-=r}return s}function P(a,t){const s=p(d(a).entries);const o=typeof t.level==="string"?C[t.level]??0:0;const r=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const u=s.filter(l=>{const e=d(l);const i=String(e.level??"log");if((C[i]??1)<o)return false;if(!r)return true;return String(e.message??"").toLowerCase().includes(r)});const n=v(u,w(t.limit,40));return{totalCaptured:s.length,shown:n.length,entries:n.map(l=>{const e=d(l);return{at:e.timestamp,level:e.level,message:e.message}})}}function R(a,t){const s=d(a);const o=p(s.events).filter(e=>!A(d(e).url));const r=t.failedOnly===true;const u=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const n=o.filter(e=>{const i=d(e);const c=typeof i.status==="number"?i.status:void 0;if(r&&!(i.error||c!==void 0&&c>=400))return false;if(!u)return true;return String(i.url??"").toLowerCase().includes(u)});const l=v(n,w(t.limit,25));return{totalCaptured:o.length,shown:l.length,requests:l.map(e=>{const i=d(e);return{id:i.id,at:i.timestamp,method:i.method,url:i.url,status:i.status,durationMs:i.duration,error:i.error,overridden:i.override?true:void 0}}),hint:"Read one response with network.getEventBody({ id })."}}function g(a){const t=Object.entries(d(a)).slice(0,6).map(([s,o])=>{const r=typeof o==="string"||typeof o==="number"||typeof o==="boolean";const u=r&&String(o).length<=48&&!L(s,"route");return[s.slice(0,48),u?O(o):"(value omitted)"]});return t.length?Object.fromEntries(t):void 0}function $(a,t,s=80){const o=new Map;for(const i of t.slice(0,160)){if(typeof i.pathname!=="string")continue;const c=g(i.params);if(c)o.set(i.pathname,{...c,...o.get(i.pathname)})}const r=[];const u=new Set;let n=3e3;const l=(i,c=[])=>{if(r.length>=s||u.has(i))return;u.add(i);const y=o.get(i)??{};const f=[...new Set([...Object.keys(y),...p(c).filter(h=>typeof h==="string")])].slice(0,6).map(h=>{const S=y[h];return S===void 0||S==="(value omitted)"?h.slice(0,48):`${h}=${String(S)}`});const b=f.every(h=>Object.keys(y).some(S=>h===S||h.startsWith(`${S}=`)))?"params seen":"params";const m=f.length?` (${b}: ${f.join(", ")})`:"";const N=m.length<=n;if(N)n-=m.length;r.push(i+(N?m:""))};const e=(i,c=0)=>{if(c>12)return;for(const y of p(i)){if(r.length>=s)break;const f=d(y);if(f.isInternal!==true&&typeof f.path==="string")l(f.path,f.params);e(f.children,c+1)}};e(a);for(const i of o.keys())l(i);return r}function B(a,t){const s=n=>typeof n==="string"&&n.length>300?`${n.slice(0,300)}\u2026`:n;const o=typeof t.key==="string"?t.key.toLowerCase():void 0;const r=p(a).map(d).map(n=>{const l=d(n.data);return{id:n.id,at:n.timestamp,action:n.action,storage:n.storageType,key:l.key??l.keys,value:s(l.value),prevValue:s(l.prevValue)}}).filter(n=>!o||JSON.stringify(n.key??"").toLowerCase().includes(o)).sort((n,l)=>String(l.at).localeCompare(String(n.at)));const u=w(t.limit,20);return{events:r.slice(0,u),total:r.length,returned:Math.min(u,r.length)}}function E(a,t){const s=n=>{if(n===void 0)return void 0;const l=typeof n==="string"?n:JSON.stringify(n);return l&&l.length>400?`${l.slice(0,400)}\u2026`:n};const o=typeof t.type==="string"?t.type.toLowerCase():void 0;const r=p(a).map(d).filter(n=>n.category!=="internal").map(n=>({id:n.id,at:n.timestamp,type:n.type,payload:s(n.payload),meta:s(n.meta),error:s(n.error),changed:n.diffSummary||void 0})).filter(n=>!o||String(n.type).toLowerCase().includes(o)).sort((n,l)=>Number(l.at)-Number(n.at));const u=w(t.limit,20);return{actions:r.slice(0,u),total:r.length,returned:Math.min(u,r.length)}}function j(a,t,s,o){const r=e=>{if(e===void 0)return void 0;const i=typeof e==="string"?e:JSON.stringify(e);return i&&i.length>400?`${i.slice(0,400)}\u2026`:e};const u=typeof t[o]==="string"?String(t[o]).toLowerCase():void 0;const n=p(d(a).changes).map(d).map(e=>({id:e.id,at:e.timestamp,[o]:e[s],changed:e.changedKeys,summary:e.diffSummary||void 0,...s==="storeName"?{partial:r(e.partial)}:{value:r(e.valuePreview)}})).filter(e=>!u||String(e[o]??"").toLowerCase().includes(u)).sort((e,i)=>Number(i.at)-Number(e.at));const l=w(t.limit,20);return{changes:n.slice(0,l),total:n.length,returned:Math.min(l,n.length)}}function x(a,t){const s=d(a);const o=d(s.sitemap);const r=p(s.stack).map(d);const u=p(s.events).map(d);const n=r.map(c=>({name:c.name,path:c.pathname,...g(c.params)?{params:g(c.params)}:{},focused:c.isFocused===true?true:void 0}));const l=u.slice(0,w(t.limit,15)).map(c=>({at:c.timestamp,from:c.previousPathname,to:c.pathname,...g(c.params)?{params:g(c.params)}:{}}));const e=r.find(c=>c.isFocused===true)??r[r.length-1];const i=u.length?{path:u[0].pathname,source:"event",...g(u[0].params)?{params:g(u[0].params)}:{}}:typeof e?.pathname==="string"?{path:e.pathname,source:"stack",...g(e.params)?{params:g(e.params)}:{}}:void 0;return{currentRoute:i,stack:n,routes:$(o.routes,[...u.slice(0,80),...r.slice(0,80)]),sitemapSource:o.source,recentNavigations:l}}function F(a){const t=d(a);return{env:t.env??t.variables,requiredEnvVars:t.requiredEnvVars,...Array.isArray(t.checks)?{checks:t.checks}:{}}}function I(a,t){const s=d(a);const o=p(s.envelopes).map(d);const r=typeof t.type==="string"?t.type:void 0;const u=typeof t.pattern==="string"?t.pattern.toLowerCase():void 0;const n=o.filter(e=>{const i=p(e.items).map(d);if(r&&!i.some(c=>c.type===r))return false;if(!u)return true;return i.some(c=>String(c.summary??"").toLowerCase().includes(u))});const l=k(v(n,w(t.limit,10)).map(e=>({id:e.id,at:e.timestamp,origin:e.origin,eventId:e.eventId,totalBytes:e.totalBytes,items:p(e.items).slice(0,8).map(i=>{const c=d(i);return{type:c.type,summary:typeof c.summary==="string"?c.summary.slice(0,300):c.summary,spanCount:c.spanCount,bytes:c.bytes}})})),12e3);return{status:s.status,totalCaptured:o.length,shown:l.length,envelopes:l,drops:(()=>{const e=d(s.drops);const i=p(e.recent).map(d);const c=m=>m.category==="span"||m.category==="transaction";const y=[...v(i.filter(m=>!c(m)),8),...v(i.filter(c),3)];const f=k(y.map(m=>({at:m.timestamp,reason:m.reason,category:m.category,quantity:m.quantity,detail:typeof m.detail==="string"?m.detail.slice(0,200):m.detail})),4e3);const b=k(p(e.summary),2e3);return f.length||e.summary?{summary:b,recent:f}:void 0})(),hint:"For crash stack traces, console's [FATAL] entries carry the component stacks. `drops` lists events the SDK discarded before sending, with the reason."}}function T(a){const t=d(a);const s=o=>{if(o===null||o===void 0)return null;const r=d(o);return{id:r.id,displayName:r.displayName,email:r.email}};return{isActive:t.isActive,isPaused:t.isPaused,currentUser:s(t.currentUser),headerKey:t.headerKey,ignorePatterns:t.ignorePatterns,dataNukeSettings:t.dataNukeSettings,showBanner:t.showBanner,history:p(t.history).slice(0,10).map(o=>{const r=d(o);return{user:s(r.user),lastUsedAt:r.lastUsedAt}})}}export function projectSnapshot(a,t,s={}){switch(a){case"console":return P(t,s);case"network":return R(t,s);case"route-events":return x(t,s);case"storage":return B(t,s);case"redux":return E(t,s);case"zustand":return j(t,s,"storeName","store");case"jotai":return j(t,s,"atomLabel","atom");case"env":return F(t);case"impersonate":return T(t);case"sentry":return I(t,s);default:return t}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{projectSnapshot as
|
|
1
|
+
"use strict";import{projectSnapshot as b,SNAPSHOT_ACTION as p}from"../catalog/snapshotReads";import{realNow as k,realSetTimeout as E}from"../realNow";const T=2500;const f=t=>Array.isArray(t)?t:Array.isArray(t?.items)?t.items:[];const a=t=>t&&typeof t==="object"?t:{};const _=(t,e,n=40)=>f(t).slice(0,n).map(s=>{if(!s||typeof s!=="object")return s;const i=s;for(const o of e){if(i[o]!==void 0)return i[o]}return i.id??i.name??i.key});const w=30;const O=24;const P=8;const R=240;const C=12e3;const L=(t,e)=>t.length<=e?t:`${t.slice(0,e-1)}\u2026`;function j(t){const e=new Map;if(typeof t!=="string"||!t.startsWith("{"))return e;let n=0;let s="";const i=()=>{const o=s.indexOf(":");if(o>0)e.set(s.slice(0,o).trim(),s.slice(o+1).trim());s=""};for(const o of t.slice(1,-1)){if(o==="{"||o==="["||o==="(")n++;else if(o==="}"||o==="]"||o===")")n--;if(o===","&&n===0)i();else s+=o}i();return e}const N=[{key:"routes",toolId:"route-events",action:p,shape:t=>f(t?.routes).slice(0,60)},{key:"reduxSlices",toolId:"redux",action:"getState",params:{includeValues:false},shape:t=>{const e=a(t);if(e.available===false)return void 0;const n=a(e.sliceFields);if(Array.isArray(e.slices))return e.slices.slice(0,40).map(s=>Array.isArray(n[String(s)])&&n[String(s)].length?`${String(s)}: ${n[String(s)].join(", ")}`:s);return e.state&&typeof e.state==="object"?Object.keys(e.state).slice(0,40):void 0}},{key:"zustandStores",toolId:"zustand",action:"listStores",shape:t=>{let e=0;return f(t?.stores??t).slice(0,w).map(n=>{const s=n??{};const i=typeof s.name==="string"?s.name:s.id;const o=f(s.shape?.lists).map(a);const l={};const d=[];for(const r of o.slice(0,P)){const c=typeof r.path==="string"?r.path:void 0;if(!c)continue;if(c.includes("[]."))continue;if(typeof r.item!=="string"){d.push(c);continue}const u=L(r.item,R);if(e+u.length>C)continue;e+=u.length;l[c]=u}return{name:i,...typeof s.persistName==="string"?{persistsTo:s.persistName}:{},...(()=>{if(!Array.isArray(s.keys)||!s.keys.length)return{};const r=j(s.shape?.shape);const c=s.keys.filter(u=>r.get(String(u))!=="function");return c.length?{keys:c.slice(0,O)}:{}})(),...Object.keys(l).length?{listItemShapes:l}:{},...d.length?{unknownLists:d}:{}}})}},{key:"jotaiAtoms",toolId:"jotai",action:"listAtoms",shape:t=>_(a(t).atoms??t,["debugLabel","label","id"],40)},{key:"storageKeys",toolId:"storage",action:"async.getAllKeys",shape:t=>f(t).slice(0,60)},{key:"env",toolId:"env",action:p,shape:t=>{const e=t?.env;return e&&typeof e==="object"?Object.keys(e).slice(0,40):void 0}}];const I=25;const x=I*2;const D=8;function M(t){const e=typeof t==="number"?t:typeof t==="string"?Date.parse(t):NaN;return Number.isFinite(e)?{age:`${Math.max(0,Math.floor((k()-e)/1e3))}s ago`}:{}}const B=[{key:"currentRoute",toolId:"route-events",action:"getCurrentRoute",shape:t=>{const e=a(t);if(typeof e.path!=="string")return void 0;return e.params&&Object.keys(a(e.params)).length?{path:e.path,params:e.params}:{path:e.path}}},{key:"queries",toolId:"query",action:"listQueries",params:{limit:x},shape:t=>{const e=f(t?.queries??t).map(a);const n=e.map(s=>{const i=typeof s.observers==="number"?s.observers:0;return{key:s.queryKey??s.queryHash,status:s.status,...i>0?{onScreen:true,observers:i}:{}}});n.sort((s,i)=>Number(Boolean(i.onScreen))-Number(Boolean(s.onScreen)));return n.slice(0,I)}},{key:"networkCapture",toolId:"network",action:"getCaptureStatus",shape:t=>{const e=a(t);if(typeof e.capturing!=="boolean")return void 0;return{recording:e.capturing,...e.interceptorLive===false?{interceptorLive:false}:{},captured:typeof e.eventCount==="number"?e.eventCount:void 0}}},{key:"recentRequests",toolId:"network",action:p,params:{limit:D},shape:t=>f(t?.requests).map(e=>{const n=a(e);return{id:n.id,...n.at!==void 0?{at:n.at}:{},...M(n.at),method:n.method,url:n.url,status:n.status,...n.overridden?{overridden:true}:{}}})},{key:"appClock",toolId:"clock",action:"getState",shape:t=>{const e=a(t);if(e.active!==true||typeof e.virtualIso!=="string")return void 0;return{appTime:e.virtualIso,mode:e.mode,...typeof e.rate==="number"&&e.rate!==1?{rate:e.rate}:{},offsetMs:e.offsetMs}}},{key:"impersonating",toolId:"impersonate",action:p,shape:t=>{const e=a(t);if(e.isActive!==true)return void 0;const n=a(e.currentUser);return{user:typeof n.displayName==="string"?n.displayName:n.id,id:n.id,...e.isPaused===true?{paused:true}:{},header:e.headerKey}}},{key:"appLocation",toolId:"location",action:"getState",shape:t=>{const e=a(t);if(e.active!==true)return void 0;const n=a(e.override);const s=a(e.current);const i=a(s.fix);const o=a(n.place);const l=a(n.route);return{mode:n.mode,...typeof i.latitude==="number"?{latitude:i.latitude,longitude:i.longitude,accuracy:i.accuracy}:{noFix:s.reason},...n.mode==="fixed"&&typeof o.label==="string"?{place:o.label}:{},...n.mode==="route"?{route:l.name??l.id,playing:n.playing===true}:{},...n.signal===false?{signal:false}:{},...n.services===false?{services:false}:{}}}},{key:"appPermissions",toolId:"permissions",action:p,shape:t=>{const e=f(a(t).permissions).map(a).filter(n=>typeof n.override==="string");if(!e.length)return void 0;return Object.fromEntries(e.map(n=>[String(n.id),n.real&&n.real!==n.override?`${n.override} (system: ${n.real})`:n.override]))}},{key:"networkOverrides",toolId:"network",action:"listOverrideRules",shape:t=>{const e=a(t);const n=f(e.rules).map(a).filter(s=>s.enabled!==false).slice(0,10).map(s=>({id:s.id,urlPattern:s.urlPattern,kind:s.kind,status:s.status}));if(!n.length)return void 0;return{enabled:e.enabled===true,rules:n}}}];async function H(t,e){return await Promise.race([t.catch(()=>void 0),new Promise(n=>E(()=>n(void 0),e))])}async function A(t,{dispatch:e,readSnapshot:n,availableToolIds:s,availableActions:i}){const o=t.filter(r=>{if(!s.includes(r.toolId))return false;const c=i?.[r.toolId];return!c||c.includes(r.action)});const l=await Promise.all(o.map(async r=>{const c=r.action===p?n?Promise.resolve(n(r.toolId)).then(g=>b(r.toolId,g,r.params??{})):Promise.resolve(void 0):Promise.resolve(e(r.toolId,r.action,r.params));const u=await H(c,T);if(u===void 0)return[r.key,void 0];try{return[r.key,r.shape(u)]}catch{return[r.key,void 0]}}));const d={};for(const[r,c]of l){const u=c===void 0||c===null||Array.isArray(c)&&c.length===0;if(!u)d[r]=c}return d}export function buildContextDigest(t){return A(N,t)}function K(t){const e=Array.isArray(t)?t:typeof t==="string"?(()=>{try{const n=JSON.parse(t);return Array.isArray(n)?n:[t]}catch{return[t]}})():[t];return e.flatMap(n=>n===null||n===void 0?[]:S(String(typeof n==="object"?JSON.stringify(n):n)))}function S(t){return t.toLowerCase().split(/[^a-z0-9]+/i).filter(Boolean)}function $(t){const e=t.currentRoute;const n=t.queries;if(!e?.path||!Array.isArray(n))return;const s=Object.values(e.params??{}).map(r=>S(String(r))).filter(r=>r.length>0);const i=S(e.path);let o=-1;let l=-1;let d=false;n.forEach((r,c)=>{if(!r.onScreen)return;const u=new Set(K(r.key));const g=s.filter(y=>y.every(h=>u.has(h))).length;const v=i.filter(y=>u.has(y)||[...u].some(h=>h.includes(y)||y.includes(h))).length;const m=g*3+v;if(m<=0)return;if(m>o){o=m;l=c;d=false}else if(m===o){d=true}});if(l>=0&&!d){n[l].likelyScreen=true;const[r]=n.splice(l,1);n.unshift(r)}}export async function buildLiveContext(t){const e=await A(B,t);$(e);let n;try{n=Intl.DateTimeFormat().resolvedOptions().timeZone}catch{n=void 0}e.now={iso:new Date(k()).toISOString(),...n?{timeZone:n}:{}};return e}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{describe as A,expect as e,it as r,vi as u}from"vitest";import{runAgentTurn as y,runGatedAction as b}from"./runAgentTurn";import{CATALOG as k}from"../catalog/catalog.g";import{EffectLedger as d}from"../effects/ledger";const p=[{toolId:"highlight-updates",title:"Screen",summary:"Screen tools",actions:[{action:"tapElement",summary:"Tap a control",effect:"write",release:"works",params:{type:"object",properties:{nativeTag:{type:"number"},testID:{type:"string"},query:{type:"string"}}}},{action:"describeScreen",summary:"Read the screen",effect:"read",release:"works",params:{type:"object",properties:{}}}]}];async function c(t,a,o=false,s=false){const l=new d;const n=[];let g=0;const v={protocol:"anthropic",async*send(i){n.push(i.messages);if(g++===0)yield{type:"tool-call",call:{id:"tap",name:"buoy_highlight-updates",input:{action:"tapElement",params:t}}};else yield{type:"text",delta:"The screen check is done."};yield{type:"done",outcome:"completed"}}};const m=u.fn(async(i,q)=>q==="describeScreen"?{elements:[{nativeTag:7,label:"Place test order",name:"Pressable"}]}:a);const h=u.fn(async()=>false);const f=[];for await(const i of y({provider:v,catalog:p,system:"s",model:"m",maxTokens:100,messages:[{role:"user",text:o?"Why is checkout wrong?":"Tap the button."}],dispatch:m,ledger:l,policy:{requireApproval:s?["write"]:[]},isRelease:false,requestApproval:h}))f.push(i);return{ledger:l,requests:n,dispatch:m,requestApproval:h,events:f}}A("empty and missed taps",()=>{r.each([{},{nativeTag:" ",testID:"",query:""},{testID:"",query:" \n "},{nativeTag:null,testID:" "}])("skips an empty tap: %j",async t=>{for(const a of[false,true]){const o=await c(t,{tapped:true},a,true);e(o.dispatch).not.toHaveBeenCalled();e(o.requestApproval).not.toHaveBeenCalled();e(o.events).toContainEqual(e.objectContaining({type:"tool-end",ok:false,summary:e.stringContaining("tapElement needs")}));e(JSON.stringify(o.requests)).toContain('\\"ok\\":false');e(o.ledger.list()).toEqual([]);e(o.events.some(s=>s.type==="approval-required"||s.type==="tool-verified"||s.type==="block")).toBe(false)}});r.each([{query:"Add"},{testID:"add"},{nativeTag:7}])("runs a valid tap: %j",async t=>{const a=await c(t,{tapped:true});e(a.dispatch).toHaveBeenCalledWith("highlight-updates","tapElement",t);e(a.events).toContainEqual(e.objectContaining({type:"tool-end",ok:true}));e(a.ledger.list()).toHaveLength(1)});r.each([{tapped:false,reason:"No match for Add."},{ok:true,tapped:false,reason:"No match for Add."}])("keeps a missed tap out of the ledger: %j",async t=>{const a=await c({query:"Add"},t);e(a.dispatch).toHaveBeenCalledOnce();e(a.events).toContainEqual(e.objectContaining({type:"tool-end",ok:false,summary:t.reason}));e(JSON.stringify(a.requests)).toContain('\\"ok\\":false');e(a.ledger.list()).toEqual([]);e(a.events.some(o=>o.type==="tool-verified"||o.type==="block")).toBe(false)});r("keeps the question gate for valid taps",async()=>{const t=await c({query:"Place test order"},{tapped:true},true);e(t.requestApproval).toHaveBeenCalledOnce();e(t.dispatch).not.toHaveBeenCalledWith("highlight-updates","tapElement",e.anything());e(t.ledger.list()).toEqual([])});r.each([{},{query:" "},{query:"Add"}])("guards a block button too: %j",async t=>{const a=new d;const o=u.fn(async()=>({tapped:false,reason:"No match for Add."}));const s=await b({toolId:"highlight-updates",action:"tapElement",params:t,catalog:p,dispatch:o,ledger:a,policy:{requireApproval:[]},isRelease:false});e(s).toEqual({ok:false,error:t.query==="Add"?"No match for Add.":"tapElement needs nativeTag, testID or query. Read describeScreen and pass one."});e(o).toHaveBeenCalledTimes(t.query==="Add"?1:0);e(a.list()).toEqual([])});r.each([{},{query:"Add"}])("does not count a failed tap as plan work: %j",async t=>{let a=0;const o=new d;const s={protocol:"anthropic",async*send(){const l=a++;if(l===0)yield{type:"tool-call",call:{id:"plan",name:"buoy_ask-buoy",input:{action:"openProcedure",params:{id:"add"}}}};else if(l===1)yield{type:"tool-call",call:{id:"tap",name:"buoy_highlight-updates",input:{action:"tapElement",params:t}}};else yield{type:"text",delta:"The plan is set."};yield{type:"done",outcome:"completed"}}};for await(const l of y({provider:s,catalog:[...p,k.find(n=>n.toolId==="ask-buoy")],system:"s",model:"m",maxTokens:100,messages:[{role:"user",text:"Add the item."}],dispatch:async()=>({tapped:false,reason:"No match."}),ledger:o,policy:{requireApproval:[]},isRelease:false,procedures:[{id:"add",title:"Add an item",summary:"Add one item",body:"Tap Add."}]})){}e(a).toBe(4);e(o.list()).toEqual([])});r.each([{},{query:"Add"}])("leaves failed taps out of the step-cap list: %j",async t=>{let a=0;const o={protocol:"anthropic",async*send(){if(a++===0)yield{type:"tool-call",call:{id:"tap",name:"buoy_highlight-updates",input:{action:"tapElement",params:t}}};yield{type:"done",outcome:"completed"}}};const s=[];for await(const n of y({provider:o,catalog:p,system:"s",model:"m",maxTokens:100,messages:[{role:"user",text:"Tap Add."}],dispatch:async()=>({tapped:false,reason:"No match."}),ledger:new d,policy:{requireApproval:[],maxSteps:1},isRelease:false}))s.push(n);const l=s.filter(n=>n.type==="text").map(n=>n.delta).join("");e(l).toContain("The job is not done.");e(l).not.toContain("These steps ran:")})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{compressedMarker as
|
|
1
|
+
"use strict";import{isRealUserMessage as m}from"../providers/types";import{compressedMarker as x}from"./evidence";import{DEFAULT_CHARS_PER_TOKEN as a}from"./tokenCalibration";export const MAX_HISTORY_TOKENS=37500;export const MAX_REQUEST_TOKENS=1e5;export const MAX_HISTORY_CHARS=MAX_HISTORY_TOKENS*a;export const MAX_REQUEST_CHARS=MAX_REQUEST_TOKENS*a;const A=4;const z=400;export function messageSize(e){try{return JSON.stringify(e).length}catch{return 0}}export function compressConsumed(e,c=true){let i=e.length;if(c){i=-1;for(let t=e.length-1;t>=0;t--){if(e[t].role==="tool-results"){i=t;break}}if(i<=0)return e}const d=new Set;for(let t=0;t<e.length;t++){const l=e[t];if(l.role!=="assistant"||!l.toolCalls?.length)continue;if(!l.toolCalls.some(s=>C.test(JSON.stringify(s.input??{})+s.name)))continue;for(let s=t-1;s>=0&&s>=t-3;s--){if(e[s].role==="tool-results"){d.add(s);break}}}let o=false;const u=e.map((t,l)=>{if(t.role!=="tool-results"||l>=i||d.has(l))return t;let s=false;const n=t.results.map(r=>{if(r.isError||r.content.length<z)return r;if(r.content.startsWith("[earlier result"))return r;s=true;return{...r,content:x(r.content.length,r.ref)}});if(!s)return t;o=true;return{...t,results:n}});return o?u:e}const C=/set|write|dispatch|navigate|tap|override|restore|delete|remove|clear|save|run|impersonat|reload/i;function S(e,c){const i=[];for(const d of e){if(m(d)||i.length===0)i.push({messages:[],size:0,pinned:false});const o=i[i.length-1];o.messages.push(d);o.size+=messageSize(d);if(c?.size&&d.role==="assistant"&&d.toolCalls?.some(u=>c.has(u.id)))o.pinned=true}return i}function _(e,c,i,d){const o=[...e];let u=o.reduce((n,r)=>n+r.size,0);let t=0;let l=0;const s=()=>o.reduce((n,r)=>n+r.messages.length,0);while(o.length>0&&u+i>c&&s()>d){let n=o.findIndex(r=>!r.pinned);if(n<0){n=0;l+=1}u-=o[n].size;o.splice(n,1);t+=1}return{kept:o.flatMap(n=>n.messages),droppedRounds:t,droppedPinned:l}}export function trimHistoryReport(e,c=a,i){const d=Math.floor(MAX_HISTORY_TOKENS*c);let o=0;for(const r of e)o+=messageSize(r);if(o<=d)return{messages:e,droppedRounds:0,droppedPinned:0};const u=compressConsumed(e);let t=0;for(const r of u)t+=messageSize(r);if(t<=d)return{messages:u,droppedRounds:0,droppedPinned:0};const{kept:l,droppedRounds:s,droppedPinned:n}=_(S(u,i),d,0,A);return{messages:l,droppedRounds:s,droppedPinned:n}}export function trimHistory(e){return trimHistoryReport(e).messages}export function budgetForRequest(e,c,i=MAX_HISTORY_CHARS,d=a,o){const u=Math.min(i,Math.floor(MAX_REQUEST_TOKENS*d)-c);let t=0;for(const p of e)t+=messageSize(p);if(t<=u)return{messages:e,droppedRounds:0,droppedPinned:0};let l=-1;for(let p=e.length-1;p>=0;p--){if(m(e[p])){l=p;break}}if(l<=0){return{messages:compressConsumed(e),droppedRounds:0,droppedPinned:0}}const s=e.slice(0,l);const n=e.slice(l);let r=0;for(const p of n)r+=messageSize(p);const h=compressConsumed(s,false);let f=0;for(const p of h)f+=messageSize(p);if(f+r<=u){return{messages:[...h,...n],droppedRounds:0,droppedPinned:0}}const{kept:R,droppedRounds:g,droppedPinned:E}=_(S(h,o),u,r,0);f=R.reduce((p,P)=>p+messageSize(P),0);const M=R.length===0&&f+r>u?compressConsumed(n):n;return{messages:[...R,...M],droppedRounds:g,droppedPinned:E}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const _=64e3;const I=32e3;const U=/\b(?:picker_\d+|[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12})\b/gi;const g=s=>s==="id"||/Id$/.test(s);export const UNSEEN_ID_ADVICE="Use an id from a read, or search for it.";export function idGrounding(s,h){let i="";const f=e=>{i=(i+"\n"+e.replace(/\[Buoy\] Unseen id:[^\n]*/g,"")).slice(-_)};for(const e of s){if(e.role==="user")f(e.text);if(e.role==="tool-results")for(const d of e.results)f(d.content)}const m=h.slice(-I);return{add:f,trailer(e,d=true){const r=new Set;let u=200;const l=(t,n="",a=0)=>{if(--u<0||a>6||r.size>=8)return;if(typeof t==="string"){if(g(n)&&t.length>0&&t.length<=160)r.add(t);for(const o of t.slice(0,2048).match(U)??[])r.add(o);for(const o of t.slice(0,2048).matchAll(/[?&]([^=&]+)=([^&#]*)/g)){try{if(g(decodeURIComponent(o[1])))l(decodeURIComponent(o[2]),"id",a+1)}catch{}}}else if(t&&typeof t==="object"){for(const[o,y]of Object.entries(t))l(y,o,a+1)}};l(e);const c=m+"\n"+i;const p=t=>{let n=c.indexOf(t);while(n>=0){if(!/[\w-]/.test(c[n-1]??"")&&!/[\w-]/.test(c[n+t.length]??""))return true;n=c.indexOf(t,n+1)}return false};return[...r].slice(0,8).filter(t=>!p(t)).map(t=>`
|
|
2
|
+
[Buoy] Unseen id: ${t.replace(/[\r\n]/g," ")} was not in any read.${d?` ${UNSEEN_ID_ADVICE}`:""}`).join("")}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";export const PLAN_ONLY_NUDGE="[Buoy, not the user: your last message said what you would do next but called no tool, so nothing ran and the turn would end there. If the task is not finished, make the next call now. If you are done, say so. If the user has to pick or answer, send the buoy_ui block (choice, confirm or input) now \u2014 text alone gives them nothing to tap.]";export const SILENT_NUDGE="[Buoy, not the user: you ended the turn without a word to the user. Tell them in a sentence or two what you found or did \u2014 a card alone does not say it.]";const s=/\b(if you|once you|when you|after you|only if|let me know|would you like|do you want|shall i|should i|tap|choose|approve)\b/i;const
|
|
1
|
+
"use strict";export const PLAN_ONLY_NUDGE="[Buoy, not the user: your last message said what you would do next but called no tool, so nothing ran and the turn would end there. If the task is not finished, make the next call now. If the user asked for this change, do it and check it. Do not ask them to grant that same step again. Still obey tool approval checks. If you are done, say so. If the user has to pick or answer, send the buoy_ui block (choice, confirm or input) now \u2014 text alone gives them nothing to tap.]";export const SILENT_NUDGE="[Buoy, not the user: you ended the turn without a word to the user. Tell them in a sentence or two what you found or did \u2014 a card alone does not say it.]";const s=/\b(if you|once you|when you|after you|only if|let me know|would you like|do you want|shall i|should i|tap|choose|approve)\b/i;const h=/\b(?:tap|press|click|choose|select)\s+\**["“]?(?:go|approve|yes|continue|start|confirm)\b|ready for your (?:approval|go-ahead|ok)|(?:plan|steps|card|options?) (?:above|below)\b|approve the plan|for your (?:approval|go-ahead)|here['’]s the (?:plan|flow|steps)\b|\b(?:choose|pick|select) (?:what|which|one|from)\b[^.?!]*:/i;const d=/\b(?:pick|choose|select|tell me) (?:what|which)\b/i;const p=/\bi(?:['’]m| am) ready to (?:submit|place|refund|continue)\b|\bbefore i (?:submit|place|refund)\b[^.!?]*\bi need to confirm\b/i;const b=/\b(i['’]ll|i will|let me|i['’]m going to|i am going to|next,? i|now i['’]ll|i['’]m about to)\b/i;const r=/\bi['’]m (?:now )?(?!sorry\b)[a-z]+ing\b/i;export function announcesUnstartedWork(a,i=false){const t=a.trim();if(h.test(t))return true;if(d.test(t))return true;if(/:\s*$/.test(t))return true;if(!t||/\?\s*$/.test(t))return false;const e=t.split(/(?<=[.!*])\s*(?=[A-Z“"])/).filter((l,c,n)=>!(c===n.length-1&&n.length>1&&/^[“"][^“”"]*[”"]\s*$/.test(l.trim())));const o=e.slice(-2).join(" ");if(i&&p.test(o))return true;const u=e.length>1&&!/\d/.test(e.at(-1)??"");if((r.test(e.at(-1)??"")||u&&r.test(e.at(-2)??""))&&!s.test(o))return true;return b.test(o)&&!s.test(o)}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
"use strict";import{CATALOG as Ve}from"../catalog/catalog.g";import{applyDefaults as ce,validateParams as fe}from"../catalog/validateParams";import{normalizeParams as ue,aliasNote as bt}from"../catalog/normalizeParams";import{fromProviderToolName as ee,toProviderTools as wt}from"../catalog/toProviderTools";import{hashQueryKey as vt}from"../catalog/normalizeParams";import{projectSnapshot as Rt,SNAPSHOT_ACTION as Xe}from"../catalog/snapshotReads";import{UNPARSEABLE_ARGS as St}from"../providers/types";import{isReversible as Ze}from"../effects/ledger";import{describeCall as Ae,decide as Y}from"../policy/policy";import{redact as Tt,stripSelfTraffic as It}from"../policy/redact";import{effectFor as et}from"./effectFor";import{digestOf as Ee}from"../effects/digest";import{buildUiBlock as At,UI_TOOL_NAME as te,uiProviderTool as Et}from"../blocks/uiTool";import{projectReceipt as $t}from"../blocks/receipts";import{isAskBlock as Ct}from"../blocks/types";import{ACQUISITION_CALLS as Nt,synthesizeAskBlock as _t,synthesizeShortfallBlock as Ot}from"./askGate";import{couldBeToolCallEnvelope as xt,parseTextToolCalls as Mt,SALVAGE_NOTE as Lt}from"./textToolCalls";import{announcesUnstartedWork as Pt,PLAN_ONLY_NUDGE as Ut,SILENT_NUDGE as Dt}from"./planOnlyStop";import{compactScreen as qt}from"./compactScreen";import{budgetForRequest as tt,MAX_HISTORY_TOKENS as jt,messageSize as $e}from"./historyBudget";import{promptTokensOf as Bt,TokenCalibration as Ft}from"./tokenCalibration";import{truncationMarker as zt}from"./evidence";import{retrieveEvidence as Ht}from"./retrieve";import{verificationTrailer as Wt,verifyOutcome as Gt}from"./verify";import{realClearTimeout as Jt,realNow as A,realSetTimeout as Yt}from"../realNow";const ot=24e3;const nt=2e3;const rt=3;function Kt(t){const n=r=>{if(r===null||typeof r!=="object")return r;if(Array.isArray(r))return r.map(n);const c=r;return Object.keys(c).sort().reduce((f,R)=>(f[R]=n(c[R]),f),{})};return t.map(r=>`${r.name}:${JSON.stringify(n(r.input??{}))}`).join("|")}const Qt="[Buoy] You have now made this exact call three times in a row and gotten the same thing back. Repeating it again will not change the answer. Read the result you already have, then either try a different tool or different arguments, or tell the user plainly what you could not get and what you tried.";const Vt=24;const it=18e4;const Xt=2;const Zt=1;const eo=1e3;const to=250;const oo=3e4;function no(t,n){return new Promise(r=>{if(n?.aborted)return r();const c=Yt(f,t);function f(){Jt(c);n?.removeEventListener("abort",f);r()}n?.addEventListener("abort",f,{once:true})})}function st(t){if(typeof t==="boolean")return{approved:t,trust:false};const n=t.reason?.trim();return{approved:t.approved,...n?{reason:n}:{},trust:t.trust===true&&t.approved}}function at(t){return t?`The user declined this change and said: "${t}". Do not retry it as proposed; take their note into account and, if a different change would fit, propose that instead.`:"The user declined this change. Do not retry it; ask what they would prefer."}function q(t,n,r){return t.find(c=>c.toolId===n)?.actions.find(c=>c.action===r)}const ro="Rejected: every image URL must be one you read from a tool result in THIS conversation (images.list, a storage value, a response body). Never invent or remember URLs \u2014 read them first, then show them.";const io=t=>` NOTE: ${t} image ${t===1?"URL was":"URLs were"} dropped \u2014 they were not read from a tool result in this conversation, so the card the user is looking at has no picture there. Do not tell them it does; read the URLs and show it again, or say the artwork is missing.`;function so(t){try{return JSON.stringify(t??null)}catch{return String(t)}}function ao(t,n){if(t.length<=ot)return t;return`${t.slice(0,ot)}${zt(t.length,n)}`}function lo(t){if(t===null||t===void 0)return"Done";if(Array.isArray(t))return Ce(t.length);if(typeof t==="boolean")return t?"Done":"Failed";if(typeof t==="object"){const n=t;if(n.ok===false)return typeof n.error==="string"?n.error:"Failed";if(n.found===false)return"Not found";if(n.tapped===false)return"Nothing to tap";if(n.active===false)return"Saved, but inactive in this build";for(const c of["returned","count","total"]){const f=n[c];if(typeof f==="number")return Ce(f)}const r=Object.values(n).filter(Array.isArray);if(r.length===1)return Ce(r[0].length);return"Done"}return String(t).slice(0,80)}function Ce(t){return t===1?"1 item":`${t} items`}function dt(t){return typeof t==="object"&&t!==null&&t.ok===false}export async function*runAgentTurn(t){const{provider:n,catalog:r,system:c,systemVolatile:f,model:R,maxTokens:g,dispatch:d,readSnapshot:m,storeKeyOwners:N,ledger:w,policy:L,isRelease:P,requestApproval:j,signal:_,evidence:B,trusted:T,procedures:F}=t;const z=new Map;const O=t.calibration??new Ft;const h=[...t.messages];const pe=wt(r,{availableToolIds:t.availableToolIds,availableActions:t.availableActions,isRelease:P});pe.push(Et());const ne=L.maxSteps??Vt;const ct=po();const ft=w.generation;const _e=c.length+(f?.length??0)+pe.reduce((x,M)=>x+M.description.length+JSON.stringify(M.inputSchema).length,0);const Oe=()=>_e+O.chars(g);let re=A()+it;const he={throttled:0,overflow:0};let ie=jt;const me=t.seenUrls??new Set;const ut=/https?:\/\/[^\s"'\\)\]}>]+/g;let xe=false;const H=[];let Me=false;const ye=[];let ge=false;let Le=false;let se=false;let Pe=false;let Ue=false;const pt=[...h].reverse().find(x=>x.role==="user");const ht=/\?\s*$/.test(pt?.text?.trim()??"");for(let x=0;x<ne;x++){if(_?.aborted){yield{type:"done",stopReason:"stopped"};return h}if(A()>re){yield{type:"block",block:{id:`cap${A()}`,kind:"notice",tone:"warning",text:`This is taking too long \u2014 stopped after ${Math.round(it/1e3)}s. What it found so far is above.`,actions:[{label:"Continue",primary:true,send:"Continue where you left off."}]}};yield{type:"done",stopReason:"time-cap"};return h}const M=tt(h,Oe(),O.chars(ie),O.charsPerToken,w.liveCallIds());if(M.droppedRounds>0){h.length=0;h.push(...M.messages);yield{type:"history-trimmed",droppedRounds:M.droppedRounds,droppedPinned:M.droppedPinned}}else if(M.messages!==h){h.length=0;h.push(...M.messages)}let E="";let De=false;const W=e=>{const l=xe&&!De;De=true;xe=true;return l?{type:"text",delta:e,breakBefore:true}:{type:"text",delta:e}};let v=[];const qe=new Set;let C="";let ke=true;let K=[];let ae=false;let de;let G;let be=0;for(;;){be+=1;E="";v=[];C="";ke=true;K=[];ae=false;de=void 0;G=void 0;let e;const l=_e+h.reduce((a,p)=>a+$e(p),0);for await(const a of n.send({messages:h,system:c,systemVolatile:f,tools:pe,model:R,maxTokens:g,signal:_,meta:{askId:ct,round:x+1,attempt:be}})){if(a.type==="text"){E+=a.delta;if(!ke){yield W(a.delta)}else if(xt(E)){C+=a.delta}else{ke=false;C="";yield W(E)}}else if(a.type==="tool-call"){v.push(a.call)}else if(a.type==="meter"){yield{type:"meter",frame:a.frame}}else if(a.type==="thinking"){K.push(a.block);if(a.block.type==="thinking"&&a.block.thinking.trim()){yield{type:"reasoning",text:a.block.thinking}}}else if(a.type==="error"){if(a.kind==="truncated"||a.kind==="timeout"){G={message:a.message}}else if(a.problem&&(a.problem.kind==="throttled"||a.problem.kind==="overflow")&&E===""&&v.length===0&&K.length===0){e={problem:a.problem,message:a.message}}else{yield{type:"error",message:a.message,...a.problem?.hosted?{hosted:a.problem.hosted}:{}};ae=true}}else if(a.type==="done"){de=a.outcome;if(a.usage){O.observe(l,Bt(a.usage,n.protocol));yield{type:"usage",...a.usage,model:a.model}}}}if(!e)break;const o=e.problem.kind;const i=1+(o==="throttled"?Xt:Zt);const k=he[o];let u=o==="throttled"?Math.min(e.problem.retryAfterMs??eo*2**k,oo)+Math.floor(Math.random()*to):0;let $=k>=i-1||_?.aborted===true||A()+u>re;if(e.problem.hosted&&be>=2)$=true;if(!$&&o==="overflow"){ie=Math.floor(ie/2);const a=h.reduce((Q,D)=>Q+$e(D),0);const p=tt(h,Oe(),O.chars(ie),O.charsPerToken,w.liveCallIds());const J=p.messages.reduce((Q,D)=>Q+$e(D),0);if(J>=a){$=true}else{h.length=0;h.push(...p.messages);if(p.droppedRounds>0)yield{type:"history-trimmed",droppedRounds:p.droppedRounds,droppedPinned:p.droppedPinned}}}if($){const a=o==="throttled"?k>0?`The AI endpoint is rate-limiting requests \u2014 tried ${k+1} times.`:"The AI endpoint is rate-limiting requests.":"This conversation is too large for the AI endpoint and there was nothing older to trim. Start a new conversation, or ask a shorter question.";const p=e.problem.hosted;yield p?{type:"error",message:p.message,hosted:p}:{type:"error",message:`${a} (${e.message})`};ae=true;break}he[o]+=1;yield{type:"retrying",reason:o,attempt:he[o]+1,maxAttempts:i,inMs:u};if(u>0)await no(u,_);if(_?.aborted){yield{type:"done",stopReason:"stopped"};return h}}if(ae){if(C)yield W(C);yield{type:"done",stopReason:"error"};return h}if(!G&&de!=="completed"){G={message:de==="output-limited"?v.length?"The model ran out of room mid-plan, so nothing was run. What it found so far is above.":"The model ran out of room before finishing this answer.":"The answer ended before the endpoint said it was finished, so nothing was run."}}if(G){if(C)yield W(C);if(E.trim())h.push({role:"assistant",text:E});yield{type:"block",block:{id:`cut${A()}`,kind:"notice",tone:"warning",text:G.message,actions:[{label:"Continue",primary:true,send:"Continue where you left off."}]}};yield{type:"done",stopReason:"incomplete"};return h}if(C){const e=v.length===0?Mt(C,r):void 0;if(e){for(const l of e.calls){v.push(l);qe.add(l.id)}E=e.reply;if(e.reply)yield W(e.reply)}else{yield W(C)}C=""}h.push({role:"assistant",text:E,toolCalls:v.length?v:void 0,thinking:K.length?K:void 0});if(E.trim())ye.push(E.trim());if(v.length===0&&!se&&!ge&&x<ne-1&&(Pt(E)||Pe&&!Ue&&!ht)){se=true;h.push({role:"user",text:Ut});continue}if(v.length===0&&!se&&ye.length===0&&x<ne-1){se=true;h.push({role:"user",text:Dt});continue}if(v.length===0){const e=ye.join("\n\n");const l=Ot({text:e,attempted:Le});if(l)yield{type:"block",block:l};const o=ge||l?null:_t(e);if(o){yield{type:"block",block:o};yield{type:"awaiting-user",blockId:o.id}}yield{type:"done",stopReason:o?"awaiting-user":"answered"};return h}const b=[];let je=false;const Be=new Map;if(v.length>1){const e=v.every(l=>{if(l.name===te)return false;const o=ee(l.name,r);if(!o||o==="ask-buoy")return false;const i=l.input?.action;if(typeof i!=="string"||i===Xe)return false;const k=q(r,o,i);if(!k||k.effect!=="read")return false;return Y({descriptor:k,toolId:o,policy:L,isRelease:P}).verdict==="allow"});if(e){for(const l of v){const o=ee(l.name,r);const i=String(l.input.action);const k={...l.input.params??{}};const u=q(r,o,i);const $=ue(o,i,k);const a=ce($.params,u.params);if(!fe(a,u.params).ok)continue;const p=Promise.resolve(d(o,i,a)).catch(J=>{throw J});p.catch(()=>{});Be.set(l.id,p)}}}let we=null;const mt=e=>{const l=ee(e.name,r)??e.name;const o=typeof e.input?.action==="string"?e.input.action:"";const i=o?q(r,l,o):void 0;const k=e.input?.params??{};return[{type:"tool-start",id:e.id,toolId:l,action:o,label:i?Ae(l,i,k):`${l}.${o}`,description:i?.summary??"",params:k,effect:i?.effect??"read"},{type:"tool-end",id:e.id,ok:false,summary:"stopped",durationMs:0}]};const Fe=v.map(e=>{if(e.name===te)return void 0;const l=ee(e.name,r);const o=typeof e.input?.action==="string"?e.input.action:void 0;if(!l||!o)return void 0;const i=q(r,l,o);if(!i)return void 0;const k=e.input.params??{};const u=ce(ue(l,o,k).params,i.params);if(!fe(u,i.params).ok)return void 0;const $=Y({descriptor:i,toolId:l,policy:L,isRelease:P});return{call:e,toolId:l,action:o,descriptor:i,params:u,label:Ae(l,i,u),mutates:i.effect!=="read",gated:$.verdict==="needs-approval",reason:$.verdict==="needs-approval"?$.reason:""}});const yt=Fe.some(e=>e?.gated);const U=new Map;let ze=false;let He=false;async function*gt(){ze=true;for(const e of Fe){if(!e?.gated||U.has(e.call.id))continue;if(T?.has(`${e.toolId}.${e.action}`)){U.set(e.call.id,true);continue}yield{type:"approval-required",id:e.call.id,toolId:e.toolId,action:e.action,label:e.label,description:e.descriptor.summary,reason:e.reason};const l=A();const o=await readTargetDigest({toolId:e.toolId,action:e.action,params:e.params,dispatch:d,catalog:r});const i=st(T?.has(`${e.toolId}.${e.action}`)?true:j?await j({id:e.call.id,toolId:e.toolId,action:e.action,label:e.label,description:e.descriptor.summary,reason:e.reason,params:e.params,targetDigest:o}):false);re+=A()-l;const k=i.approved;U.set(e.call.id,k);if(i.trust)T?.add(`${e.toolId}.${e.action}`);if(i.reason)z.set(e.call.id,i.reason);if(!k){He=true;return}}}for(const e of v){if(_?.aborted){if(e.name!==te){for(const s of mt(e))yield s}b.push({toolCallId:e.id,content:"Not run \u2014 the user pressed Stop before this call started.",isError:true});continue}const l=e.input?.[St];if(typeof l==="string"){b.push({toolCallId:e.id,content:`Not run \u2014 this call's arguments were not valid JSON, so nothing changed (${l.slice(0,120)}). Send the call again with compact JSON: {"action": "...", "params": {...}}.`,isError:true});continue}if(e.name===te){const s=At(e.input);if(!s.ok||!s.block){b.push({toolCallId:e.id,content:`Invalid ${te} block:
|
|
2
|
-
${
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
${
|
|
1
|
+
"use strict";import{CATALOG as Rt}from"../catalog/catalog.g";import{applyDefaults as Ee,validateParams as Ae}from"../catalog/validateParams";import{normalizeParams as me,aliasNote as Kt}from"../catalog/normalizeParams";import{fromProviderToolName as ae,toProviderTools as Qt}from"../catalog/toProviderTools";import{hashQueryKey as Xt}from"../catalog/normalizeParams";import{projectSnapshot as Zt,SNAPSHOT_ACTION as Tt}from"../catalog/snapshotReads";import{UNPARSEABLE_ARGS as eo,isRealUserMessage as It}from"../providers/types";import{isReversible as Et}from"../effects/ledger";import{classifyTurn as to,QUESTION_DECLINED as At,tapLabels as oo}from"../policy/turnScope";import{describeCall as We,decide as z}from"../policy/policy";import{redact as no,stripSelfTraffic as ro}from"../policy/redact";import{idGrounding as so,UNSEEN_ID_ADVICE as io}from"./idGrounding";import{effectFor as $t}from"./effectFor";import{digestOf as Ge}from"../effects/digest";import{buildUiBlock as ao,UI_TOOL_NAME as ye,uiProviderTool as lo}from"../blocks/uiTool";import{projectReceipt as co}from"../blocks/receipts";import{isAskBlock as uo}from"../blocks/types";import{ACQUISITION_CALLS as fo,synthesizeAskBlock as po,synthesizeShortfallBlock as ho}from"./askGate";import{couldBeToolCallEnvelope as Nt,parseTextToolCalls as mo,SALVAGE_NOTE as yo}from"./textToolCalls";import{announcesUnstartedWork as go,PLAN_ONLY_NUDGE as bo,SILENT_NUDGE as ko}from"./planOnlyStop";import{compactScreen as wo}from"./compactScreen";import{budgetForRequest as Ye,MAX_HISTORY_TOKENS as vo,MAX_REQUEST_TOKENS as So,messageSize as $e}from"./historyBudget";import{promptTokensOf as Ro,TokenCalibration as To}from"./tokenCalibration";import{truncationMarker as Io}from"./evidence";import{retrieveEvidence as Eo}from"./retrieve";import{VERIFICATION_REPAIR as Ao,verificationTrailer as $o,verifyOutcome as No}from"./verify";import{realClearTimeout as Co,realNow as N,realSetTimeout as _o}from"../realNow";const Ct=24e3;const _t=2e3;const Ot=3;function Oo(t){const r=o=>{if(o===null||typeof o!=="object")return o;if(Array.isArray(o))return o.map(r);const a=o;return Object.keys(a).sort().reduce((d,w)=>(d[w]=r(a[w]),d),{})};return t.map(o=>`${o.name}:${JSON.stringify(r(o.input??{}))}`).join("|")}const xt="[Buoy] You have now made this exact call three times in a row and gotten the same thing back. Repeating it again will not change the answer. Read the result you already have, then either try a different tool or different arguments, or tell the user plainly what you could not get and what you tried.";const xo=24;const Ne="Resume where you left off. Don't redo past changes. Read the past tool results first.";const Uo="You are out of steps. No more tools may run. Reply in the user's language with plain words. Name every write that ran, what you found, and what is still not done. Use the tool results as proof. A tap alone does not prove a save or an order worked. Do not claim the job is done if you have not checked it. Keep the reply short.";const Ut=18e4;const Po=2;const Do=1;const Lo=1e3;const Mo=250;const qo=3e4;function jo(t,r){return new Promise(o=>{if(r?.aborted)return o();const a=_o(d,t);function d(){Co(a);r?.removeEventListener("abort",d);o()}r?.addEventListener("abort",d,{once:true})})}function Pt(t){if(typeof t==="boolean")return{approved:t,trust:false};const r=t.reason?.trim();return{approved:t.approved,...r?{reason:r}:{},trust:t.trust===true&&t.approved}}function Dt(t){return t?`The user declined this change and said: "${t}". Do not retry it as proposed; take their note into account and, if a different change would fit, propose that instead.`:"The user declined this change. Do not retry it; ask what they would prefer."}function Bo(t,r){const o=t.toLowerCase();const a=r.toLowerCase();if(Math.abs(o.length-a.length)>1)return false;let d=0;while(d<o.length&&o[d]===a[d])d++;return o===a||o.slice(d+1)===a.slice(d+1)||o.slice(d+1)===a.slice(d)||o.slice(d)===a.slice(d+1)}function j(t,r,o){return t.find(a=>a.toolId===r)?.actions.find(a=>a.action===o)}const Lt="Rejected: every image URL must be one you read from a tool result in THIS conversation (images.list, a storage value, a response body). Never invent or remember URLs \u2014 read them first, then show them.";const Fo=t=>` NOTE: ${t} image ${t===1?"URL was":"URLs were"} dropped \u2014 they were not read from a tool result in this conversation, so the card the user is looking at has no picture there. Do not tell them it does; read the URLs and show it again, or say the artwork is missing.`;function zo(t){try{return JSON.stringify(t??null)}catch{return String(t)}}function Ho(t,r){if(t.length<=Ct)return t;return`${t.slice(0,Ct)}${Io(t.length,r)}`}function Jo(t){if(t===null||t===void 0)return"Done";if(Array.isArray(t))return Ve(t.length);if(typeof t==="boolean")return t?"Done":"Failed";if(typeof t==="object"){const r=t;if(r.ok===false)return typeof r.error==="string"?r.error:"Failed";if(r.found===false)return"Not found";if(r.tapped===false)return"Nothing to tap";if(r.active===false)return"Saved, but inactive in this build";for(const a of["returned","count","total"]){const d=r[a];if(typeof d==="number")return Ve(d)}const o=Object.values(r).filter(Array.isArray);if(o.length===1)return Ve(o[0].length);return"Done"}return String(t).slice(0,80)}function Ve(t){return t===1?"1 item":`${t} items`}function Mt(t){return typeof t==="object"&&t!==null&&t.ok===false}const Ke="tapElement needs nativeTag, testID or query. Read describeScreen and pass one.";function Ce(t,r,o){return t==="highlight-updates"&&r==="tapElement"&&![o.nativeTag,o.testID,o.query].some(a=>typeof a==="string"?a.trim().length>0:a!=null)}function qt(t,r,o){if(t!=="highlight-updates"||r!=="tapElement"||!o||typeof o!=="object")return o;const a=o;if(a.tapped!==false)return o;return{...a,ok:false,error:typeof a.reason==="string"?a.reason:typeof a.error==="string"?a.error:"Nothing was tapped. Read describeScreen and try again."}}export async function*runAgentTurn(t){const{provider:r,catalog:o,system:a,systemVolatile:d,model:w,maxTokens:y,dispatch:c,readSnapshot:g,storeKeyOwners:D,ledger:k,policy:L,isRelease:M,requestApproval:V,signal:C,evidence:K,trusted:$,procedures:H}=t;const Q=new Map;const x=t.calibration??new To;const de=[...t.messages].reverse().find(p=>It(p));const B=to(de?.role==="user"?de.text:"");const f=[...t.messages];const be=r.protocol==="anthropic"?t.feedOptions??{}:{};const J=be.freezeHistory===true;const U=be.notices==="system-msg";const le=(p,E=false)=>({role:"user",text:p,...r.protocol==="anthropic"?{source:"engine"}:{},...E?{systemNotice:true}:{}});const Xe=()=>{const p=f[f.length-1];return U&&(p?.role==="tool-results"||p?.role==="user"&&p.systemNotice===true)};const Ze=so(f,`${t.system}
|
|
2
|
+
${t.systemVolatile??""}${de?.role==="user"&&de.liveBlock?`
|
|
3
|
+
${de.liveBlock.text}`:""}
|
|
4
|
+
${JSON.stringify(H??[])}`);let X=Qt(o,{availableToolIds:t.availableToolIds,availableActions:t.availableActions,isRelease:M});X.push(lo());if(J)X=JSON.parse(JSON.stringify(X));const Z=L.maxSteps??xo;const et=Vo();const Bt=k.generation;const Ft=new Set(k.list().map(p=>p.id));const tt=a.length+(d?.length??0)+X.reduce((p,E)=>p+E.description.length+JSON.stringify(E.inputSchema).length,0);const ke=()=>tt+x.chars(y);let we=N()+Ut;const _e={throttled:0,overflow:0};let ee=vo;const Oe=t.seenUrls??new Set;const zt=/https?:\/\/[^\s"'\\)\]}>]+/g;let xe=false;const te=[];let ot=false;const Ue=[];let Pe=false;let nt=false;let ve=false;let rt=false;let st=false;const Ht=[...f].reverse().find(It);const Jt=/\?\s*$/.test(Ht?.text?.trim()??"");const it=p=>p.reduce((E,_)=>E+$e(_),0)<=Math.min(x.chars(ee),x.chars(So)-ke());const De="This turn has no room left. Tap Continue to go on.";for(let p=0;p<Z;p++){if(C?.aborted){yield{type:"done",stopReason:"stopped"};return f}if(N()>we){yield{type:"block",block:{id:`cap${N()}`,kind:"notice",tone:"warning",text:`This is taking too long \u2014 stopped after ${Math.round(Ut/1e3)}s. What it found so far is above.`,actions:[{label:"Continue",primary:true,send:Ne}]}};yield{type:"done",stopReason:"time-cap"};return f}const E=J&&p>0?{messages:f,droppedRounds:0,droppedPinned:0}:Ye(f,ke(),x.chars(ee),x.charsPerToken,k.liveCallIds());if(E.droppedRounds>0){f.length=0;f.push(...E.messages);yield{type:"history-trimmed",droppedRounds:E.droppedRounds,droppedPinned:E.droppedPinned}}else if(E.messages!==f){f.length=0;f.push(...E.messages)}if(Z>6&&p===Z-6){f.push(le("6 steps left. Finish what the user asked. Skip extra checks. Batch reads that do not depend on each other in one round.",Xe()))}if(J&&!it(f)){yield{type:"block",block:{id:`space${N()}`,kind:"notice",tone:"warning",text:De,actions:[{label:"Continue",primary:true,send:Ne}]}};yield{type:"done",stopReason:"incomplete"};return f}let _="";let ct=false;const ne=e=>{const l=xe&&!ct;ct=true;xe=true;return l?{type:"text",delta:e,breakBefore:true}:{type:"text",delta:e}};let R=[];const ut=new Set;let P="";let Me=true;let ue=[];let Se=false;let W;let G;let qe=0;for(;;){qe+=1;_="";R=[];P="";Me=true;ue=[];Se=false;W=void 0;G=void 0;let e;const l=tt+f.reduce((u,m)=>u+$e(m),0);for await(const u of r.send({messages:f,system:a,systemVolatile:d,tools:X,model:w,maxTokens:y,signal:C,meta:{askId:et,round:p+1,attempt:qe}})){if(u.type==="text"){_+=u.delta;if(!Me){yield ne(u.delta)}else if(Nt(_)){P+=u.delta}else{Me=false;P="";yield ne(_)}}else if(u.type==="tool-call"){R.push(u.call)}else if(u.type==="meter"){yield{type:"meter",frame:u.frame}}else if(u.type==="thinking"){ue.push(u.block);if(u.block.type==="thinking"&&u.block.thinking.trim()){yield{type:"reasoning",text:u.block.thinking}}}else if(u.type==="error"){if(u.kind==="truncated"||u.kind==="timeout"){G={message:u.message}}else if(u.problem&&(u.problem.kind==="throttled"||u.problem.kind==="overflow")&&_===""&&R.length===0&&ue.length===0){e={problem:u.problem,message:u.message}}else{yield{type:"error",message:u.message,...u.problem?.hosted?{hosted:u.problem.hosted}:{}};Se=true}}else if(u.type==="done"){W=u.outcome;if(u.usage){x.observe(l,Ro(u.usage,r.protocol));yield{type:"usage",...u.usage,model:u.model}}}}if(!e)break;const n=e.problem.kind;if(J&&n==="overflow"){G={message:De};break}const s=1+(n==="throttled"?Po:Do);const b=_e[n];let h=n==="throttled"?Math.min(e.problem.retryAfterMs??Lo*2**b,qo)+Math.floor(Math.random()*Mo):0;let O=b>=s-1||C?.aborted===true||N()+h>we;if(e.problem.hosted&&qe>=2)O=true;if(!O&&n==="overflow"){ee=Math.floor(ee/2);const u=f.reduce((fe,Y)=>fe+$e(Y),0);const m=Ye(f,ke(),x.chars(ee),x.charsPerToken,k.liveCallIds());const re=m.messages.reduce((fe,Y)=>fe+$e(Y),0);if(re>=u){O=true}else{f.length=0;f.push(...m.messages);if(m.droppedRounds>0)yield{type:"history-trimmed",droppedRounds:m.droppedRounds,droppedPinned:m.droppedPinned}}}if(O){const u=n==="throttled"?b>0?`The AI endpoint is rate-limiting requests \u2014 tried ${b+1} times.`:"The AI endpoint is rate-limiting requests.":"This conversation is too large for the AI endpoint and there was nothing older to trim. Start a new conversation, or ask a shorter question.";const m=e.problem.hosted;yield m?{type:"error",message:m.message,hosted:m}:{type:"error",message:`${u} (${e.message})`};Se=true;break}_e[n]+=1;yield{type:"retrying",reason:n,attempt:_e[n]+1,maxAttempts:s,inMs:h};if(h>0)await jo(h,C);if(C?.aborted){yield{type:"done",stopReason:"stopped"};return f}}if(W==="refused"||W==="context-limited"){yield{type:"error",message:W==="refused"?"Claude could not help with this request.":"Claude ran out of room for this turn. Please send a new message."};yield{type:"done",stopReason:"error"};return f}if(Se){if(P)yield ne(P);yield{type:"done",stopReason:"error"};return f}if(!G&&W!=="completed"){G={message:W==="output-limited"?R.length?"The model ran out of room mid-plan, so nothing was run. What it found so far is above.":"The model ran out of room before finishing this answer.":"The answer ended before the endpoint said it was finished, so nothing was run."}}if(G){if(P)yield ne(P);if(_.trim())f.push({role:"assistant",text:_});yield{type:"block",block:{id:`cut${N()}`,kind:"notice",tone:"warning",text:G.message,actions:[{label:"Continue",primary:true,send:Ne}]}};yield{type:"done",stopReason:"incomplete"};return f}if(P){const e=R.length===0?mo(P,o):void 0;if(e){for(const l of e.calls){R.push(l);ut.add(l.id)}_=e.reply;if(e.reply)yield ne(e.reply)}else{yield ne(P)}P=""}f.push({role:"assistant",text:_,toolCalls:R.length?R:void 0,thinking:ue.length?ue:void 0});if(_.trim())Ue.push(_.trim());if(R.length===0&&!ve&&!Pe&&p<Z-1&&(go(_,B==="task")||rt&&!st&&!Jt)){ve=true;f.push(le(bo));continue}if(R.length===0&&!ve&&Ue.length===0&&p<Z-1){ve=true;f.push(le(ko));continue}if(R.length===0){const e=Ue.join("\n\n");const l=ho({text:e,attempted:nt});if(l)yield{type:"block",block:l};const n=Pe||l?null:po(e);if(n){yield{type:"block",block:n};yield{type:"awaiting-user",blockId:n.id}}yield{type:"done",stopReason:n?"awaiting-user":"answered"};return f}const v=[];const Re=[];const q=e=>{if(!U)return e;Re.push(e.trim());return""};let ft=false;const pt=new Map;if(R.length>1){const e=R.every(l=>{if(l.name===ye)return false;const n=ae(l.name,o);if(!n||n==="ask-buoy")return false;const s=l.input?.action;if(typeof s!=="string"||s===Tt)return false;const b=j(o,n,s);if(!b||b.effect!=="read")return false;return z({descriptor:b,toolId:n,policy:L,isRelease:M}).verdict==="allow"});if(e){for(const l of R){const n=ae(l.name,o);const s=String(l.input.action);const b={...l.input.params??{}};const h=j(o,n,s);const O=me(n,s,b);const u=Ee(O.params,h.params);if(!Ae(u,h.params).ok)continue;const m=Promise.resolve(c(n,s,u)).catch(re=>{throw re});m.catch(()=>{});pt.set(l.id,m)}}}let je=null;const Gt=e=>{const l=ae(e.name,o)??e.name;const n=typeof e.input?.action==="string"?e.input.action:"";const s=n?j(o,l,n):void 0;const b=e.input?.params??{};return[{type:"tool-start",id:e.id,toolId:l,action:n,label:s?We(l,s,b):`${l}.${n}`,description:s?.summary??"",params:b,effect:s?.effect??"read"},{type:"tool-end",id:e.id,ok:false,summary:"stopped",durationMs:0}]};const Te=new Map;if(B==="question"){for(const e of R){if(ae(e.name,o)!=="highlight-updates"||e.input?.action!=="tapElement")continue;const l=me("highlight-updates","tapElement",e.input.params??{}).params;if(Ce("highlight-updates","tapElement",l))continue;const n=j(o,"highlight-updates","describeScreen");const s=j(o,"highlight-updates","tapElement");if(C?.aborted||!n||!s||z({descriptor:n,toolId:"highlight-updates",policy:L,isRelease:M}).verdict!=="allow"||z({descriptor:s,toolId:"highlight-updates",policy:L,isRelease:M,turnScope:B,params:l}).verdict==="refuse")continue;const b=await c("highlight-updates","describeScreen",{}).catch(()=>void 0);Te.set(e.id,oo(b,l))}}const ht=R.map(e=>{if(e.name===ye)return void 0;const l=ae(e.name,o);const n=typeof e.input?.action==="string"?e.input.action:void 0;if(!l||!n)return void 0;const s=j(o,l,n);if(!s)return void 0;const b=e.input.params??{};const h=Ee(me(l,n,b).params,s.params);if(Ce(l,n,h)||!Ae(h,s.params).ok)return void 0;const O=z({descriptor:s,toolId:l,policy:L,isRelease:M,turnScope:B,params:h,tapLabels:Te.get(e.id)});return{call:e,toolId:l,action:n,descriptor:s,params:h,label:We(l,s,h),mutates:s.effect!=="read",gated:O.verdict==="needs-approval",turnScoped:O.verdict==="needs-approval"&&O.turnScoped,reason:O.verdict==="needs-approval"?O.reason:""}});const Yt=ht.some(e=>e?.gated);const F=new Map;let mt=false;let yt=false;async function*Vt(){mt=true;for(const e of ht){if(!e?.gated||F.has(e.call.id))continue;if(!e.turnScoped&&$?.has(`${e.toolId}.${e.action}`)){F.set(e.call.id,true);continue}yield{type:"approval-required",id:e.call.id,toolId:e.toolId,action:e.action,label:e.label,description:e.descriptor.summary,reason:e.reason};const l=N();const n=await readTargetDigest({toolId:e.toolId,action:e.action,params:e.params,dispatch:c,catalog:o});const s=Pt(!e.turnScoped&&$?.has(`${e.toolId}.${e.action}`)?true:V?await V({id:e.call.id,toolId:e.toolId,action:e.action,label:e.label,description:e.descriptor.summary,reason:e.reason,params:e.params,targetDigest:n}):false);we+=N()-l;const b=s.approved;F.set(e.call.id,b);if(s.trust)$?.add(`${e.toolId}.${e.action}`);if(s.reason)Q.set(e.call.id,s.reason);if(!b){yt=true;return}}}for(const e of R){if(C?.aborted){if(e.name!==ye){for(const i of Gt(e))yield i}v.push({toolCallId:e.id,content:"Not run \u2014 the user pressed Stop before this call started.",isError:true});continue}const l=e.input?.[eo];if(typeof l==="string"){v.push({toolCallId:e.id,content:`Not run \u2014 this call's arguments were not valid JSON, so nothing changed (${l.slice(0,120)}). Send the call again with compact JSON: {"action": "...", "params": {...}}.`,isError:true});continue}if(e.name===ye){const i=ao(e.input);if(!i.ok||!i.block){v.push({toolCallId:e.id,content:`Invalid ${ye} block:
|
|
5
|
+
${i.errors.join("\n")}`+q("\n\nResend ONLY the corrected block. Do not rewrite your answer \u2014 whatever you already said this turn has been shown to the user, and saying it again repeats it on their screen."),isError:true});continue}let T=0;if(i.block.kind==="imageGrid"){const A=i.block.images.filter(I=>Oe.has(I.url));T=i.block.images.length-A.length;if(A.length===0){v.push({toolCallId:e.id,content:U?"Rejected: image URLs were not read from a tool result."+q(Lt):Lt,isError:true});continue}i.block={...i.block,images:A}}if(i.block.kind==="list"){const A=i.block.items.map(I=>{if(!I.image||Oe.has(I.image))return I;T+=1;return{...I,image:void 0}});i.block={...i.block,items:A}}yield{type:"block",block:i.block,forToolCallId:e.id};if(i.block.kind==="actions"||i.block.kind==="suggestions"||i.block.kind==="plan"&&i.block.pending){Pe=true}if(uo(i.block)){je=i.block.id;v.push({toolCallId:e.id,content:"Shown. Waiting for the user's answer \u2014 it will arrive as their next message."+q(" Do not guess it.")})}else{v.push({toolCallId:e.id,content:"Shown to the user."+q(" Don't repeat its contents in text.")+(T>0?Fo(T):"")})}continue}const n=ae(e.name,o);const s=e.input.action;const b=(i,T)=>[{type:"tool-start",id:e.id,toolId:n??e.name,action:s??"",label:i,description:"",params:e.input.params??{},effect:"read"},{type:"tool-end",id:e.id,ok:false,summary:T,durationMs:0}];if(!n){for(const i of b(e.name,"No such tool"))yield i;v.push({toolCallId:e.id,content:`There is no tool called "${e.name}". Use one of the tools you were given.`,isError:true});continue}if(!s){for(const i of b(e.name,"No action given"))yield i;v.push({toolCallId:e.id,content:`"${e.name}" needs an "action" \u2014 it is required, and it names which of this tool's actions to run. See the action list in the tool's description.`,isError:true});continue}const h=j(o,n,s);if(!h){const i=o.find(S=>S.toolId===n)?.actions.map(S=>S.action)??[];const T=o.filter(S=>S.toolId!==n&&S.actions.some(ze=>ze.action===s));const A=i.filter(S=>Bo(S,s));const I=[...T.map(S=>`${S.toolId}.${s}`),...A.map(S=>`${n}.${S}`)].slice(0,3);const ie=`${n} has no ${s}.${I.length?` Did you mean ${I.join(" or ")}?`:""}`;for(const S of b(`${n}.${s}`,I.length?ie:"No such action"))yield S;v.push({toolCallId:e.id,content:`${ie}${i.length?` Valid actions: ${i.join(", ")}.`:""}`,isError:true});continue}const O=e.input.params??{};const u=me(n,s,O);const m=Ee(u.params,h.params);if(Ce(n,s,m)){for(const i of b(`${n}.${s}`,Ke))yield i;v.push({toolCallId:e.id,content:JSON.stringify({ok:false,error:U?"tapElement needs nativeTag, testID or query."+q("Read describeScreen and pass nativeTag, testID or query."):Ke}),isError:true});continue}const re=Ae(m,h.params);if(!re.ok){v.push({toolCallId:e.id,content:`Invalid parameters for ${n}.${s}:
|
|
6
|
+
${re.errors.join("\n")}
|
|
6
7
|
|
|
7
8
|
Expected schema:
|
|
8
|
-
${JSON.stringify(
|
|
9
|
+
${JSON.stringify(h.params)}`,isError:true});continue}const fe=Kt(u.notes);const Y=h.effect!=="read"?Ze.trailer(m,!U):"";if(U&&Y)q(io);const Ie=We(n,h,m);const se=z({descriptor:h,toolId:n,policy:L,isRelease:M,turnScope:B,params:m,tapLabels:Te.get(e.id)});const pe=i=>[{type:"tool-start",id:e.id,toolId:n,action:s,label:Ie,description:h.summary,params:m,effect:h.effect},{type:"tool-end",id:e.id,ok:false,summary:i,durationMs:0}];if(se.verdict==="refuse"){for(const i of pe("refused"))yield i;v.push({toolCallId:e.id,content:se.reason,isError:true});continue}const Be=se.verdict==="needs-approval";if(Yt&&!mt&&(h.effect!=="read"||Be)){yield*Vt()}if(yt&&(h.effect!=="read"||Be)){for(const i of pe("declined"))yield i;v.push({toolCallId:e.id,content:B==="question"?At:F.get(e.id)===false?Dt(Q.get(e.id)):"Not run \u2014 the user declined another change in this same batch, so none of it was applied. Ask what they would prefer before proposing it again.",isError:true});continue}if(Be){let i;if(F.has(e.id)){i=F.get(e.id)}else if(!se.turnScoped&&$?.has(`${n}.${s}`)){i=true;F.set(e.id,true)}else{yield{type:"approval-required",id:e.id,toolId:n,action:s,label:Ie,description:h.summary,reason:se.reason};const T=N();const A=await readTargetDigest({toolId:n,action:s,params:m,dispatch:c,catalog:o});const I=Pt(V?await V({id:e.id,toolId:n,action:s,label:Ie,description:h.summary,reason:se.reason,params:m,targetDigest:A}):false);we+=N()-T;i=I.approved;F.set(e.id,i);if(I.trust)$?.add(`${n}.${s}`);if(I.reason)Q.set(e.id,I.reason)}if(!i){for(const T of pe("declined"))yield T;v.push({toolCallId:e.id,content:B==="question"?At:Dt(Q.get(e.id)),isError:true});continue}}if(fo.has(`${n}.${s}`))nt=true;if(n==="ask-buoy"&&s==="openProcedure")rt=true;yield{type:"tool-start",id:e.id,toolId:n,action:s,label:Ie,description:h.summary,params:m,effect:h.effect};const gt=N();const bt=await Qe(h,n,m,c);const Fe=await ge(h,n,m,c);if(C?.aborted){for(const i of pe("stopped"))yield i;v.push({toolCallId:e.id,content:"Not run \u2014 the user pressed Stop before this call started.",isError:true});continue}const kt=z({descriptor:h,toolId:n,policy:L,isRelease:M,turnScope:B,params:m,tapLabels:Te.get(e.id)});if(kt.verdict==="refuse"){for(const i of pe("refused"))yield i;v.push({toolCallId:e.id,content:kt.reason,isError:true});continue}try{let i=s===Tt?Zt(n,await Go(g,n),m):n==="ask-buoy"?await jt(s,k,c,K,m,H):await(pt.get(e.id)??c(n,s,m));i=qt(n,s,i);const T=no(ro(i));const A=Mt(i);if(h.effect!=="read"&&!A)st=true;const I=zo(n==="highlight-updates"&&s==="describeScreen"&&!A?wo(T):T);const ie=K&&!A&&!(n==="ask-buoy"&&s==="retrieve")?K.stash({toolId:n,action:s,params:m,capturedAt:N(),text:I}):void 0;const S=Ho(I,ie);Ze.add(I);const ze=S.length>_t?`${S.slice(0,_t)}\u2026`:S;yield{type:"tool-end",id:e.id,ok:!A,summary:Jo(i),durationMs:N()-gt,result:ze};const He=Fe===void 0||A?void 0:await ge(h,n,m,c);if(h.effect!=="read"&&n!=="ask-buoy"&&!A){const Je=k.noteExternalRevert(n,s,m);const vt=$t(n,h,m,i,bt,{before:Fe,after:He});if(vt&&!Je){const St=k.record(n,s,vt,Bt);if(St)St.callId=e.id}}const wt=A?void 0:co({toolId:n,action:s,params:m,result:T,before:Fe??bt,after:He,effect:h.effect});if(wt)yield{type:"block",block:wt,forToolCallId:e.id};const he=h.effect!=="read"&&!A?await No({toolId:n,action:s,params:m,result:i,dispatch:c,readSnapshot:g,signal:C,after:He}):void 0;if(he)yield{type:"tool-verified",id:e.id,verification:he};if(U&&he?.status==="failed")q(Ao);for(const Je of S.match(zt)??[])Oe.add(Je);v.push({toolCallId:e.id,content:S+Wo(n,h,m,D,U?q:void 0)+fe+Y+(he?$o(he,!U):"")+(ut.has(e.id)?q(yo):""),...ie?{ref:ie}:{}});if(n==="app"&&(s==="reloadApp"||s==="reload")){ft=true}}catch(i){const T=i instanceof Error?i.message:String(i);yield{type:"tool-end",id:e.id,ok:false,summary:T,durationMs:N()-gt};v.push({toolCallId:e.id,content:`Failed: ${T}${Y}`,isError:true})}}if(R.length){te.push(Oo(R));if(te.length>Ot)te.shift();const e=!ot&&te.length===Ot&&te.every(l=>l===te[0]);if(e){ot=true;const l=v[v.length-1];if(U)Re.push(xt);else if(l)l.content=`${l.content}
|
|
9
10
|
|
|
10
|
-
${
|
|
11
|
+
${xt}`}}f.push({role:"tool-results",results:v});if(Re.length)f.push(le([...new Set(Re)].join("\n\n"),true));if(je){yield{type:"awaiting-user",blockId:je};yield{type:"done",stopReason:"awaiting-user"};return f}if(ft){yield{type:"done",stopReason:"realm-died"};return f}}if(C?.aborted){yield{type:"done",stopReason:"stopped"};return f}const oe=[];let at=false;let Le=false;const ce=[...f,le(Uo,Xe())];const Wt=J?{messages:ce}:Ye(ce,ke(),x.chars(ee),x.charsPerToken,k.liveCallIds());try{if(J&&!it(ce))throw new Error(De);if(J||U||be.liveBlock==="append"||be.liveBlock==="system-msg")f.push(ce[ce.length-1]);for await(const p of r.send({messages:Wt.messages,system:a,systemVolatile:d,tools:X,toolChoice:"none",model:w,maxTokens:y,signal:C,meta:{askId:et,round:Z+1,attempt:1}})){if(p.type==="text")oe.push(p.delta);else if(p.type==="tool-call"||p.type==="error")Le=true;else if(p.type==="meter")yield{type:"meter",frame:p.frame};else if(p.type==="done"){if(p.outcome==="refused"||p.outcome==="context-limited"){yield{type:"error",message:p.outcome==="refused"?"Claude could not help with this request.":"Claude ran out of room for this turn. Please send a new message."};yield{type:"done",stopReason:"error"};return f}at=p.outcome==="completed";if(p.usage)yield{type:"usage",...p.usage,model:p.model}}}}catch{Le=true}if(C?.aborted){yield{type:"done",stopReason:"stopped"};return f}const dt=oe.join("");if(Le||!at||!dt.trim()||Nt(dt)){const p=k.list().filter(E=>!Ft.has(E.id));oe.length=0;oe.push(["I ran out of steps. The job is not done.",...p.length?["These steps ran:",...p.map(E=>`- ${E.effect.label}${E.undoneAt?" (since undone)":""}`),"This list does not prove each change worked."]:[]].join("\n"))}let lt=true;for(const p of oe){if(!p)continue;yield{type:"text",delta:p,...lt&&xe?{breakBefore:true}:{}};lt=false}f.push({role:"assistant",text:oe.join("")});yield{type:"block",block:{id:`cap${N()}`,kind:"notice",tone:"warning",text:"I ran out of steps. The job is not done. Tap Continue to go on.",actions:[{label:"Continue",primary:true,send:Ne}]}};yield{type:"done",stopReason:"step-cap"};return f}function Wo(t,r,o,a,d){if(t!=="storage"||r.effect==="read")return"";if(!a||typeof o.key!=="string")return"";const w=a[o.key];if(!w)return"";const y=`
|
|
11
12
|
|
|
12
|
-
[Buoy] This wrote to disk, but "${
|
|
13
|
+
[Buoy] This wrote to disk, but "${o.key}" is the saved copy of the live "${w}" store, so nothing on screen has changed \u2014 the app read that key once at startup and has held the state in memory since.`;if(d){d("Use zustand.rehydrate for the store named in the result to load the saved value. Use zustand.setState to change the live store. If the change was for the next app launch, no more work is needed.");return y}return y+` Call zustand.rehydrate({"storeName":"${w}"}) to make the app pick this up, or use zustand.setState next time to change the store directly. If you meant to set the value for the app's next launch, this is already done and no further action is needed.`}export{Ge as digestOf};export async function readTargetDigest(t){const r=j(t.catalog??Rt,t.toolId,t.action);if(!r||r.effect==="read")return void 0;const o=await ge(r,t.toolId,t.params,t.dispatch);if(o!==void 0)return Ge(o);const a=await Qe(r,t.toolId,t.params,t.dispatch);if(a!==void 0)return Ge(a);return void 0}export async function runGatedAction(t){const{toolId:r,action:o,dispatch:a,ledger:d,policy:w,isRelease:y}=t;const c=t.catalog??Rt;const g=j(c,r,o);if(!g){return{ok:false,error:`${r}.${o} is not in Buoy's catalog.`}}const D=me(r,o,t.params??{});const k=Ee(D.params,g.params);if(Ce(r,o,k))return{ok:false,error:Ke};const L=Ae(k,g.params);if(!L.ok){return{ok:false,error:L.errors.join("\n")}}const M=z({descriptor:g,toolId:r,policy:w,isRelease:y});if(M.verdict==="refuse"){return{ok:false,error:M.reason}}const V=await Qe(g,r,k,a);const C=await ge(g,r,k,a);const K=z({descriptor:g,toolId:r,policy:w,isRelease:y});if(K.verdict==="refuse"){return{ok:false,error:K.reason}}try{let $=r==="ask-buoy"?await jt(o,d,a,void 0,k):await a(r,o,k);$=qt(r,o,$);if(Mt($)){const H=$.error;return{ok:false,error:typeof H==="string"?H:"The tool refused this."}}if(g.effect!=="read"&&r!=="ask-buoy"){const H=d.noteExternalRevert(r,o,k);const Q=await ge(g,r,k,a);const x=$t(r,g,k,$,V,{before:C,after:Q});if(x&&!H)d.record(r,o,x)}return{ok:true,result:$}}catch($){return{ok:false,error:$ instanceof Error?$.message:String($)}}}async function jt(t,r,o,a,d,w){if(t==="retrieve"){return Eo(a,d??{})}if(t==="openProcedure"){const y=typeof d?.id==="string"?d.id:"";const c=w?.find(g=>g.id===y);if(!c){const g=(w??[]).map(D=>D.id);return{ok:false,error:g.length?`No procedure "${y}". This app's procedures: ${g.join(", ")}.`:"This app has no procedures written for it."}}return{id:c.id,title:c.title,...c.version?{version:c.version}:{},...c.requires?.length?{requires:c.requires}:{},body:c.body,note:"Follow these steps with the ordinary tools. Every write still goes through the same policy, approval and undo as any other call; a step that is refused stays refused."}}if(t==="listChanges"){const y=r.list().filter(c=>!c.undoneAt).map(c=>({id:c.id,toolId:c.toolId,action:c.action,kind:c.effect.kind,label:c.effect.label,reversible:Et(c.effect)}));return{changes:y,returned:y.length}}if(t==="undoChange"){const y=typeof d?.id==="string"?d.id:"";const c=r.list().find(D=>D.id===y);if(!c){const D=r.list().filter(k=>!k.undoneAt).map(k=>k.id);return{ok:false,error:`No change with id "${y}". Call listChanges for the current ids${D.length?` (${D.join(", ")})`:""}.`}}if(c.undoneAt)return{ok:false,error:`"${c.effect.label}" was already undone.`};if(!Et(c.effect)){return{ok:false,error:`"${c.effect.label}" can't be undone automatically \u2014 it is permanent.`}}const g=await r.revertOne(c,o);return g?{ok:false,error:g}:{ok:true,undone:{id:c.id,label:c.effect.label}}}if(t==="undoAll"){const y=await r.revertAll(o);return{ok:y.failed.length===0,...y}}throw new Error(`Unknown ask-buoy action "${t}"`)}async function Go(t,r){if(!t){throw new Error(`This app cannot read ${r}'s snapshot \u2014 Buoy's tool registry is not published here.`)}return await t(r)}async function ge(t,r,o,a){if(r==="zustand"&&t.action==="setState"){if(typeof o.storeName!=="string")return void 0;try{return await a(r,"getStoreState",{storeName:o.storeName})}catch{return void 0}}if(r==="query"&&t.action==="setQueryData"){const d=typeof o.queryHash==="string"?o.queryHash:Array.isArray(o.queryKey)?Xt(o.queryKey):void 0;if(!d)return void 0;try{return await a(r,"getQueryData",{queryHash:d})}catch{return void 0}}return void 0}async function Qe(t,r,o,a){if(t.effect==="read")return void 0;if(r==="network")return Yo(t,o,a);if(r!=="storage"||typeof o.key!=="string")return void 0;const d=t.action.startsWith("async.")?"async.getItem":t.action.startsWith("mmkv.")?"mmkv.get":void 0;if(!d)return void 0;const w={key:o.key};if(d==="mmkv.get"){if(typeof o.instanceId!=="string")return void 0;w.instanceId=o.instanceId}try{const y=await a(r,d,w);const c=y&&typeof y==="object"?y:void 0;if(c?.found===false)return void 0;const g=c?c.value:y;return{had:g!==void 0&&g!==null,value:g??void 0}}catch{return void 0}}async function Yo(t,r,o){if(t.action!=="setOverrideRuleEnabled"&&t.action!=="setOverridesEnabled"){return void 0}try{const a=await o("network","listOverrideRules",{});if(!a||typeof a!=="object")return void 0;if(t.action==="setOverridesEnabled"){return{had:true,value:String(a.enabled!==false)}}const d=(a.rules??[]).find(w=>w&&w.id===r.id);if(!d)return void 0;return{had:true,value:String(d.enabled!==false)}}catch{return void 0}}function Vo(){const t="abcdefghijklmnopqrstuvwxyz0123456789";let r="ask_";for(let o=0;o<20;o++)r+=t[Math.floor(Math.random()*t.length)];return r}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{describe as y,expect as t,it as a,vi as f}from"vitest";import{runAgentTurn as g}from"./runAgentTurn";import{buildSystemPrompt as v}from"./systemPrompt";import{EffectLedger as w}from"../effects/ledger";const q=[{toolId:"sample",title:"Sample",summary:"Test tools",actions:[{action:"read",summary:"Read",effect:"read",release:"works",params:{type:"object",properties:{}}},{action:"write",summary:"Write",effect:"write",release:"works",params:{type:"object",properties:{}}}]}];const r="6 steps left.";async function i(e,s,o=false){const l=[];const p=f.fn(async()=>({value:1}));const u=f.fn(async()=>false);const d=new w;const m={protocol:"anthropic",async*send(n){l.push(structuredClone(n.messages));const c=l.length;if(c===s||n.toolChoice==="none")yield{type:"text",delta:"The check is done."};else yield{type:"tool-call",call:{id:`call-${c}`,name:"buoy_sample",input:{action:o&&c===19?"write":"read",params:{}}}};yield{type:"done",outcome:"completed"}}};const h=[];for await(const n of g({provider:m,catalog:q,system:"s",model:"m",maxTokens:100,messages:[{role:"user",text:o?"Why is the value wrong?":"Check the value."}],dispatch:p,requestApproval:u,ledger:d,policy:{maxSteps:e,requireApproval:[]},isRelease:false}))h.push(n);return{requests:l,dispatch:p,requestApproval:u,ledger:d,events:h}}y("step budget note",()=>{a("asks for reads with no shared result to share a round",()=>{const e=v({catalog:[],isRelease:false,readOnly:false,requireApproval:[]});t(e).toContain("Batch reads in one round when none needs the other\u2019s result.")});a("adds one note with six rounds left and keeps the cap",async()=>{const e=await i(24,99);t(e.requests).toHaveLength(25);t(JSON.stringify(e.requests[17])).not.toContain(r);for(const s of e.requests.slice(18)){t(s.filter(o=>o.role==="user"&&o.text.startsWith(r))).toHaveLength(1)}t(e.dispatch).toHaveBeenCalledTimes(24);t(e.events).toContainEqual({type:"done",stopReason:"step-cap"})});a.each([[24,3],[6,99]])("leaves short jobs alone (%i steps)",async(e,s)=>{t(JSON.stringify((await i(e,s)).requests)).not.toContain(r)});a("keeps question writes behind approval after the note",async()=>{const e=await i(24,20,true);t(e.requestApproval).toHaveBeenCalledOnce();t(e.dispatch).not.toHaveBeenCalledWith("sample","write",t.anything());t(e.ledger.list()).toEqual([])});a("starts a fresh budget for each turn",async()=>{for(let e=0;e<2;e++){const s=await i(8,4);t(JSON.stringify(s.requests[1])).not.toContain(r);t(s.requests[2].filter(o=>o.role==="user"&&o.text.startsWith(r))).toHaveLength(1)}})});
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
The people who talk to you are usually NOT developers \u2014 QA testers, customer support, product managers. Write like you are talking to a smart colleague who does not read code. Never show raw JSON, stack traces, or internal ids unless they ask. Say what you found and what it means.
|
|
4
4
|
|
|
5
5
|
You can read the app's live state and change it, through the Buoy tools available to you. Prefer looking before acting: read the relevant state first so your change matches how this app actually stores things, rather than guessing at a shape.`);t.push(`HOW TO WORK
|
|
6
|
-
- Chain tools freely to answer one question.
|
|
6
|
+
- Chain tools freely to answer one question. Read what you need.
|
|
7
|
+
- Batch reads in one round when none needs the other\u2019s result.
|
|
7
8
|
- BIG RESULTS ARE KEPT IN FULL. A result over 24,000 characters is cut, and the cut ends with a marker naming a ref (\`[truncated \u2014 \u2026; ref ev_7]\`). Everything past the cut is still here: call ask-buoy.retrieve with that ref \u2014 bare, to see the shape; then with a path ("stats.0.base_stat") or a pattern ("base_stat") to read the part you need. Never tell the user a value is unreadable because it sat past the cut, and never ask them for a narrower slice. The same goes for \`[earlier result \u2014 \u2026; ref ev_3]\`: that result was compressed out of your memory, and retrieve re-reads EXACTLY what you saw at the time \u2014 the value before a write, say \u2014 while calling the tool again gives the app's current value. Choose by which one the question is about.
|
|
8
9
|
- OPENING A SCREEN IS NOT SEEING IT. A navigation proves where the app is, not what the screen shows \u2014 a page can be locked, empty or showing an error. Read it (describeScreen) before you say what is on it. When asked to open a screen for an id that may not exist, open it anyway and report what it shows: how the screen handles that is the test.
|
|
9
10
|
- A DEEP LINK IS TESTED WITH lifecycle.openUrl (the app's own link handling runs). route-events navigate skips it, so never say a link "opened" when you navigated.
|
|
@@ -14,6 +15,8 @@ You can read the app's live state and change it, through the Buoy tools availabl
|
|
|
14
15
|
- When you change something, say plainly what you changed, in the user's language ("I put the double burger out of stock at store 100"), not in tool terms.
|
|
15
16
|
- A WRITE'S RESULT IS CHECKED, AND THE CHECK TELLS YOU WHAT ACTUALLY HAPPENED. Some writes come back with a trailer: \`[Buoy] Verified: \u2026\` means the app was read back and shows the requested state \u2014 say it plainly. \`[Buoy] Unverified: \u2026\` means the write landed but the outcome is not visible yet (an override installed that no request has used) \u2014 do the thing the trailer names (a refetch, a visit to the screen) before saying the screen shows it, or say honestly that it will show on the next load. \`[Buoy] Check FAILED: \u2026\` means the app does NOT hold what you wrote: read the current state, work out why, and correct ONCE with a different, targeted change \u2014 never repeat the same write, never report the outcome as done.
|
|
16
17
|
- If a tool returns an empty list, check whether the tool needed to be recording first. Several Buoy tools only capture while something is watching them, so an empty result often means "nothing was recording", not "nothing happened". Say which one it is.
|
|
18
|
+
- An old error can show where to look. It does not prove this try failed. For a vague "it is broken, fix it" with no steps, read first. Find the fault now, then ask which step fails. Do not place an order or change test settings for that vague ask. If the user asked you to place an order, refund it, or make a change, do it and check the result. Do not ask them to grant that same step again. Still obey tool approval checks.
|
|
19
|
+
- If you place an order or change state to test a fix, say so plainly in your reply.
|
|
17
20
|
- A COUNT IS NOT A BUG UNTIL YOU HAVE CHECKED THE CLOCK. recentRequests and the events timeline accumulate for as long as the app has been open, so "23 calls to /orders/pending" is a polling interval times minutes, not one screen load, until the timestamps say otherwise. Before you call anything a bug, run the one check that would make it NOT one \u2014 the timestamps across the window, the query's refetchInterval, whether capture was on the whole time, whether the screen actually shows the wrong thing \u2014 and say in the finding what you checked. A finding you did not check is a guess wearing a card, and the person reading it cannot tell the two apart.
|
|
18
21
|
- WHAT YOU READ IS A RECORD OF WHAT HAS ALREADY HAPPENED, never a list of what this app has. The query cache holds what the app has already fetched THIS SESSION; recentRequests what it has already called; console, events, images and assets only what was recording or has already rendered. So a SHORT list means "nobody has been there yet", not "that is all there is" \u2014 and you have to work out which, exactly as you would for an empty one. Four of something is not the answer to "show me all of them". It is four of them, and a gap.
|
|
19
22
|
- GETTING DATA THAT ISN'T LOADED YET. You are inside the running app and you can drive it, so a gap is a job, not an answer. In order, cheapest first:
|
|
@@ -53,7 +56,7 @@ This app has configured you to look but not touch. Answer questions and diagnose
|
|
|
53
56
|
Actions classed as ${i.join(" and ")} need the user to tap Approve before they run. Buoy shows that card itself the moment you call one \u2014 so CALL THE ACTION rather than asking for permission in words or with a confirm block first. If the result says the user declined, respect it and ask what they would prefer instead.
|
|
54
57
|
|
|
55
58
|
Say what you are ABOUT to do, never what you have done, until the result is back. "Setting the Lugia line to 3" is right; "Bumped the Lugia line to 3" before the tap is a claim you cannot make \u2014 the user may decline, and then your own answer contradicts itself in the same breath.`)}else if(h){t.push(`APPROVALS ARE OFF
|
|
56
|
-
The user has turned approval prompts off on this device.
|
|
59
|
+
The user has turned approval prompts off on this device. Task writes can run at once, even wipes and resets. A question still needs a tap before a data change. So: do only what was actually asked, one change at a time; never run a destructive action speculatively, in bulk, or to find out what it does; and read the current state before you overwrite it. Say what you are about to change in the same message you change it, then report what happened. If a request is ambiguous and one reading is destructive, ask with buoy_ui choice instead of guessing \u2014 a broad "reset everything" always is.`)}t.push(`SHOWING THINGS
|
|
57
60
|
You have a \`buoy_ui\` tool. Use it instead of prose whenever the answer is a set of things, a picture, a comparison, a number, or a decision the user has to make:
|
|
58
61
|
- A request could mean two things \u2192 buoy_ui choice. Never guess between real alternatives.
|
|
59
62
|
- You are about to write APP STATE in a shape you did not read (a store, a cache entry), or act as a user you invented \u2192 buoy_ui confirm first. A mock response the user described ("200 with an error in the body", "a 503 with a maintenance message") is not a guess: write it \u2014 for an endpoint you have not seen, a plain {"error": "...", "message": "..."} body \u2014 and say what you sent. Do NOT pre-confirm destructive tool actions: just call them. Buoy shows the user its own approval card and runs the action only if they tap Approve, so asking first makes them answer twice.
|