@buoy-gg/ask-buoy 7.0.54 → 7.0.55
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/components/AskBuoyModal.js +2 -2
- package/lib/commonjs/store/hosted.test.js +1 -1
- package/lib/module/components/AskBuoyModal.js +2 -2
- package/lib/module/store/hosted.test.js +1 -1
- package/lib/typescript/store/askBuoyStore.d.ts +1 -1
- package/lib/web/index.mjs +1 -1
- package/package.json +7 -7
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.AskBuoyModal=AskBuoyModal;var _react=require("react");var _reactNative=require("react-native");var _agentCore=require("@buoy-gg/agent-core");var _sharedUi=require("@buoy-gg/shared-ui");var _blocks=require("../blocks");var _ApprovalCard=require("./ApprovalCard");var _ChangesBar=require("./ChangesBar");var _ruleStates=require("../blocks/ruleStates");var _Composer=require("./Composer");var _KeyboardPad=require("./KeyboardPad");var _AskBuoyHeader=require("./AskBuoyHeader");var _AskBuoySettings=require("./AskBuoySettings");var _EmptyState=require("./EmptyState");var _MessageList=require("./MessageList");var _QueuedMessages=require("./QueuedMessages");var _askBuoyStore=require("../store/askBuoyStore");var _ticketPrompt=require("../store/ticketPrompt");var _useAskBuoy=require("../store/useAskBuoy");var _useAskBuoySettings=require("../store/useAskBuoySettings");var _askBuoySettings=require("../store/askBuoySettings");var _jsxRuntime=require("react/jsx-runtime");function AskBuoyModal({visible:f,onClose:v,onBack:S,onMinimize:u,persistenceKey:$,config:r}){const{state:e,send:c,stop:h,reset:d,undoThenReset:x,approve:E,retry:O,answer:A,runAction:B,editQueued:L,removeQueued:N,configured:k,toolCount:D,availableToolIds:K}=(0,_useAskBuoy.useAskBuoy)(r);const[V,z]=(0,_react.useState)(false);const U=(0,_react.useCallback)(()=>{h();v()},[h,v]);const F=(0,_react.useCallback)(()=>_askBuoyStore.askBuoyStore.clearDraft(),[]);const[g,b]=(0,_react.useState)(false);const Q=(0,_react.useCallback)(()=>b(false),[]);const W=(0,_react.useCallback)(()=>b(true),[]);const a=!f;const C=!!e.pending||!!e.awaitingBlockId;const o=e.messages[e.messages.length-1];const y=(0,_react.useRef)(false);const T=(0,_react.useRef)(e.busy);(0,_react.useEffect)(()=>{if(a&&T.current&&!e.busy)y.current=true;if(!a)y.current=false;T.current=e.busy},[a,e.busy]);(0,_react.useEffect)(()=>{if(!a){(0,_sharedUi.setToolAttention)("ask-buoy",null);return}if(C){(0,_sharedUi.setToolAttention)("ask-buoy",{kind:"needsInput",label:e.pending?"Needs your approval":"Has a question for you"})}else if(y.current&&o?.role==="assistant"&&o.error){(0,_sharedUi.setToolAttention)("ask-buoy",{kind:"failed",label:"The last turn failed"})}else if(y.current&&!e.busy){(0,_sharedUi.setToolAttention)("ask-buoy",{kind:"done",label:"Finished"})}else{(0,_sharedUi.setToolAttention)("ask-buoy",null)}},[a,C,e.busy,e.pending,o]);(0,_react.useEffect)(()=>()=>(0,_sharedUi.setToolAttention)("ask-buoy",null),[]);(0,_react.useEffect)(()=>{if(e.pending&&g)b(false)},[e.pending,g]);const G=(0,_sharedUi.useSafeAreaInsets)({minBottom:16});const[J,Y]=(0,_react.useState)("bottomSheet");const X=J==="floating"?0:G.bottom;const Z=(0,_react.useCallback)(()=>{const t=_askBuoyStore.askBuoyStore.getLedger();if(!t)return;void(async()=>{const s=await t.revertAll(_sharedUi.dispatchToolAction);if(s.failed.length===0){_askBuoyStore.askBuoyStore.setNotice(null);return}const n=s.failed.map(l=>`${l.label} (${l.error})`).join("; ");_askBuoyStore.askBuoyStore.setNotice(`Couldn't undo ${s.failed.length} change${s.failed.length===1?"":"s"} \u2014 ${n}`)})()},[]);const ee=(0,_react.useCallback)(()=>z(t=>!t),[]);const i=(0,_react.useRef)(new _reactNative.Animated.Value(560)).current;const m=(0,_react.useRef)(false);const
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.AskBuoyModal=AskBuoyModal;var _react=require("react");var _reactNative=require("react-native");var _agentCore=require("@buoy-gg/agent-core");var _sharedUi=require("@buoy-gg/shared-ui");var _blocks=require("../blocks");var _ApprovalCard=require("./ApprovalCard");var _ChangesBar=require("./ChangesBar");var _ruleStates=require("../blocks/ruleStates");var _Composer=require("./Composer");var _KeyboardPad=require("./KeyboardPad");var _AskBuoyHeader=require("./AskBuoyHeader");var _AskBuoySettings=require("./AskBuoySettings");var _EmptyState=require("./EmptyState");var _MessageList=require("./MessageList");var _QueuedMessages=require("./QueuedMessages");var _askBuoyStore=require("../store/askBuoyStore");var _ticketPrompt=require("../store/ticketPrompt");var _useAskBuoy=require("../store/useAskBuoy");var _useAskBuoySettings=require("../store/useAskBuoySettings");var _askBuoySettings=require("../store/askBuoySettings");var _jsxRuntime=require("react/jsx-runtime");function AskBuoyModal({visible:f,onClose:v,onBack:S,onMinimize:u,persistenceKey:$,config:r}){const{state:e,send:c,stop:h,reset:d,undoThenReset:x,approve:E,retry:O,answer:A,runAction:B,editQueued:L,removeQueued:N,configured:k,toolCount:D,availableToolIds:K}=(0,_useAskBuoy.useAskBuoy)(r);const[V,z]=(0,_react.useState)(false);const U=(0,_react.useCallback)(()=>{h();v()},[h,v]);const F=(0,_react.useCallback)(()=>_askBuoyStore.askBuoyStore.clearDraft(),[]);const[g,b]=(0,_react.useState)(false);const Q=(0,_react.useCallback)(()=>b(false),[]);const W=(0,_react.useCallback)(()=>b(true),[]);const a=!f;const C=!!e.pending||!!e.awaitingBlockId;const o=e.messages[e.messages.length-1];const y=(0,_react.useRef)(false);const T=(0,_react.useRef)(e.busy);(0,_react.useEffect)(()=>{if(a&&T.current&&!e.busy)y.current=true;if(!a)y.current=false;T.current=e.busy},[a,e.busy]);(0,_react.useEffect)(()=>{if(!a){(0,_sharedUi.setToolAttention)("ask-buoy",null);return}if(C){(0,_sharedUi.setToolAttention)("ask-buoy",{kind:"needsInput",label:e.pending?"Needs your approval":"Has a question for you"})}else if(y.current&&o?.role==="assistant"&&o.error){(0,_sharedUi.setToolAttention)("ask-buoy",{kind:"failed",label:"The last turn failed"})}else if(y.current&&!e.busy){(0,_sharedUi.setToolAttention)("ask-buoy",{kind:"done",label:"Finished"})}else{(0,_sharedUi.setToolAttention)("ask-buoy",null)}},[a,C,e.busy,e.pending,o]);(0,_react.useEffect)(()=>()=>(0,_sharedUi.setToolAttention)("ask-buoy",null),[]);(0,_react.useEffect)(()=>{if(e.pending&&g)b(false)},[e.pending,g]);const G=(0,_sharedUi.useSafeAreaInsets)({minBottom:16});const[J,Y]=(0,_react.useState)("bottomSheet");const X=J==="floating"?0:G.bottom;const Z=(0,_react.useCallback)(()=>{const t=_askBuoyStore.askBuoyStore.getLedger();if(!t)return;void(async()=>{const s=await t.revertAll(_sharedUi.dispatchToolAction);if(s.failed.length===0){_askBuoyStore.askBuoyStore.setNotice(null);return}const n=s.failed.map(l=>`${l.label} (${l.error})`).join("; ");_askBuoyStore.askBuoyStore.setNotice(`Couldn't undo ${s.failed.length} change${s.failed.length===1?"":"s"} \u2014 ${n}`)})()},[]);const ee=(0,_react.useCallback)(()=>z(t=>!t),[]);const i=(0,_react.useRef)(new _reactNative.Animated.Value(560)).current;const m=(0,_react.useRef)(false);const w=(0,_react.useCallback)(()=>{if(m.current)return;m.current=true;i.stopAnimation(t=>{const s=typeof t==="number"&&t>200?t:560;_reactNative.Animated.timing(i,{toValue:120,duration:220,useNativeDriver:false}).start(()=>{setTimeout(()=>{_reactNative.Animated.spring(i,{toValue:s,friction:9,useNativeDriver:false}).start(()=>{m.current=false})},2500)})})},[i]);const p=(0,_react.useMemo)(()=>{const t=e.messages[e.messages.length-1];if(!t||t.role!=="assistant")return"";const s=t.blocks.filter(n=>n.block.kind==="peek").length;return s>0?`${t.id}:${s}`:""},[e.messages]);const j=(0,_react.useRef)("");(0,_react.useEffect)(()=>{if(p&&p!==j.current&&r?.peek!==false)w();j.current=p},[p,w,r]);const te=(0,_react.useCallback)(()=>{const t=_askBuoyStore.askBuoyStore.getLedger();const s=t?.list().filter(n=>!n.undoneAt&&(0,_agentCore.isReversible)(n.effect)).length??0;if(s===0){d();return}_reactNative.Alert.alert("Undo changes first?",`Ask Buoy made ${s} change${s===1?"":"s"} that can still be undone. Clearing the conversation loses the only automatic undo for ${s===1?"it":"them"}.`,[{text:"Undo, then clear",onPress:()=>{void(async()=>{const n=await x();if(n.cleared)return;const l=n.reverted+n.failed.length;const ce=n.failed.map(R=>`${R.label} (${R.error})`).join("\n");_reactNative.Alert.alert(`Couldn't undo ${n.failed.length} of ${l} change${l===1?"":"s"}`,`${ce}
|
|
2
2
|
|
|
3
|
-
Still applied in the app. Clear the conversation anyway?`,[{text:"Keep conversation",style:"cancel"},{text:"Clear anyway",style:"destructive",onPress:d}])})()}},{text:"Clear anyway",style:"destructive",onPress:d},{text:"Cancel",style:"cancel"}])},[d,x]);const se=(0,_react.useMemo)(()=>{const t=e.messages[e.messages.length-1];if(!t||t.role!=="assistant"||!t.streaming)return null;return t.toolCalls.find(s=>s.status==="running")?.label??null},[e.messages]);const q=(0,_ChangesBar.useLedgerEntries)(_askBuoyStore.askBuoyStore.getLedger());const _=(0,_react.useMemo)(()=>(0,_ruleStates.ruleStatesFrom)(q),[q]);const oe=(0,_react.useMemo)(()=>({answer:A,runAction:(t,s)=>{if(t.minimize){u?.({mode:"bottomSheet"});return}return B(t,s)},answered:e.answers,actionState:e.actionState,busy:e.busy,supports:{openTool:false,minimize:!!u,dispatch:true},rules:_}),[A,B,u,e.answers,e.actionState,e.busy,_]);const ne=!e.busy&&o?.role==="assistant"&&!!o.error;const P=!e.busy&&o?.role==="assistant"&&o.stopped&&!o.error?o.id:null;const re=(0,_askBuoyStore.queuePauseReason)(e);const{showThinking:M,readOnly:H,hostedNoticeSeen:ae}=(0,_useAskBuoySettings.useAskBuoySettings)();const I=r?.hosted===true&&!ae;const ie=(0,_react.useCallback)(()=>(0,_MessageList.transcriptToText)(e.messages,M),[e.messages,M]);const le=(0,_react.useCallback)(()=>c(_ticketPrompt.TICKET_PROMPT),[c]);const ue=g?(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:Q}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:"Settings",subtitle:"Ask Buoy",testID:"buoy-ask-buoy-settings-title"})]}):(0,_jsxRuntime.jsx)(_AskBuoyHeader.AskBuoyHeader,{configured:k,busy:e.busy,failed:ne,activeLabel:se,awaitingApproval:!!e.pending,startedAt:o?.role==="assistant"&&o.streaming?o.startedAt??null:null,stopping:e.stopping,readOnly:H,toolCount:D,hasMessages:e.messages.length>0,onBack:S,onReset:te,getTranscript:ie,onOpenSettings:W,onTicket:le});return(0,_jsxRuntime.jsx)(_sharedUi.JsModal,{visible:f,onClose:U,disableScrollWrapper:true,background:false,initialHeight:560,animatedHeight:i,header:{customContent:ue,showToggleButton:false},onBack:S,onMinimize:u,onModeChange:Y,persistenceKey:$??"buoy-ask-buoy",enablePersistence:true,children:(0,_jsxRuntime.jsx)(_blocks.BlockHandlersProvider,{value:oe,children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[styles.root,{paddingBottom:X}],children:[(0,_jsxRuntime.jsx)(_ChangesBar.ChangesBar,{ledger:_askBuoyStore.askBuoyStore.getLedger(),onUndoAll:Z,expanded:V,onToggle:ee}),I&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.notice,testID:"buoy-ask-buoy-hosted-notice",children:[(0,_jsxRuntime.jsx)(_sharedUi.Info,{size:14,color:_sharedUi.night.color.info}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:{flex:1,gap:8},children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.noticeText,children:"Hosted Ask Buoy (beta) sends your question, and the app data it needs to answer, to OpenAI through OpenRouter. Buoy keeps the cost of each answer, never the chat. Your plan includes
|
|
3
|
+
Still applied in the app. Clear the conversation anyway?`,[{text:"Keep conversation",style:"cancel"},{text:"Clear anyway",style:"destructive",onPress:d}])})()}},{text:"Clear anyway",style:"destructive",onPress:d},{text:"Cancel",style:"cancel"}])},[d,x]);const se=(0,_react.useMemo)(()=>{const t=e.messages[e.messages.length-1];if(!t||t.role!=="assistant"||!t.streaming)return null;return t.toolCalls.find(s=>s.status==="running")?.label??null},[e.messages]);const q=(0,_ChangesBar.useLedgerEntries)(_askBuoyStore.askBuoyStore.getLedger());const _=(0,_react.useMemo)(()=>(0,_ruleStates.ruleStatesFrom)(q),[q]);const oe=(0,_react.useMemo)(()=>({answer:A,runAction:(t,s)=>{if(t.minimize){u?.({mode:"bottomSheet"});return}return B(t,s)},answered:e.answers,actionState:e.actionState,busy:e.busy,supports:{openTool:false,minimize:!!u,dispatch:true},rules:_}),[A,B,u,e.answers,e.actionState,e.busy,_]);const ne=!e.busy&&o?.role==="assistant"&&!!o.error;const P=!e.busy&&o?.role==="assistant"&&o.stopped&&!o.error?o.id:null;const re=(0,_askBuoyStore.queuePauseReason)(e);const{showThinking:M,readOnly:H,hostedNoticeSeen:ae}=(0,_useAskBuoySettings.useAskBuoySettings)();const I=r?.hosted===true&&!ae;const ie=(0,_react.useCallback)(()=>(0,_MessageList.transcriptToText)(e.messages,M),[e.messages,M]);const le=(0,_react.useCallback)(()=>c(_ticketPrompt.TICKET_PROMPT),[c]);const ue=g?(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:Q}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:"Settings",subtitle:"Ask Buoy",testID:"buoy-ask-buoy-settings-title"})]}):(0,_jsxRuntime.jsx)(_AskBuoyHeader.AskBuoyHeader,{configured:k,busy:e.busy,failed:ne,activeLabel:se,awaitingApproval:!!e.pending,startedAt:o?.role==="assistant"&&o.streaming?o.startedAt??null:null,stopping:e.stopping,readOnly:H,toolCount:D,hasMessages:e.messages.length>0,onBack:S,onReset:te,getTranscript:ie,onOpenSettings:W,onTicket:le});return(0,_jsxRuntime.jsx)(_sharedUi.JsModal,{visible:f,onClose:U,disableScrollWrapper:true,background:false,initialHeight:560,animatedHeight:i,header:{customContent:ue,showToggleButton:false},onBack:S,onMinimize:u,onModeChange:Y,persistenceKey:$??"buoy-ask-buoy",enablePersistence:true,children:(0,_jsxRuntime.jsx)(_blocks.BlockHandlersProvider,{value:oe,children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[styles.root,{paddingBottom:X}],children:[(0,_jsxRuntime.jsx)(_ChangesBar.ChangesBar,{ledger:_askBuoyStore.askBuoyStore.getLedger(),onUndoAll:Z,expanded:V,onToggle:ee}),I&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.notice,testID:"buoy-ask-buoy-hosted-notice",children:[(0,_jsxRuntime.jsx)(_sharedUi.Info,{size:14,color:_sharedUi.night.color.info}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:{flex:1,gap:8},children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.noticeText,children:"Hosted Ask Buoy (beta) sends your question, and the app data it needs to answer, to OpenAI through OpenRouter. Buoy keeps the cost of each answer, never the chat. Your plan includes weekly credits."}),(0,_jsxRuntime.jsx)(_reactNative.Pressable,{testID:"buoy-ask-buoy-hosted-notice-ok",accessibilityRole:"button",onPress:()=>_askBuoySettings.askBuoySettings.set("hostedNoticeSeen",true),style:({pressed:t})=>[styles.noticeButton,t&&{opacity:.7}],children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.noticeButtonText,children:"Got it"})})]})]}),e.notice&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.notice,testID:"buoy-ask-buoy-notice",children:[(0,_jsxRuntime.jsx)(_sharedUi.Info,{size:14,color:_sharedUi.night.color.info}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.noticeText,children:e.notice})]}),(0,_jsxRuntime.jsx)(_KeyboardPad.KeyboardPad,{children:(0,_jsxRuntime.jsx)(_sharedUi.FeatureGate,{featureName:"Ask Buoy",children:g?(0,_jsxRuntime.jsx)(_AskBuoySettings.AskBuoySettings,{}):(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_MessageList.MessageList,{messages:e.messages,onRetry:O,waitingOnUser:!!e.pending||!!e.awaitingBlockId,ListEmptyComponent:(0,_jsxRuntime.jsx)(_EmptyState.EmptyState,{configured:k,onPick:c,availableToolIds:K,readOnly:H||!!r?.policy?.readOnly})}),(0,_jsxRuntime.jsx)(_QueuedMessages.QueuedMessages,{items:e.queued,paused:re,onEdit:L,onRemove:N}),e.pending&&(0,_jsxRuntime.jsx)(_ApprovalCard.ApprovalCard,{pending:e.pending,onAnswer:E}),(0,_jsxRuntime.jsx)(_Composer.Composer,{onSend:c,onStop:h,busy:e.busy,draft:e.draft,onDraftTaken:F,disabled:!k||I,focusKey:P,canFocus:f,placeholder:P?"Tell it what to do differently\u2026":e.awaitingBlockId?"Pick an option above, or type\u2026":void 0})]})})})]})})})}const styles=_reactNative.StyleSheet.create({root:{flex:1,backgroundColor:"transparent"},notice:{flexDirection:"row",alignItems:"flex-start",gap:8,paddingHorizontal:_sharedUi.night.gutter,paddingVertical:10,backgroundColor:_sharedUi.night.color.infoSoft},noticeText:{flex:1,color:_sharedUi.night.color.text,fontSize:_sharedUi.night.font.caption,lineHeight:18},noticeButton:{alignSelf:"flex-start",paddingHorizontal:12,paddingVertical:6,borderRadius:_sharedUi.night.radius.row,backgroundColor:_sharedUi.night.color.button},noticeButtonText:{color:_sharedUi.night.color.text,fontSize:_sharedUi.night.font.caption,fontWeight:"600"}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var _vitest=require("vitest");var _agentCore=require("@buoy-gg/agent-core");var _askBuoyStore=require("./askBuoyStore");(0,_vitest.beforeEach)(()=>_askBuoyStore.askBuoyStore.reset());(0,_vitest.describe)("hosted credits and errors",()=>{(0,_vitest.it)("shows credits from the closing frame, and keeps the last number while one is pending",()=>{const e=_askBuoyStore.askBuoyStore.startAssistant();_askBuoyStore.askBuoyStore.applyEvent(e,{type:"meter",frame:{phase:"start",request_id:"rq_1"}});_askBuoyStore.askBuoyStore.applyEvent(e,{type:"meter",frame:{phase:"end",request_id:"rq_1",credits_left:1240,credits_reset:"2026-11-
|
|
1
|
+
"use strict";var _vitest=require("vitest");var _agentCore=require("@buoy-gg/agent-core");var _askBuoyStore=require("./askBuoyStore");(0,_vitest.beforeEach)(()=>_askBuoyStore.askBuoyStore.reset());(0,_vitest.describe)("hosted credits and errors",()=>{(0,_vitest.it)("shows credits from the closing frame, and keeps the last number while one is pending",()=>{const e=_askBuoyStore.askBuoyStore.startAssistant();_askBuoyStore.askBuoyStore.applyEvent(e,{type:"meter",frame:{phase:"start",request_id:"rq_1"}});_askBuoyStore.askBuoyStore.applyEvent(e,{type:"meter",frame:{phase:"end",request_id:"rq_1",credits_left:1240,credits_reset:"2026-11-16",charged:1}});(0,_vitest.expect)(_askBuoyStore.askBuoyStore.getState().credits).toEqual({left:1240,resetAt:"2026-11-16",pending:false});_askBuoyStore.askBuoyStore.applyEvent(e,{type:"meter",frame:{phase:"end",request_id:"rq_2",credits_left:null,credits_reset:null,charged:null,pending:true}});(0,_vitest.expect)(_askBuoyStore.askBuoyStore.getState().credits).toEqual({left:1240,resetAt:"2026-11-16",pending:true})});(0,_vitest.it)("keeps a hosted error's code and request id on the message",()=>{const e=_askBuoyStore.askBuoyStore.startAssistant();_askBuoyStore.askBuoyStore.applyEvent(e,{type:"error",message:"You used this week's credits. They come back on Oct 19.",hosted:{code:"credits_out",message:"x",request_id:"rq_9",action:_agentCore.HOSTED_ERRORS.credits_out.action,retry_after_ms:null,reset_at:"2026-11-16T00:00:00Z",help_url:null}});const t=_askBuoyStore.askBuoyStore.getState().messages.find(r=>r.id===e);(0,_vitest.expect)(t.error).toMatch(/credits/);(0,_vitest.expect)(t.errorInfo).toEqual({code:"credits_out",requestId:"rq_9",action:"none",resetAt:"2026-11-16T00:00:00Z",helpUrl:null})});(0,_vitest.it)("an error from your own endpoint has no hosted info",()=>{const e=_askBuoyStore.askBuoyStore.startAssistant();_askBuoyStore.askBuoyStore.applyEvent(e,{type:"error",message:"HTTP 500"});(0,_vitest.expect)(_askBuoyStore.askBuoyStore.getState().messages.find(t=>t.id===e).errorInfo).toBeUndefined()})});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";import{useCallback as a,useEffect as y,useMemo as
|
|
1
|
+
"use strict";import{useCallback as a,useEffect as y,useMemo as T,useRef as h,useState as H}from"react";import{Alert as Y,Animated as R,Pressable as Ie,StyleSheet as Pe,Text as O,View as B}from"react-native";import{isReversible as $e}from"@buoy-gg/agent-core";import{dispatchToolAction as He,FeatureGate as Re,Info as X,JsModal as Oe,ModalHeader as D,night as i,setToolAttention as u,useSafeAreaInsets as De}from"@buoy-gg/shared-ui";import{BlockHandlersProvider as Ne}from"../blocks";import{ApprovalCard as Le}from"./ApprovalCard";import{ChangesBar as Me,useLedgerEntries as Ke}from"./ChangesBar";import{ruleStatesFrom as Ee}from"../blocks/ruleStates";import{Composer as je}from"./Composer";import{KeyboardPad as qe}from"./KeyboardPad";import{AskBuoyHeader as ze}from"./AskBuoyHeader";import{AskBuoySettings as Fe}from"./AskBuoySettings";import{EmptyState as Ve}from"./EmptyState";import{MessageList as Ue,transcriptToText as _e}from"./MessageList";import{QueuedMessages as Qe}from"./QueuedMessages";import{askBuoyStore as l,queuePauseReason as We}from"../store/askBuoyStore";import{TICKET_PROMPT as Ge}from"../store/ticketPrompt";import{useAskBuoy as Je}from"../store/useAskBuoy";import{useAskBuoySettings as Ye}from"../store/useAskBuoySettings";import{askBuoySettings as Xe}from"../store/askBuoySettings";import{jsx as o,jsxs as d,Fragment as Ze}from"react/jsx-runtime";export function AskBuoyModal({visible:x,onClose:N,onBack:L,onMinimize:b,persistenceKey:Z,config:g}){const{state:e,send:k,stop:C,reset:S,undoThenReset:M,approve:ee,retry:te,answer:K,runAction:E,editQueued:oe,removeQueued:se,configured:I,toolCount:ne,availableToolIds:re}=Je(g);const[ae,ie]=H(false);const le=a(()=>{C();N()},[C,N]);const ce=a(()=>l.clearDraft(),[]);const[v,P]=H(false);const ue=a(()=>P(false),[]);const de=a(()=>P(true),[]);const p=!x;const j=!!e.pending||!!e.awaitingBlockId;const n=e.messages[e.messages.length-1];const w=h(false);const q=h(e.busy);y(()=>{if(p&&q.current&&!e.busy)w.current=true;if(!p)w.current=false;q.current=e.busy},[p,e.busy]);y(()=>{if(!p){u("ask-buoy",null);return}if(j){u("ask-buoy",{kind:"needsInput",label:e.pending?"Needs your approval":"Has a question for you"})}else if(w.current&&n?.role==="assistant"&&n.error){u("ask-buoy",{kind:"failed",label:"The last turn failed"})}else if(w.current&&!e.busy){u("ask-buoy",{kind:"done",label:"Finished"})}else{u("ask-buoy",null)}},[p,j,e.busy,e.pending,n]);y(()=>()=>u("ask-buoy",null),[]);y(()=>{if(e.pending&&v)P(false)},[e.pending,v]);const ge=De({minBottom:16});const[pe,fe]=H("bottomSheet");const me=pe==="floating"?0:ge.bottom;const ye=a(()=>{const t=l.getLedger();if(!t)return;void(async()=>{const s=await t.revertAll(He);if(s.failed.length===0){l.setNotice(null);return}const r=s.failed.map(m=>`${m.label} (${m.error})`).join("; ");l.setNotice(`Couldn't undo ${s.failed.length} change${s.failed.length===1?"":"s"} \u2014 ${r}`)})()},[]);const he=a(()=>ie(t=>!t),[]);const f=h(new R.Value(560)).current;const $=h(false);const z=a(()=>{if($.current)return;$.current=true;f.stopAnimation(t=>{const s=typeof t==="number"&&t>200?t:560;R.timing(f,{toValue:120,duration:220,useNativeDriver:false}).start(()=>{setTimeout(()=>{R.spring(f,{toValue:s,friction:9,useNativeDriver:false}).start(()=>{$.current=false})},2500)})})},[f]);const A=T(()=>{const t=e.messages[e.messages.length-1];if(!t||t.role!=="assistant")return"";const s=t.blocks.filter(r=>r.block.kind==="peek").length;return s>0?`${t.id}:${s}`:""},[e.messages]);const F=h("");y(()=>{if(A&&A!==F.current&&g?.peek!==false)z();F.current=A},[A,z,g]);const be=a(()=>{const t=l.getLedger();const s=t?.list().filter(r=>!r.undoneAt&&$e(r.effect)).length??0;if(s===0){S();return}Y.alert("Undo changes first?",`Ask Buoy made ${s} change${s===1?"":"s"} that can still be undone. Clearing the conversation loses the only automatic undo for ${s===1?"it":"them"}.`,[{text:"Undo, then clear",onPress:()=>{void(async()=>{const r=await M();if(r.cleared)return;const m=r.reverted+r.failed.length;const Ce=r.failed.map(J=>`${J.label} (${J.error})`).join("\n");Y.alert(`Couldn't undo ${r.failed.length} of ${m} change${m===1?"":"s"}`,`${Ce}
|
|
2
2
|
|
|
3
|
-
Still applied in the app. Clear the conversation anyway?`,[{text:"Keep conversation",style:"cancel"},{text:"Clear anyway",style:"destructive",onPress:S}])})()}},{text:"Clear anyway",style:"destructive",onPress:S},{text:"Cancel",style:"cancel"}])},[S,M]);const ke=
|
|
3
|
+
Still applied in the app. Clear the conversation anyway?`,[{text:"Keep conversation",style:"cancel"},{text:"Clear anyway",style:"destructive",onPress:S}])})()}},{text:"Clear anyway",style:"destructive",onPress:S},{text:"Cancel",style:"cancel"}])},[S,M]);const ke=T(()=>{const t=e.messages[e.messages.length-1];if(!t||t.role!=="assistant"||!t.streaming)return null;return t.toolCalls.find(s=>s.status==="running")?.label??null},[e.messages]);const V=Ke(l.getLedger());const U=T(()=>Ee(V),[V]);const Se=T(()=>({answer:K,runAction:(t,s)=>{if(t.minimize){b?.({mode:"bottomSheet"});return}return E(t,s)},answered:e.answers,actionState:e.actionState,busy:e.busy,supports:{openTool:false,minimize:!!b,dispatch:true},rules:U}),[K,E,b,e.answers,e.actionState,e.busy,U]);const ve=!e.busy&&n?.role==="assistant"&&!!n.error;const _=!e.busy&&n?.role==="assistant"&&n.stopped&&!n.error?n.id:null;const we=We(e);const{showThinking:Q,readOnly:W,hostedNoticeSeen:Ae}=Ye();const G=g?.hosted===true&&!Ae;const Te=a(()=>_e(e.messages,Q),[e.messages,Q]);const Be=a(()=>k(Ge),[k]);const xe=v?d(D,{children:[o(D.Navigation,{onBack:ue}),o(D.Content,{title:"Settings",subtitle:"Ask Buoy",testID:"buoy-ask-buoy-settings-title"})]}):o(ze,{configured:I,busy:e.busy,failed:ve,activeLabel:ke,awaitingApproval:!!e.pending,startedAt:n?.role==="assistant"&&n.streaming?n.startedAt??null:null,stopping:e.stopping,readOnly:W,toolCount:ne,hasMessages:e.messages.length>0,onBack:L,onReset:be,getTranscript:Te,onOpenSettings:de,onTicket:Be});return o(Oe,{visible:x,onClose:le,disableScrollWrapper:true,background:false,initialHeight:560,animatedHeight:f,header:{customContent:xe,showToggleButton:false},onBack:L,onMinimize:b,onModeChange:fe,persistenceKey:Z??"buoy-ask-buoy",enablePersistence:true,children:o(Ne,{value:Se,children:d(B,{style:[c.root,{paddingBottom:me}],children:[o(Me,{ledger:l.getLedger(),onUndoAll:ye,expanded:ae,onToggle:he}),G&&d(B,{style:c.notice,testID:"buoy-ask-buoy-hosted-notice",children:[o(X,{size:14,color:i.color.info}),d(B,{style:{flex:1,gap:8},children:[o(O,{style:c.noticeText,children:"Hosted Ask Buoy (beta) sends your question, and the app data it needs to answer, to OpenAI through OpenRouter. Buoy keeps the cost of each answer, never the chat. Your plan includes weekly credits."}),o(Ie,{testID:"buoy-ask-buoy-hosted-notice-ok",accessibilityRole:"button",onPress:()=>Xe.set("hostedNoticeSeen",true),style:({pressed:t})=>[c.noticeButton,t&&{opacity:.7}],children:o(O,{style:c.noticeButtonText,children:"Got it"})})]})]}),e.notice&&d(B,{style:c.notice,testID:"buoy-ask-buoy-notice",children:[o(X,{size:14,color:i.color.info}),o(O,{style:c.noticeText,children:e.notice})]}),o(qe,{children:o(Re,{featureName:"Ask Buoy",children:v?o(Fe,{}):d(Ze,{children:[o(Ue,{messages:e.messages,onRetry:te,waitingOnUser:!!e.pending||!!e.awaitingBlockId,ListEmptyComponent:o(Ve,{configured:I,onPick:k,availableToolIds:re,readOnly:W||!!g?.policy?.readOnly})}),o(Qe,{items:e.queued,paused:we,onEdit:oe,onRemove:se}),e.pending&&o(Le,{pending:e.pending,onAnswer:ee}),o(je,{onSend:k,onStop:C,busy:e.busy,draft:e.draft,onDraftTaken:ce,disabled:!I||G,focusKey:_,canFocus:x,placeholder:_?"Tell it what to do differently\u2026":e.awaitingBlockId?"Pick an option above, or type\u2026":void 0})]})})})]})})})}const c=Pe.create({root:{flex:1,backgroundColor:"transparent"},notice:{flexDirection:"row",alignItems:"flex-start",gap:8,paddingHorizontal:i.gutter,paddingVertical:10,backgroundColor:i.color.infoSoft},noticeText:{flex:1,color:i.color.text,fontSize:i.font.caption,lineHeight:18},noticeButton:{alignSelf:"flex-start",paddingHorizontal:12,paddingVertical:6,borderRadius:i.radius.row,backgroundColor:i.color.button},noticeButtonText:{color:i.color.text,fontSize:i.font.caption,fontWeight:"600"}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{beforeEach as a,describe as d,expect as r,it as o}from"vitest";import{HOSTED_ERRORS as i}from"@buoy-gg/agent-core";import{askBuoyStore as e}from"./askBuoyStore";a(()=>e.reset());d("hosted credits and errors",()=>{o("shows credits from the closing frame, and keeps the last number while one is pending",()=>{const t=e.startAssistant();e.applyEvent(t,{type:"meter",frame:{phase:"start",request_id:"rq_1"}});e.applyEvent(t,{type:"meter",frame:{phase:"end",request_id:"rq_1",credits_left:1240,credits_reset:"2026-11-
|
|
1
|
+
"use strict";import{beforeEach as a,describe as d,expect as r,it as o}from"vitest";import{HOSTED_ERRORS as i}from"@buoy-gg/agent-core";import{askBuoyStore as e}from"./askBuoyStore";a(()=>e.reset());d("hosted credits and errors",()=>{o("shows credits from the closing frame, and keeps the last number while one is pending",()=>{const t=e.startAssistant();e.applyEvent(t,{type:"meter",frame:{phase:"start",request_id:"rq_1"}});e.applyEvent(t,{type:"meter",frame:{phase:"end",request_id:"rq_1",credits_left:1240,credits_reset:"2026-11-16",charged:1}});r(e.getState().credits).toEqual({left:1240,resetAt:"2026-11-16",pending:false});e.applyEvent(t,{type:"meter",frame:{phase:"end",request_id:"rq_2",credits_left:null,credits_reset:null,charged:null,pending:true}});r(e.getState().credits).toEqual({left:1240,resetAt:"2026-11-16",pending:true})});o("keeps a hosted error's code and request id on the message",()=>{const t=e.startAssistant();e.applyEvent(t,{type:"error",message:"You used this week's credits. They come back on Oct 19.",hosted:{code:"credits_out",message:"x",request_id:"rq_9",action:i.credits_out.action,retry_after_ms:null,reset_at:"2026-11-16T00:00:00Z",help_url:null}});const s=e.getState().messages.find(n=>n.id===t);r(s.error).toMatch(/credits/);r(s.errorInfo).toEqual({code:"credits_out",requestId:"rq_9",action:"none",resetAt:"2026-11-16T00:00:00Z",helpUrl:null})});o("an error from your own endpoint has no hosted info",()=>{const t=e.startAssistant();e.applyEvent(t,{type:"error",message:"HTTP 500"});r(e.getState().messages.find(s=>s.id===t).errorInfo).toBeUndefined()})});
|
|
@@ -7,7 +7,7 @@ export interface HostedErrorInfo {
|
|
|
7
7
|
resetAt: string | null;
|
|
8
8
|
helpUrl: string | null;
|
|
9
9
|
}
|
|
10
|
-
/** Hosted credits this
|
|
10
|
+
/** Hosted credits this week, from the gateway's /v1/usage. */
|
|
11
11
|
export interface HostedUsage {
|
|
12
12
|
creditsLeft: number;
|
|
13
13
|
grantCredits: number;
|
package/lib/web/index.mjs
CHANGED
|
@@ -12,4 +12,4 @@ ${t.delta}`:r.text+t.delta,breakBeforeNextText:false};case"reasoning":return{...
|
|
|
12
12
|
`)}function sd({testID:e,label:t,getText:o}){let{copied:r,copy:n}=to(o);return be(m,{testID:e,accessibilityRole:"button",accessibilityLabel:t,onPress:n,style:({pressed:i})=>[f.copyBtn,f.iconBtn,i&&f.copyPressed],children:[r?x(xn,{size:11,color:k.color.accent}):x(Pc,{size:11,color:k.color.textSecondary}),x(l,{style:[f.copyText,r&&f.copiedText],children:r?"Copied":t})]})}function cd({message:e}){let{showThinking:t}=Ht(),o=Tt(()=>An(b.getState().messages,t),[t]),r=Es(e);return be(s,{style:f.transcriptActions,children:[r?x(l,{style:f.turnFooter,testID:"buoy-ask-buoy-turn-footer",children:r}):null,x(sd,{testID:"buoy-ask-buoy-copy",label:"Copy conversation",getText:o})]})}var dd=Hc(function({message:e,onRetry:t,waitingOnUser:o}){if(e.role==="user")return x(od,{message:e});if(e.system)return x(td,{message:e});let r=!e.streaming&&!e.error;return be(s,{children:[x(rd,{message:e,onRetry:t,waitingOnUser:o}),r&&x(cd,{message:e})]})});function Bn({messages:e,ListHeaderComponent:t,ListEmptyComponent:o,ListFooterComponent:r,onRetry:n,waitingOnUser:i}){let a=Po(null),h=Po(true),u=Po(false),g=Tt(ke=>{let{contentOffset:Je,contentSize:Re,layoutMeasurement:Qe}=ke.nativeEvent,Ge=Re.height-Qe.height-Je.y;h.current=Ge<80},[]),y=Tt(()=>{u.current=true},[]),C=Tt(ke=>{u.current&&g(ke)},[g]),j=Tt(ke=>{u.current=false,g(ke)},[g]),M=Tt(()=>{h.current&&(a.current?.scrollToEnd({animated:false}),requestAnimationFrame(()=>a.current?.scrollToEnd({animated:false})))},[]),X=e[e.length-1],ee=X?`${X.id}:${X.text.length}:${X.toolCalls.length}:${X.error??""}`:"";return Lc(()=>{M()},[ee,M]),x(tr,{ref:a,testID:"buoy-ask-buoy-messages-scroll",style:f.list,contentContainerStyle:f.listContent,data:e,keyExtractor:ke=>ke.id,renderItem:({item:ke})=>x(dd,{message:ke,onRetry:ke.id===X?.id?n:void 0,waitingOnUser:ke.id===X?.id?i:false}),onScroll:C,onScrollBeginDrag:y,onScrollEndDrag:j,onMomentumScrollEnd:g,scrollEventThrottle:32,onContentSizeChange:M,keyboardShouldPersistTaps:"handled",keyboardDismissMode:"interactive",ListHeaderComponent:t,ListEmptyComponent:o,ListFooterComponent:r})}var f=w.create({list:{flex:1,backgroundColor:"transparent"},listContent:{paddingHorizontal:k.gutter,paddingTop:12,paddingBottom:16,gap:16,flexGrow:1},userWrap:{alignItems:"flex-end"},userBubble:{maxWidth:"84%",paddingHorizontal:14,paddingVertical:9,borderRadius:18,borderBottomRightRadius:6,backgroundColor:k.color.accentFill,borderWidth:w.hairlineWidth,borderColor:k.color.accentBorder},userText:{color:k.color.text,fontSize:k.font.row,lineHeight:21},systemLine:{alignSelf:"stretch",paddingHorizontal:12,paddingVertical:7,borderRadius:k.radius.row,backgroundColor:k.color.fill},systemText:{color:k.color.textSecondary,fontSize:k.font.micro,lineHeight:16,textAlign:"center"},assistantWrap:{alignItems:"stretch",gap:10},blocks:{gap:10},waitingRow:{flexDirection:"row",alignItems:"center",gap:8},waitingText:{color:k.color.textSecondary,fontSize:k.font.caption},assistantText:{color:k.color.text,fontSize:k.font.row,lineHeight:22,paddingHorizontal:2},card:{borderRadius:k.radius.card,borderWidth:w.hairlineWidth,borderColor:k.color.border,backgroundColor:k.color.surface,overflow:"hidden"},row:{paddingHorizontal:12,paddingVertical:10},rowDivider:{borderBottomWidth:w.hairlineWidth,borderBottomColor:k.color.separator},rowLine:{flexDirection:"row",alignItems:"center",gap:10},markSlot:{width:18,height:18,alignItems:"center",justifyContent:"center"},mark:{width:16,height:16,borderRadius:8,alignItems:"center",justifyContent:"center"},markOk:{backgroundColor:k.color.accent},markFailed:{backgroundColor:k.color.danger},markRefused:{backgroundColor:k.color.warning},markNotRun:{borderWidth:1.5,borderColor:k.color.textTertiary},markNotRunBar:{width:7,height:1.5,borderRadius:1,backgroundColor:k.color.textSecondary},rowTitle:{flex:1,color:k.color.text,fontSize:k.font.body,lineHeight:19},rowStatus:{color:k.color.textTertiary,fontSize:k.font.caption,maxWidth:120},rowStatusDanger:{color:k.color.danger},rowStatusWarning:{color:k.color.warning},rowStatusOk:{color:k.color.accent},rowVerified:{color:k.color.accent},rowDetail:{paddingLeft:28,paddingTop:8,gap:4},rowTool:{color:k.color.textTertiary,fontSize:k.font.micro,fontWeight:"600",letterSpacing:.8},rowDescription:{color:k.color.textSecondary,fontSize:k.font.caption,lineHeight:18},rowFailure:{color:k.color.danger,fontSize:k.font.caption,lineHeight:18},rowReceipts:{paddingTop:10,gap:10},groupBody:{paddingLeft:14,backgroundColor:k.color.fill},stoppedBox:{alignItems:"flex-start",gap:2,paddingHorizontal:12,paddingVertical:8},stoppedText:{color:k.color.textTertiary,fontSize:k.font.caption,lineHeight:18},errorBox:{flexDirection:"row",alignItems:"flex-start",gap:8,paddingHorizontal:12,paddingVertical:10,borderRadius:k.radius.row,backgroundColor:k.color.dangerSoft},errorBody:{flex:1,gap:4},errorText:{color:k.color.danger,fontSize:k.font.caption,fontWeight:"600",lineHeight:18},errorDetail:{color:k.color.textSecondary,fontSize:k.font.micro,lineHeight:15},retry:{alignSelf:"flex-start",marginTop:6,paddingHorizontal:14,paddingVertical:7,borderRadius:k.radius.row,backgroundColor:k.color.button},retryPressed:{opacity:.7},retryText:{color:k.color.accent,fontSize:k.font.caption,fontWeight:"600"},transcriptActions:{flexDirection:"row",alignItems:"center",gap:10,marginTop:8},turnFooter:{flex:1,color:k.color.textTertiary,fontSize:k.font.micro,fontVariant:["tabular-nums"]},copyBtn:{paddingHorizontal:10,paddingVertical:5,borderRadius:k.radius.badge,backgroundColor:k.color.fill},iconBtn:{flexDirection:"row",alignItems:"center",gap:4,paddingHorizontal:8},copyPressed:{opacity:.7},copyText:{color:k.color.textSecondary,fontSize:k.font.micro,fontWeight:"600"},copiedText:{color:k.color.accent}});import{night as Le,X as ud}from"@buoy-gg/shared-ui/web";import{jsx as Lt,jsxs as Vo}from"react/jsx-runtime";var pd=132;function hd({items:e,paused:t,onEdit:o,onRemove:r}){return e.length===0?null:Vo(s,{style:Pe.wrap,testID:"buoy-ask-buoy-queued",children:[Lt(Fe,{style:Pe.list,contentContainerStyle:Pe.listContent,keyboardShouldPersistTaps:"handled",children:e.map((n,i)=>Vo(s,{style:[Pe.row,t!==null&&Pe.rowPaused],children:[Vo(m,{testID:`buoy-ask-buoy-queue-item-${i}`,accessibilityRole:"button",accessibilityLabel:`Edit queued message: ${n.text}`,onPress:()=>o(n.id),style:({pressed:a})=>[Pe.rowBody,a&&Pe.pressed],children:[Lt(l,{style:Pe.ordinal,children:i===0?"NEXT":"THEN"}),Lt(l,{style:Pe.text,numberOfLines:3,children:n.text})]}),Lt(m,{testID:`buoy-ask-buoy-queue-remove-${i}`,accessibilityRole:"button",accessibilityLabel:"Remove queued message",onPress:()=>r(n.id),hitSlop:8,style:({pressed:a})=>[Pe.remove,a&&Pe.pressed],children:Lt(ud,{size:11,color:Le.color.textTertiary})})]},n.id))}),Lt(l,{style:[Pe.hint,t!==null&&Pe.hintPaused],testID:"buoy-ask-buoy-queue-hint",children:t??"Sends one at a time when this turn finishes \xB7 tap to edit"})]})}var Pe=w.create({wrap:{paddingHorizontal:Le.gutter,paddingTop:8,gap:6,backgroundColor:Le.color.bg},list:{maxHeight:pd},listContent:{gap:6},row:{flexDirection:"row",alignItems:"center",borderRadius:Le.radius.row,borderWidth:w.hairlineWidth,borderColor:Le.color.border,backgroundColor:Le.color.surface},rowPaused:{borderColor:Le.color.warning},rowBody:{flex:1,flexDirection:"row",alignItems:"center",gap:8,paddingVertical:7,paddingLeft:10},ordinal:{color:Le.color.textTertiary,fontSize:9,fontWeight:"700",letterSpacing:.6,width:30},text:{flex:1,color:Le.color.textSecondary,fontSize:Le.font.caption,lineHeight:17},remove:{width:32,height:32,alignItems:"center",justifyContent:"center"},pressed:{opacity:.7},hint:{color:Le.color.textTertiary,fontSize:Le.font.micro},hintPaused:{color:Le.color.warning}});var gd="Summarize this conversation as a bug ticket, using one buoy_ui finding block and nothing else. Title it with the problem in one line. Evidence rows, in this order: Steps (what we did), Expected, Observed, Build (dev or release, platform), Evidence (the exact requests, statuses, logs or values you actually saw this conversation), Still applied (overrides or changes from your changes list that are still live, or 'none'). Only facts you observed in this conversation; write 'unknown' where you don't know rather than guessing, and put anything you inferred rather than saw in a final row labelled Inference. Do not run any tool or change anything to produce this.";import{Fragment as fd,jsx as pe,jsxs as Pt}from"react/jsx-runtime";function No({visible:e,onClose:t,onBack:o,onMinimize:r,persistenceKey:n,config:i}){let{state:a,send:h,stop:u,reset:g,undoThenReset:y,approve:C,retry:j,answer:M,runAction:X,editQueued:ee,removeQueued:ke,configured:Je,toolCount:Re,availableToolIds:Qe}=bn(i),[Ge,ft]=ho(false),bt=Me(()=>{u(),t()},[u,t]),It=Me(()=>b.clearDraft(),[]),[qe,at]=ho(false),st=Me(()=>at(false),[]),Ye=Me(()=>at(true),[]),yt=!e,Xe=!!a.pending||!!a.awaitingBlockId,Ie=a.messages[a.messages.length-1],zt=Wt(false),Qt=Wt(a.busy);Ot(()=>{yt&&Qt.current&&!a.busy&&(zt.current=true),yt||(zt.current=false),Qt.current=a.busy},[yt,a.busy]),Ot(()=>{if(!yt){At("ask-buoy",null);return}Xe?At("ask-buoy",{kind:"needsInput",label:a.pending?"Needs your approval":"Has a question for you"}):zt.current&&Ie?.role==="assistant"&&Ie.error?At("ask-buoy",{kind:"failed",label:"The last turn failed"}):zt.current&&!a.busy?At("ask-buoy",{kind:"done",label:"Finished"}):At("ask-buoy",null)},[yt,Xe,a.busy,a.pending,Ie]),Ot(()=>()=>At("ask-buoy",null),[]),Ot(()=>{a.pending&&qe&&at(false)},[a.pending,qe]);let so=Ui({minBottom:16}),[Gt,co]=ho("bottomSheet"),uo=Gt==="floating"?0:so.bottom,po=Me(()=>{let K=b.getLedger();K&&(async()=>{let ie=await K.revertAll(Mi);if(ie.failed.length===0){b.setNotice(null);return}let Dt=ie.failed.map(Yt=>`${Yt.label} (${Yt.error})`).join("; ");b.setNotice(`Couldn't undo ${ie.failed.length} change${ie.failed.length===1?"":"s"} \u2014 ${Dt}`)})()},[]),S=Me(()=>ft(K=>!K),[]),V=Wt(new ce.Value(560)).current,_=Wt(false),ne=Me(()=>{_.current||(_.current=true,V.stopAnimation(K=>{let ie=typeof K=="number"&&K>200?K:560;ce.timing(V,{toValue:120,duration:220,useNativeDriver:false}).start(()=>{setTimeout(()=>{ce.spring(V,{toValue:ie,friction:9,useNativeDriver:false}).start(()=>{_.current=false})},2500)})}))},[V]),Se=Zt(()=>{let K=a.messages[a.messages.length-1];if(!K||K.role!=="assistant")return"";let ie=K.blocks.filter(Dt=>Dt.block.kind==="peek").length;return ie>0?`${K.id}:${ie}`:""},[a.messages]),te=Wt("");Ot(()=>{Se&&Se!==te.current&&i?.peek!==false&&ne(),te.current=Se},[Se,ne,i]);let Vn=Me(()=>{let K=b.getLedger()?.list().filter(ie=>!ie.undoneAt&&qi(ie.effect)).length??0;if(K===0){g();return}fo.alert("Undo changes first?",`Ask Buoy made ${K} change${K===1?"":"s"} that can still be undone. Clearing the conversation loses the only automatic undo for ${K===1?"it":"them"}.`,[{text:"Undo, then clear",onPress:()=>{(async()=>{let ie=await y();if(ie.cleared)return;let Dt=ie.reverted+ie.failed.length,Yt=ie.failed.map(Go=>`${Go.label} (${Go.error})`).join(`
|
|
13
13
|
`);fo.alert(`Couldn't undo ${ie.failed.length} of ${Dt} change${Dt===1?"":"s"}`,`${Yt}
|
|
14
14
|
|
|
15
|
-
Still applied in the app. Clear the conversation anyway?`,[{text:"Keep conversation",style:"cancel"},{text:"Clear anyway",style:"destructive",onPress:g}])})()}},{text:"Clear anyway",style:"destructive",onPress:g},{text:"Cancel",style:"cancel"}])},[g,y]),Nn=Zt(()=>{let K=a.messages[a.messages.length-1];return!K||K.role!=="assistant"||!K.streaming?null:K.toolCalls.find(ie=>ie.status==="running")?.label??null},[a.messages]),Fo=Nr(b.getLedger()),_o=Zt(()=>ys(Fo),[Fo]),jn=Zt(()=>({answer:M,runAction:(K,ie)=>{if(K.minimize){r?.({mode:"bottomSheet"});return}return X(K,ie)},answered:a.answers,actionState:a.actionState,busy:a.busy,supports:{openTool:false,minimize:!!r,dispatch:true},rules:_o}),[M,X,r,a.answers,a.actionState,a.busy,_o]),qn=!a.busy&&Ie?.role==="assistant"&&!!Ie.error,Uo=!a.busy&&Ie?.role==="assistant"&&Ie.stopped&&!Ie.error?Ie.id:null,Mn=ln(a),{showThinking:Ko,readOnly:Jo,hostedNoticeSeen:Fn}=Ht(),Qo=i?.hosted===true&&!Fn,_n=Me(()=>An(a.messages,Ko),[a.messages,Ko]),Un=Me(()=>h(gd),[h]),Kn=qe?Pt(bo,{children:[pe(bo.Navigation,{onBack:st}),pe(bo.Content,{title:"Settings",subtitle:"Ask Buoy",testID:"buoy-ask-buoy-settings-title"})]}):pe(Yr,{configured:Je,busy:a.busy,failed:qn,activeLabel:Nn,awaitingApproval:!!a.pending,startedAt:Ie?.role==="assistant"&&Ie.streaming?Ie.startedAt??null:null,stopping:a.stopping,readOnly:Jo,toolCount:Re,hasMessages:a.messages.length>0,onBack:o,onReset:Vn,getTranscript:_n,onOpenSettings:Ye,onTicket:Un});return pe(_i,{visible:e,onClose:bt,disableScrollWrapper:true,background:false,initialHeight:560,animatedHeight:V,header:{customContent:Kn,showToggleButton:false},onBack:o,onMinimize:r,onModeChange:co,persistenceKey:n??"buoy-ask-buoy",enablePersistence:true,children:pe(ar,{value:jn,children:Pt(s,{style:[Ct.root,{paddingBottom:uo}],children:[pe(jr,{ledger:b.getLedger(),onUndoAll:po,expanded:Ge,onToggle:S}),Qo&&Pt(s,{style:Ct.notice,testID:"buoy-ask-buoy-hosted-notice",children:[pe(nr,{size:14,color:_e.color.info}),Pt(s,{style:{flex:1,gap:8},children:[pe(l,{style:Ct.noticeText,children:"Hosted Ask Buoy (beta) sends your question, and the app data it needs to answer, to OpenAI through OpenRouter. Buoy keeps the cost of each answer, never the chat. Your plan includes
|
|
15
|
+
Still applied in the app. Clear the conversation anyway?`,[{text:"Keep conversation",style:"cancel"},{text:"Clear anyway",style:"destructive",onPress:g}])})()}},{text:"Clear anyway",style:"destructive",onPress:g},{text:"Cancel",style:"cancel"}])},[g,y]),Nn=Zt(()=>{let K=a.messages[a.messages.length-1];return!K||K.role!=="assistant"||!K.streaming?null:K.toolCalls.find(ie=>ie.status==="running")?.label??null},[a.messages]),Fo=Nr(b.getLedger()),_o=Zt(()=>ys(Fo),[Fo]),jn=Zt(()=>({answer:M,runAction:(K,ie)=>{if(K.minimize){r?.({mode:"bottomSheet"});return}return X(K,ie)},answered:a.answers,actionState:a.actionState,busy:a.busy,supports:{openTool:false,minimize:!!r,dispatch:true},rules:_o}),[M,X,r,a.answers,a.actionState,a.busy,_o]),qn=!a.busy&&Ie?.role==="assistant"&&!!Ie.error,Uo=!a.busy&&Ie?.role==="assistant"&&Ie.stopped&&!Ie.error?Ie.id:null,Mn=ln(a),{showThinking:Ko,readOnly:Jo,hostedNoticeSeen:Fn}=Ht(),Qo=i?.hosted===true&&!Fn,_n=Me(()=>An(a.messages,Ko),[a.messages,Ko]),Un=Me(()=>h(gd),[h]),Kn=qe?Pt(bo,{children:[pe(bo.Navigation,{onBack:st}),pe(bo.Content,{title:"Settings",subtitle:"Ask Buoy",testID:"buoy-ask-buoy-settings-title"})]}):pe(Yr,{configured:Je,busy:a.busy,failed:qn,activeLabel:Nn,awaitingApproval:!!a.pending,startedAt:Ie?.role==="assistant"&&Ie.streaming?Ie.startedAt??null:null,stopping:a.stopping,readOnly:Jo,toolCount:Re,hasMessages:a.messages.length>0,onBack:o,onReset:Vn,getTranscript:_n,onOpenSettings:Ye,onTicket:Un});return pe(_i,{visible:e,onClose:bt,disableScrollWrapper:true,background:false,initialHeight:560,animatedHeight:V,header:{customContent:Kn,showToggleButton:false},onBack:o,onMinimize:r,onModeChange:co,persistenceKey:n??"buoy-ask-buoy",enablePersistence:true,children:pe(ar,{value:jn,children:Pt(s,{style:[Ct.root,{paddingBottom:uo}],children:[pe(jr,{ledger:b.getLedger(),onUndoAll:po,expanded:Ge,onToggle:S}),Qo&&Pt(s,{style:Ct.notice,testID:"buoy-ask-buoy-hosted-notice",children:[pe(nr,{size:14,color:_e.color.info}),Pt(s,{style:{flex:1,gap:8},children:[pe(l,{style:Ct.noticeText,children:"Hosted Ask Buoy (beta) sends your question, and the app data it needs to answer, to OpenAI through OpenRouter. Buoy keeps the cost of each answer, never the chat. Your plan includes weekly credits."}),pe(m,{testID:"buoy-ask-buoy-hosted-notice-ok",accessibilityRole:"button",onPress:()=>pt.set("hostedNoticeSeen",true),style:({pressed:K})=>[Ct.noticeButton,K&&{opacity:.7}],children:pe(l,{style:Ct.noticeButtonText,children:"Got it"})})]})]}),a.notice&&Pt(s,{style:Ct.notice,testID:"buoy-ask-buoy-notice",children:[pe(nr,{size:14,color:_e.color.info}),pe(l,{style:Ct.noticeText,children:a.notice})]}),pe(Is,{children:pe(Fi,{featureName:"Ask Buoy",children:qe?pe(yn,{}):Pt(fd,{children:[pe(Bn,{messages:a.messages,onRetry:j,waitingOnUser:!!a.pending||!!a.awaitingBlockId,ListEmptyComponent:pe(Ac,{configured:Je,onPick:h,availableToolIds:Qe,readOnly:Jo||!!i?.policy?.readOnly})}),pe(hd,{items:a.queued,paused:Mn,onEdit:ee,onRemove:ke}),a.pending&&pe(Wr,{pending:a.pending,onAnswer:C}),pe(_r,{onSend:h,onStop:u,busy:a.busy,draft:a.draft,onDraftTaken:It,disabled:!Je||Qo,focusKey:Uo,canFocus:e,placeholder:Uo?"Tell it what to do differently\u2026":a.awaitingBlockId?"Pick an option above, or type\u2026":void 0})]})})})]})})})}var Ct=w.create({root:{flex:1,backgroundColor:"transparent"},notice:{flexDirection:"row",alignItems:"flex-start",gap:8,paddingHorizontal:_e.gutter,paddingVertical:10,backgroundColor:_e.color.infoSoft},noticeText:{flex:1,color:_e.color.text,fontSize:_e.font.caption,lineHeight:18},noticeButton:{alignSelf:"flex-start",paddingHorizontal:12,paddingVertical:6,borderRadius:_e.radius.row,backgroundColor:_e.color.button},noticeButtonText:{color:_e.color.text,fontSize:_e.font.caption,fontWeight:"600"}});import{AskBuoyIcon as Hn,ASK_BUOY_ICON_COLOR as Ln}from"@buoy-gg/floating-tools-core/web";import{jsx as Pn}from"react/jsx-runtime";var ao={id:"ask-buoy",name:"ASK BUOY",description:"Ask about this app in plain English",slot:"both",color:Ln,icon:({size:e,slot:t})=>Pn(Hn,{size:t==="dial"?e*1.5:e}),component:No};function bd(e){let{name:t,description:o,...r}=e,n=r;return{...ao,name:t??ao.name,description:o??ao.description,component:i=>Pn(No,{...i,config:n})}}import{isReversible as jo}from"@buoy-gg/agent-core/web";var qo=1,yd={version:qo,async dispose(e){let t=b.getLedger();for(let o of t?.list()??[])o.undoneAt||o.effect.kind==="network-override"&&await e("network","deleteOverrideRule",{id:o.effect.ruleId})},getSnapshot(){let e=b.getState(),t=(b.getLedger()?.list()??[]).filter(o=>!o.undoneAt);return{version:qo,busy:e.busy,configured:e.configured,notice:e.notice,messageCount:e.messages.length,messages:e.messages.map(o=>({id:o.id,role:o.role,text:Ut(o.text),streaming:!!o.streaming,toolCalls:o.toolCalls.map(r=>({toolId:r.toolId,action:r.action,status:r.status}))})),usage:e.usage,pendingApproval:e.pending?{toolId:e.pending.toolId,action:e.pending.action,label:e.pending.label}:null,changes:{reversible:t.filter(o=>jo(o.effect)).length,permanent:t.filter(o=>o.effect.kind!=="transient"&&!jo(o.effect)).length,transients:t.filter(o=>o.effect.kind==="transient").length,items:t.map(o=>({kind:o.effect.kind,label:o.effect.label}))}}},subscribe(e){let t=b.subscribe(e),o=b.getLedger()?.subscribe(e);return()=>{t(),o?.()}},actions:{listChanges:()=>(b.getLedger()?.list()??[]).filter(e=>!e.undoneAt).map(e=>({id:e.id,at:e.at,toolId:e.toolId,action:e.action,kind:e.effect.kind,label:e.effect.label,reversible:jo(e.effect)})),undoAll:async()=>{let e=b.getLedger();return e?{ok:true,...await e.revertAll(pn)}:{ok:false,error:"No Ask Buoy session on this device."}},reset:async()=>{let e=await gn({dispatch:pn,undoFirst:true});return e.cleared?{ok:true,reverted:e.reverted}:{ok:false,error:`Couldn't undo ${e.failed.length} change${e.failed.length===1?"":"s"} \u2014 conversation kept.`,failed:e.failed}}}};import*as md from"@buoy-gg/license/web";import{HOSTED_HEADERS as gt,HOSTED_PROTOCOL_VERSION as xd}from"@buoy-gg/agent-core/web";var On="https://ai.buoy.gg/v1/chat/completions",Wn="openai/gpt-6-luna",kd=10*6e4;async function Mo(e=false){let t=md.SessionManager;if(!t)return null;let o=t.getStatus();return o.state==="active"&&(e||o.grace||(o.validUntil??0)-Date.now()<kd)&&await t.refresh(true),t.getAccessToken()}function En(e){let t=/^(https?:\/\/[^/]+)/.exec(e);return t?t[1]:e}function wd(e={}){let{endpoint:t=On,...o}=e,r=`ask-buoy ${$t.OS}`;return{...o,endpoint:t,hosted:true,protocol:"openai",model:Wn,maxTokens:8192,headers:async n=>{let i=await Mo((n?.attempt??1)>1);return{...i?{[gt.session]:i}:{},[gt.ask]:n?.askId??"ask_unknown000",[gt.round]:String(n?.round??1),[gt.attempt]:String(n?.attempt??1),[gt.protocol]:String(xd),[gt.client]:r}},readUsage:async()=>{let n=await Mo();if(!n)throw new Error("Sign in to see hosted credits.");let i=await fetch(`${En(t)}/v1/usage`,{headers:{[gt.session]:n}});if(!i.ok)throw new Error(`usage HTTP ${i.status}`);let a=await i.json();return{creditsLeft:a.creditsLeft??0,grantCredits:a.grantCredits??0,spentCredits:a.spentCredits??0,resetAt:a.resetAt??null}},reportProblem:async({requestId:n,code:i,note:a})=>{let h=await Mo(),u=await fetch(`${En(t)}/v1/report`,{method:"POST",headers:{"Content-Type":"application/json",...h?{[gt.session]:h}:{}},body:JSON.stringify({request_id:n,code:i,note:a,client:r})});if(!u.ok)throw new Error(`report HTTP ${u.status}`);let g=await u.json();if(!g.report_id)throw new Error("report: no id");return{reportId:g.report_id}}}}export{qo as ASK_BUOY_ADAPTER_VERSION,Ln as ASK_BUOY_ICON_COLOR,Wr as ApprovalCard,Yr as AskBuoyHeader,Hn as AskBuoyIcon,No as AskBuoyModal,yn as AskBuoySettings,We as BlockActionRow,O as BlockCard,ar as BlockHandlersProvider,no as BlockRenderer,jr as ChangesBar,_r as Composer,ds as EditableUserBubble,ls as ElapsedRow,On as HOSTED_ENDPOINT,Wn as HOSTED_MODEL,Lr as LivePill,yr as Markdown,ns as MessageActions,Bn as MessageList,hr as ProvenanceBadge,as as QueuedComposer,is as ReasoningStrip,ss as ReloadDivider,Cn as ThinkingStrip,cs as Timestamp,pt as askBuoySettings,b as askBuoyStore,yd as askBuoySyncAdapter,ao as askBuoyToolPreset,bd as createAskBuoyTool,Sn as hasTrace,wd as hostedAskBuoy,Ut as redactForWire,Eo as traceEntries,Tn as traceToText,bn as useAskBuoy,Ht as useAskBuoySettings,Lo as useAskBuoyState,Z as useBlockHandlers};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/ask-buoy",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.55",
|
|
4
4
|
"description": "In-app AI chat for supported Buoy inspection and action tools, using a configured model endpoint.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"buoy"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@buoy-gg/agent-core": "^7.0.
|
|
29
|
-
"@buoy-gg/floating-tools-core": "^7.0.
|
|
30
|
-
"@buoy-gg/shared-ui": "^7.0.
|
|
28
|
+
"@buoy-gg/agent-core": "^7.0.55",
|
|
29
|
+
"@buoy-gg/floating-tools-core": "^7.0.55",
|
|
30
|
+
"@buoy-gg/shared-ui": "^7.0.55"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@buoy-gg/license": "7.0.
|
|
33
|
+
"@buoy-gg/license": "7.0.55",
|
|
34
34
|
"react": "*"
|
|
35
35
|
},
|
|
36
36
|
"peerDependenciesMeta": {
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"@types/react": "^19.1.0",
|
|
52
52
|
"@types/react-native": "^0.73.0",
|
|
53
53
|
"typescript": "~5.8.3",
|
|
54
|
-
"@buoy-gg/
|
|
55
|
-
"@buoy-gg/
|
|
54
|
+
"@buoy-gg/license": "^7.0.55",
|
|
55
|
+
"@buoy-gg/core": "^7.0.55"
|
|
56
56
|
},
|
|
57
57
|
"react-native-builder-bob": {
|
|
58
58
|
"source": "src",
|