@buoy-gg/ask-buoy 7.0.66 → 7.0.68

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.
@@ -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 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}
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:a}){const{state:e,send:c,stop:h,reset:d,undoThenReset:x,approve:E,retry:L,answer:A,runAction:B,editQueued:N,removeQueued:O,configured:k,toolCount:D,availableToolIds:K}=(0,_useAskBuoy.useAskBuoy)(a);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 r=!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(r&&T.current&&!e.busy)y.current=true;if(!r)y.current=false;T.current=e.busy},[r,e.busy]);(0,_react.useEffect)(()=>{if(!r){(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)}},[r,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&&a?.peek!==false)w();j.current=p},[p,w,a]);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 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"}});
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 ae=(0,_askBuoyStore.queuePauseReason)(e);const{showThinking:M,readOnly:H,claudeNoticeSeen:re}=(0,_useAskBuoySettings.useAskBuoySettings)();const I=a?.hosted===true&&!re;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 Anthropic for Claude. If Claude is down, it goes to OpenAI through OpenRouter for Luna instead. 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("claudeNoticeSeen",true);_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:L,waitingOnUser:!!e.pending||!!e.awaitingBlockId,ListEmptyComponent:(0,_jsxRuntime.jsx)(_EmptyState.EmptyState,{configured:k,onPick:c,availableToolIds:K,readOnly:H||!!a?.policy?.readOnly})}),(0,_jsxRuntime.jsx)(_QueuedMessages.QueuedMessages,{items:e.queued,paused:ae,onEdit:N,onRemove:O}),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";Object.defineProperty(exports,"__esModule",{value:true});exports.AskBuoySettings=AskBuoySettings;var _react=require("react");var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _useAskBuoySettings=require("../store/useAskBuoySettings");var _askBuoySettings=require("../store/askBuoySettings");var _askBuoyStore=require("../store/askBuoyStore");var _useAskBuoy=require("../store/useAskBuoy");var _jsxRuntime=require("react/jsx-runtime");function AskBuoySettings(){const e=(0,_useAskBuoySettings.useAskBuoySettings)();const s=(0,_useAskBuoy.useAskBuoyState)().trusted;return(0,_jsxRuntime.jsxs)(_reactNative.ScrollView,{style:styles.scroll,contentContainerStyle:styles.content,testID:"buoy-ask-buoy-settings",children:[(0,_jsxRuntime.jsx)(HostedCredits,{}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.section,children:"CHAT"}),(0,_jsxRuntime.jsx)(_sharedUi.ToggleRow,{testID:"buoy-ask-buoy-setting-thinking",label:"Show agent thinking",description:"Adds a collapsed strip under each answer: the model's reasoning, and every step it ran with its parameters, result, duration and token cost. Copy includes the steps and reasoning while this is on, so you can read the run outside the app.",value:e.showThinking,onValueChange:t=>_askBuoySettings.askBuoySettings.set("showThinking",t)}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,children:"Not every model reports reasoning. Claude thinks by default; most OpenAI models return none, and the strip then shows the steps alone."}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.section,children:"PERMISSIONS"}),(0,_jsxRuntime.jsx)(_sharedUi.ToggleRow,{testID:"buoy-ask-buoy-setting-readonly",label:"Read only",description:"Inspect the app, change nothing. Every write and simulation is refused until this is off. Undo still works \u2014 putting things back is the safe direction.",value:e.readOnly,onValueChange:t=>_askBuoySettings.askBuoySettings.set("readOnly",t)}),e.readOnly?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,testID:"buoy-ask-buoy-readonly-note",children:"On across reloads until you turn it off here. The header says \u201CRead only\u201D while it is on. It adds to whatever this app already restricts \u2014 it never loosens anything."}):null,(0,_jsxRuntime.jsx)(_sharedUi.ToggleRow,{testID:"buoy-ask-buoy-setting-bypass",tone:"warning",emphasizeLabel:e.bypassApprovals,disabled:e.readOnly,label:"Skip approval prompts",description:e.readOnly?"Unavailable while Read only is on \u2014 nothing can be approved.":"Runs every action the moment the agent calls it, with no Allow card \u2014 including the destructive ones that wipe or reset data.",value:e.bypassApprovals,onValueChange:t=>_askBuoySettings.askBuoySettings.set("bypassApprovals",t)}),e.bypassApprovals&&!e.readOnly?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.warning,testID:"buoy-ask-buoy-bypass-warning",children:[(0,_jsxRuntime.jsx)(_sharedUi.AlertTriangle,{size:14,color:_sharedUi.night.color.warning}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.warningText,children:"Nothing will ask before a change lands. A wipe or a reset runs on the model's word alone, and Undo only reverses the changes that can be reversed \u2014 the bar tells you which. This stays on across reloads and app restarts until you turn it off here, so leave it off on any device holding data you care about. It does not widen what the agent may reach: read-only mode, this app's deny list and the release-build refusals still apply."})]}):(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,children:"On by default for destructive actions only \u2014 reads and ordinary writes already run without asking, and the changes bar records them."}),s.length>0?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.trustedBox,testID:"buoy-ask-buoy-trusted",children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.section,children:"ALLOWED WITHOUT ASKING \u2014 THIS CHAT"}),s.map(t=>(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.trustedRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.trustedLabel,numberOfLines:2,children:t.label}),(0,_jsxRuntime.jsx)(_reactNative.Pressable,{testID:`buoy-ask-buoy-untrust-${t.key}`,accessibilityRole:"button",onPress:()=>_askBuoyStore.askBuoyStore.untrust(t.key),style:({pressed:n})=>[styles.forget,n&&{opacity:.7}],hitSlop:6,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.forgetText,children:"Ask again"})})]},t.key))]}):null]})}function formatReset(e){const s=Date.parse(e.length===10?`${e}T00:00:00Z`:e);if(Number.isNaN(s))return e;const t=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];const n=new Date(s);return`${t[n.getUTCMonth()]} ${n.getUTCDate()}`}function HostedCredits(){const e=_askBuoyStore.askBuoyStore.getUsageReader();const[s,t]=(0,_react.useState)({kind:"loading"});const n=(0,_react.useCallback)(()=>{if(!e)return;t({kind:"loading"});e().then(r=>t({kind:"ok",usage:r})).catch(r=>t({kind:"error",message:r instanceof Error?r.message:"Could not load credits."}))},[e]);(0,_react.useEffect)(n,[n]);if(!e)return null;return(0,_jsxRuntime.jsxs)(_reactNative.View,{testID:"buoy-ask-buoy-credits",children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.section,children:"HOSTED AI CREDITS"}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.trustedRow,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:{flex:1,gap:2},children:s.kind==="ok"?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.trustedLabel,children:[s.usage.creditsLeft.toLocaleString()," of ",s.usage.grantCredits.toLocaleString()," credits left"]}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.note,children:[s.usage.resetAt?`Resets ${formatReset(s.usage.resetAt)}. `:"","1 credit is $0.001 of AI use, included with your plan."]})]}):s.kind==="error"?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,children:s.message}):(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,children:"Loading\u2026"})}),(0,_jsxRuntime.jsx)(_reactNative.Pressable,{testID:"buoy-ask-buoy-credits-refresh",accessibilityRole:"button",onPress:n,style:({pressed:r})=>[styles.forget,r&&{opacity:.7}],hitSlop:6,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.forgetText,children:"Refresh"})})]})]})}const styles=_reactNative.StyleSheet.create({scroll:{flex:1},content:{padding:_sharedUi.night.gutter,gap:10,paddingBottom:32},section:{color:_sharedUi.night.color.textTertiary,fontSize:10,fontWeight:"700",letterSpacing:.6,paddingHorizontal:4,paddingTop:4},warning:{flexDirection:"row",gap:8,padding:_sharedUi.night.cardPad,borderRadius:_sharedUi.night.radius.card,borderWidth:_reactNative.StyleSheet.hairlineWidth,borderColor:_sharedUi.night.color.warning,backgroundColor:_sharedUi.night.color.surface},warningText:{flex:1,color:_sharedUi.night.color.warning,fontSize:_sharedUi.night.font.micro,lineHeight:16},trustedBox:{gap:6,paddingTop:6},trustedRow:{flexDirection:"row",alignItems:"center",gap:10,padding:_sharedUi.night.cardPad,borderRadius:_sharedUi.night.radius.card,borderWidth:_reactNative.StyleSheet.hairlineWidth,borderColor:_sharedUi.night.color.border,backgroundColor:_sharedUi.night.color.surface},trustedLabel:{flex:1,color:_sharedUi.night.color.text,fontSize:_sharedUi.night.font.caption,lineHeight:18},forget:{paddingHorizontal:10,paddingVertical:6,borderRadius:_sharedUi.night.radius.row,backgroundColor:_sharedUi.night.color.button},forgetText:{color:_sharedUi.night.color.text,fontSize:_sharedUi.night.font.micro,fontWeight:"600"},note:{color:_sharedUi.night.color.textTertiary,fontSize:_sharedUi.night.font.micro,lineHeight:16,paddingHorizontal:4}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.AskBuoySettings=AskBuoySettings;var _react=require("react");var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _useAskBuoySettings=require("../store/useAskBuoySettings");var _askBuoySettings=require("../store/askBuoySettings");var _hostedModelSetting=require("../store/hostedModelSetting");var _askBuoyStore=require("../store/askBuoyStore");var _useAskBuoy=require("../store/useAskBuoy");var _jsxRuntime=require("react/jsx-runtime");function AskBuoySettings(){const t=(0,_useAskBuoySettings.useAskBuoySettings)();const s=(0,_useAskBuoy.useAskBuoyState)().trusted;return(0,_jsxRuntime.jsxs)(_reactNative.ScrollView,{style:styles.scroll,contentContainerStyle:styles.content,testID:"buoy-ask-buoy-settings",children:[(0,_jsxRuntime.jsx)(HostedCredits,{}),(0,_jsxRuntime.jsx)(HostedModel,{}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.section,children:"CHAT"}),(0,_jsxRuntime.jsx)(_sharedUi.ToggleRow,{testID:"buoy-ask-buoy-setting-thinking",label:"Show agent thinking",description:"Adds a collapsed strip under each answer: the model's reasoning, and every step it ran with its parameters, result, duration and token cost. Copy includes the steps and reasoning while this is on, so you can read the run outside the app.",value:t.showThinking,onValueChange:e=>_askBuoySettings.askBuoySettings.set("showThinking",e)}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,children:"Not every model reports reasoning. Claude thinks by default; most OpenAI models return none, and the strip then shows the steps alone."}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.section,children:"PERMISSIONS"}),(0,_jsxRuntime.jsx)(_sharedUi.ToggleRow,{testID:"buoy-ask-buoy-setting-readonly",label:"Read only",description:"Inspect the app, change nothing. Every write and simulation is refused until this is off. Undo still works \u2014 putting things back is the safe direction.",value:t.readOnly,onValueChange:e=>_askBuoySettings.askBuoySettings.set("readOnly",e)}),t.readOnly?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,testID:"buoy-ask-buoy-readonly-note",children:"On across reloads until you turn it off here. The header says \u201CRead only\u201D while it is on. It adds to whatever this app already restricts \u2014 it never loosens anything."}):null,(0,_jsxRuntime.jsx)(_sharedUi.ToggleRow,{testID:"buoy-ask-buoy-setting-bypass",tone:"warning",emphasizeLabel:t.bypassApprovals,disabled:t.readOnly,label:"Skip approval prompts",description:t.readOnly?"Unavailable while Read only is on \u2014 nothing can be approved.":"Runs every action the moment the agent calls it, with no Allow card \u2014 including the destructive ones that wipe or reset data.",value:t.bypassApprovals,onValueChange:e=>_askBuoySettings.askBuoySettings.set("bypassApprovals",e)}),t.bypassApprovals&&!t.readOnly?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.warning,testID:"buoy-ask-buoy-bypass-warning",children:[(0,_jsxRuntime.jsx)(_sharedUi.AlertTriangle,{size:14,color:_sharedUi.night.color.warning}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.warningText,children:"Nothing will ask before a change lands. A wipe or a reset runs on the model's word alone, and Undo only reverses the changes that can be reversed \u2014 the bar tells you which. This stays on across reloads and app restarts until you turn it off here, so leave it off on any device holding data you care about. It does not widen what the agent may reach: read-only mode, this app's deny list and the release-build refusals still apply."})]}):(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,children:"On by default for destructive actions only \u2014 reads and ordinary writes already run without asking, and the changes bar records them."}),s.length>0?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.trustedBox,testID:"buoy-ask-buoy-trusted",children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.section,children:"ALLOWED WITHOUT ASKING \u2014 THIS CHAT"}),s.map(e=>(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.trustedRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.trustedLabel,numberOfLines:2,children:e.label}),(0,_jsxRuntime.jsx)(_reactNative.Pressable,{testID:`buoy-ask-buoy-untrust-${e.key}`,accessibilityRole:"button",onPress:()=>_askBuoyStore.askBuoyStore.untrust(e.key),style:({pressed:o})=>[styles.forget,o&&{opacity:.7}],hitSlop:6,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.forgetText,children:"Ask again"})})]},e.key))]}):null]})}function formatReset(t){const s=Date.parse(t.length===10?`${t}T00:00:00Z`:t);if(Number.isNaN(s))return t;const e=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];const o=new Date(s);return`${e[o.getUTCMonth()]} ${o.getUTCDate()}`}function HostedCredits(){const t=_askBuoyStore.askBuoyStore.getUsageReader();const[s,e]=(0,_react.useState)({kind:"loading"});const o=(0,_react.useCallback)(()=>{if(!t)return;e({kind:"loading"});t().then(n=>{if(n.claude)_askBuoyStore.askBuoyStore.setClaudeOut(!n.claude.available);e({kind:"ok",usage:n})}).catch(n=>e({kind:"error",message:n instanceof Error?n.message:"Could not load credits."}))},[t]);(0,_react.useEffect)(o,[o]);if(!t)return null;return(0,_jsxRuntime.jsxs)(_reactNative.View,{testID:"buoy-ask-buoy-credits",children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.section,children:"HOSTED AI CREDITS"}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.trustedRow,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:{flex:1,gap:2},children:s.kind==="ok"?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.trustedLabel,children:[s.usage.creditsLeft.toLocaleString()," of ",s.usage.grantCredits.toLocaleString()," credits left"]}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.note,children:[s.usage.resetAt?`Resets ${formatReset(s.usage.resetAt)}. `:"","That's about ",Math.floor(s.usage.creditsLeft/5).toLocaleString()," more asks, included with your plan."]})]}):s.kind==="error"?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,children:s.message}):(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,children:"Loading\u2026"})}),(0,_jsxRuntime.jsx)(_reactNative.Pressable,{testID:"buoy-ask-buoy-credits-refresh",accessibilityRole:"button",onPress:o,style:({pressed:n})=>[styles.forget,n&&{opacity:.7}],hitSlop:6,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.forgetText,children:"Refresh"})})]})]})}const MODELS=[{id:"claude",label:"Claude Haiku 5.5",note:"The default. Answers faster."},{id:"luna",label:"GPT-6 Luna",note:"Uses fewer credits."}];function HostedModel(){const t=(0,_hostedModelSetting.useHostedModel)();const{claudeOut:s}=(0,_useAskBuoy.useAskBuoyState)();if(!_askBuoyStore.askBuoyStore.getUsageReader())return null;return(0,_jsxRuntime.jsxs)(_reactNative.View,{testID:"buoy-ask-buoy-model",style:{gap:6},children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.section,children:"MODEL"}),MODELS.map(e=>{const o=t===e.id;return(0,_jsxRuntime.jsxs)(_reactNative.Pressable,{testID:`buoy-ask-buoy-model-${e.id}`,accessibilityRole:"radio",accessibilityState:{checked:o},onPress:()=>_hostedModelSetting.hostedModelSetting.set(e.id),style:({pressed:n})=>[styles.trustedRow,o&&styles.picked,n&&{opacity:.7}],children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:{flex:1,gap:2},children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.trustedLabel,children:e.label}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,children:e.note})]}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.forgetText,children:o?"On":""})]},e.id)}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.note,testID:"buoy-ask-buoy-model-note",children:t==="claude"&&s?"Claude is busy right now. New chats use Luna until it opens again.":"If one model is down, the other answers. A change starts with your next new chat. You can also pick it on buoy.gg, on the Ask Buoy tab."})]})}const styles=_reactNative.StyleSheet.create({picked:{borderColor:_sharedUi.night.color.info},scroll:{flex:1},content:{padding:_sharedUi.night.gutter,gap:10,paddingBottom:32},section:{color:_sharedUi.night.color.textTertiary,fontSize:10,fontWeight:"700",letterSpacing:.6,paddingHorizontal:4,paddingTop:4},warning:{flexDirection:"row",gap:8,padding:_sharedUi.night.cardPad,borderRadius:_sharedUi.night.radius.card,borderWidth:_reactNative.StyleSheet.hairlineWidth,borderColor:_sharedUi.night.color.warning,backgroundColor:_sharedUi.night.color.surface},warningText:{flex:1,color:_sharedUi.night.color.warning,fontSize:_sharedUi.night.font.micro,lineHeight:16},trustedBox:{gap:6,paddingTop:6},trustedRow:{flexDirection:"row",alignItems:"center",gap:10,padding:_sharedUi.night.cardPad,borderRadius:_sharedUi.night.radius.card,borderWidth:_reactNative.StyleSheet.hairlineWidth,borderColor:_sharedUi.night.color.border,backgroundColor:_sharedUi.night.color.surface},trustedLabel:{flex:1,color:_sharedUi.night.color.text,fontSize:_sharedUi.night.font.caption,lineHeight:18},forget:{paddingHorizontal:10,paddingVertical:6,borderRadius:_sharedUi.night.radius.row,backgroundColor:_sharedUi.night.color.button},forgetText:{color:_sharedUi.night.color.text,fontSize:_sharedUi.night.font.micro,fontWeight:"600"},note:{color:_sharedUi.night.color.textTertiary,fontSize:_sharedUi.night.font.micro,lineHeight:16,paddingHorizontal:4}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.HOSTED_MODEL=exports.HOSTED_ENDPOINT=void 0;exports.hostedAskBuoy=hostedAskBuoy;var _reactNative=require("react-native");var license=_interopRequireWildcard(require("@buoy-gg/license"));var _agentCore=require("@buoy-gg/agent-core");function _interopRequireWildcard(a,e){if("function"==typeof WeakMap)var i=new WeakMap,u=new WeakMap;return(_interopRequireWildcard=function(t,o){if(!o&&t&&t.__esModule)return t;var r,c,n={__proto__:null,default:t};if(null===t||"object"!=typeof t&&"function"!=typeof t)return n;if(r=o?u:i){if(r.has(t))return r.get(t);r.set(t,n)}for(const s in t)"default"!==s&&{}.hasOwnProperty.call(t,s)&&((c=(r=Object.defineProperty)&&Object.getOwnPropertyDescriptor(t,s))&&(c.get||c.set)?r(n,s,c):n[s]=t[s]);return n})(a,e)}const HOSTED_ENDPOINT=exports.HOSTED_ENDPOINT="https://ai.buoy.gg/v1/chat/completions";const HOSTED_MODEL=exports.HOSTED_MODEL="openai/gpt-6-luna";const REFRESH_WITHIN_MS=10*6e4;async function sessionToken(a=false){const e=license.SessionManager;if(!e)return null;const i=e.getStatus();if(i.state==="active"&&(a||i.grace||(i.validUntil??0)-Date.now()<REFRESH_WITHIN_MS)){await e.refresh(true)}return e.getAccessToken()}function origin(a){const e=/^(https?:\/\/[^/]+)/.exec(a);return e?e[1]:a}function hostedAskBuoy(a={}){const{endpoint:e=HOSTED_ENDPOINT,...i}=a;const u=`ask-buoy ${_reactNative.Platform.OS}`;return{...i,endpoint:e,hosted:true,protocol:"openai",model:HOSTED_MODEL,maxTokens:8192,headers:async t=>{const o=await sessionToken((t?.attempt??1)>1);return{...o?{[_agentCore.HOSTED_HEADERS.session]:o}:{},[_agentCore.HOSTED_HEADERS.ask]:t?.askId??"ask_unknown000",[_agentCore.HOSTED_HEADERS.round]:String(t?.round??1),[_agentCore.HOSTED_HEADERS.attempt]:String(t?.attempt??1),[_agentCore.HOSTED_HEADERS.protocol]:String(_agentCore.HOSTED_PROTOCOL_VERSION),[_agentCore.HOSTED_HEADERS.client]:u}},readUsage:async()=>{const t=await sessionToken();if(!t)throw new Error("Sign in to see hosted credits.");const o=await fetch(`${origin(e)}/v1/usage`,{headers:{[_agentCore.HOSTED_HEADERS.session]:t}});if(!o.ok)throw new Error(`usage HTTP ${o.status}`);const r=await o.json();return{creditsLeft:r.creditsLeft??0,grantCredits:r.grantCredits??0,spentCredits:r.spentCredits??0,resetAt:r.resetAt??null}},reportProblem:async({requestId:t,code:o,note:r})=>{const c=await sessionToken();const n=await fetch(`${origin(e)}/v1/report`,{method:"POST",headers:{"Content-Type":"application/json",...c?{[_agentCore.HOSTED_HEADERS.session]:c}:{}},body:JSON.stringify({request_id:t,code:o,note:r,client:u})});if(!n.ok)throw new Error(`report HTTP ${n.status}`);const s=await n.json();if(!s.report_id)throw new Error("report: no id");return{reportId:s.report_id}}}}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"HOSTED_CLAUDE_MODEL",{enumerable:true,get:function(){return _hostedModel.HOSTED_CLAUDE_MODEL}});exports.HOSTED_MODEL=exports.HOSTED_ENDPOINT=void 0;exports.hostedAskBuoy=hostedAskBuoy;Object.defineProperty(exports,"pickHostedModel",{enumerable:true,get:function(){return _hostedModel.pickHostedModel}});var _reactNative=require("react-native");var license=_interopRequireWildcard(require("@buoy-gg/license"));var _agentCore=require("@buoy-gg/agent-core");var _hostedModel=require("./hostedModel");function _interopRequireWildcard(a,r){if("function"==typeof WeakMap)var i=new WeakMap,u=new WeakMap;return(_interopRequireWildcard=function(e,o){if(!o&&e&&e.__esModule)return e;var t,c,n={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return n;if(t=o?u:i){if(t.has(e))return t.get(e);t.set(e,n)}for(const s in e)"default"!==s&&{}.hasOwnProperty.call(e,s)&&((c=(t=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,s))&&(c.get||c.set)?t(n,s,c):n[s]=e[s]);return n})(a,r)}const HOSTED_ENDPOINT=exports.HOSTED_ENDPOINT="https://ai.buoy.gg/v1/chat/completions";const HOSTED_MODEL=exports.HOSTED_MODEL="openai/gpt-6-luna";const REFRESH_WITHIN_MS=10*6e4;async function sessionToken(a=false){const r=license.SessionManager;if(!r)return null;const i=r.getStatus();if(i.state==="active"&&(a||i.grace||(i.validUntil??0)-Date.now()<REFRESH_WITHIN_MS)){await r.refresh(true)}return r.getAccessToken()}function origin(a){const r=/^(https?:\/\/[^/]+)/.exec(a);return r?r[1]:a}function hostedAskBuoy(a={}){const{endpoint:r=HOSTED_ENDPOINT,...i}=a;const u=`ask-buoy ${_reactNative.Platform.OS}`;return{...i,endpoint:r,hosted:true,protocol:"openai",model:HOSTED_MODEL,hostedModels:{luna:{endpoint:r,protocol:"openai",model:HOSTED_MODEL},claude:{endpoint:`${origin(r)}/v1/messages`,protocol:"anthropic",model:_hostedModel.HOSTED_CLAUDE_MODEL}},maxTokens:8192,headers:async e=>{const o=await sessionToken((e?.attempt??1)>1);return{...o?{[_agentCore.HOSTED_HEADERS.session]:o}:{},[_agentCore.HOSTED_HEADERS.ask]:e?.askId??"ask_unknown000",[_agentCore.HOSTED_HEADERS.round]:String(e?.round??1),[_agentCore.HOSTED_HEADERS.attempt]:String(e?.attempt??1),[_agentCore.HOSTED_HEADERS.protocol]:String(_agentCore.HOSTED_PROTOCOL_VERSION),[_agentCore.HOSTED_HEADERS.client]:u}},readUsage:async()=>{const e=await sessionToken();if(!e)throw new Error("Sign in to see hosted credits.");const o=await fetch(`${origin(r)}/v1/usage`,{headers:{[_agentCore.HOSTED_HEADERS.session]:e}});if(!o.ok)throw new Error(`usage HTTP ${o.status}`);const t=await o.json();return{creditsLeft:t.creditsLeft??0,grantCredits:t.grantCredits??0,spentCredits:t.spentCredits??0,resetAt:t.resetAt??null,...t.claude?{claude:{available:t.claude.available===true,reason:typeof t.claude.reason==="string"?t.claude.reason:null}}:{}}},reportProblem:async({requestId:e,code:o,note:t})=>{const c=await sessionToken();const n=await fetch(`${origin(r)}/v1/report`,{method:"POST",headers:{"Content-Type":"application/json",...c?{[_agentCore.HOSTED_HEADERS.session]:c}:{}},body:JSON.stringify({request_id:e,code:o,note:t,client:u})});if(!n.ok)throw new Error(`report HTTP ${n.status}`);const s=await n.json();if(!s.report_id)throw new Error("report: no id");return{reportId:s.report_id}}}}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.HOSTED_CLAUDE_MODEL=void 0;exports.pickHostedModel=pickHostedModel;const HOSTED_CLAUDE_MODEL=exports.HOSTED_CLAUDE_MODEL="claude-haiku-5-5";function pickHostedModel(t,s,o){const e=t?.hostedModels;if(!t||!e?.claude||!e.luna)return t;const u=s==="claude"&&!o?e.claude:e.luna;const d=u===e.claude?e.luna:e.claude;return{...t,...u,hostedFallback:d}}
@@ -0,0 +1 @@
1
+ "use strict";var _vitest=require("vitest");var _hostedModel=require("./hostedModel");const luna={endpoint:"https://ai.buoy.gg/v1/chat/completions",protocol:"openai",model:"openai/gpt-6-luna"};const claude={endpoint:"https://ai.buoy.gg/v1/messages",protocol:"anthropic",model:_hostedModel.HOSTED_CLAUDE_MODEL};const config={...luna,hosted:true,hostedModels:{luna,claude}};(0,_vitest.describe)("pickHostedModel",()=>{(0,_vitest.it)("answers on Claude with Luna as the backup",()=>{(0,_vitest.expect)((0,_hostedModel.pickHostedModel)(config,"claude",false)).toMatchObject({...claude,hostedFallback:luna})});(0,_vitest.it)("starts on Luna while Claude's budget is out, keeping Claude as the backup",()=>{(0,_vitest.expect)((0,_hostedModel.pickHostedModel)(config,"claude",true)).toMatchObject({...luna,hostedFallback:claude})});(0,_vitest.it)("answers on Luna when Luna is picked, with Claude as the backup",()=>{(0,_vitest.expect)((0,_hostedModel.pickHostedModel)(config,"luna",false)).toMatchObject({...luna,hostedFallback:claude})});(0,_vitest.it)("leaves your own endpoint alone",()=>{const e={endpoint:"https://gw.acme.test/v1/messages",model:"m"};(0,_vitest.expect)((0,_hostedModel.pickHostedModel)(e,"claude",false)).toBe(e)})});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.askBuoySettings=exports.DEFAULT_SETTINGS=void 0;var _sharedUi=require("@buoy-gg/shared-ui");const DEFAULT_SETTINGS=exports.DEFAULT_SETTINGS={showThinking:false,bypassApprovals:false,readOnly:false,hostedNoticeSeen:false};const store=(0,_sharedUi.definePersistedSetting)(`${_sharedUi.devToolsStorageKeys.base}_ask_buoy_settings`,DEFAULT_SETTINGS,{sanitize:e=>({showThinking:e.showThinking===true,bypassApprovals:e.bypassApprovals===true,readOnly:e.readOnly===true,hostedNoticeSeen:e.hostedNoticeSeen===true})});let current=DEFAULT_SETTINGS;let loaded=false;const listeners=new Set;function emit(){for(const e of listeners){try{e()}catch{}}}const askBuoySettings=exports.askBuoySettings={get:()=>current,subscribe(e){listeners.add(e);if(!loaded){loaded=true;void store.load().then(s=>{current=s;emit()})}return()=>listeners.delete(e)},set(e,s){if(current[e]===s)return;current={...current,[e]:s};emit();void store.save(current)},reset(){current=DEFAULT_SETTINGS;loaded=false;void store.clear()}};
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.askBuoySettings=exports.DEFAULT_SETTINGS=void 0;var _sharedUi=require("@buoy-gg/shared-ui");const DEFAULT_SETTINGS=exports.DEFAULT_SETTINGS={showThinking:false,bypassApprovals:false,readOnly:false,hostedNoticeSeen:false,claudeNoticeSeen:false};const store=(0,_sharedUi.definePersistedSetting)(`${_sharedUi.devToolsStorageKeys.base}_ask_buoy_settings`,DEFAULT_SETTINGS,{sanitize:e=>({showThinking:e.showThinking===true,bypassApprovals:e.bypassApprovals===true,readOnly:e.readOnly===true,hostedNoticeSeen:e.hostedNoticeSeen===true,claudeNoticeSeen:e.claudeNoticeSeen===true})});let current=DEFAULT_SETTINGS;let loaded=false;const listeners=new Set;function emit(){for(const e of listeners){try{e()}catch{}}}const askBuoySettings=exports.askBuoySettings={get:()=>current,subscribe(e){listeners.add(e);if(!loaded){loaded=true;void store.load().then(s=>{current=s;emit()})}return()=>listeners.delete(e)},set(e,s){if(current[e]===s)return;current={...current,[e]:s};emit();void store.save(current)},reset(){current=DEFAULT_SETTINGS;loaded=false;void store.clear()}};
@@ -1,3 +1,3 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.askBuoyStore=void 0;exports.queuePauseReason=queuePauseReason;exports.turnPhaseOf=turnPhaseOf;var _agentCore=require("@buoy-gg/agent-core");var _sharedUi=require("@buoy-gg/shared-ui");const nextId=(0,_agentCore.idFactory)("m");const MAX_PARAM_CHARS=400;function encodeParams(t){if(!t||Object.keys(t).length===0)return void 0;try{const e=JSON.stringify(t);return e.length>MAX_PARAM_CHARS?`${e.slice(0,MAX_PARAM_CHARS)}\u2026`:e}catch{return void 0}}const nextQueuedId=(0,_agentCore.idFactory)("q");let state={messages:[],busy:false,stopping:false,pending:null,trusted:[],notice:null,configured:false,answers:{},awaitingBlockId:null,actionState:{},queued:[],draft:null,usage:{input:0,output:0,requests:0},credits:null,meta:{}};let reporter=null;let usageReader=null;const listeners=new Set;let ledgerRef=null;let heldSession=null;function set(t){state={...state,...t};for(const e of listeners){try{e()}catch{}}}const askBuoyStore=exports.askBuoyStore={getState:()=>state,subscribe(t){listeners.add(t);return()=>listeners.delete(t)},setLedger(t){ledgerRef=t},getLedger(){return ledgerRef},holdSession(t,e){heldSession={session:t,signature:e}},getHeldSession(){return heldSession},setReporter(t){reporter=t},getReporter(){return reporter},setUsageReader(t){usageReader=t},getUsageReader(){return usageReader},setNotice(t){set({notice:t})},setConfigured(t){if(t!==state.configured)set({configured:t})},setMeta(t){const e=Object.keys(t).every(r=>state.meta[r]===t[r]);if(!e)set({meta:{...state.meta,...t}})},addUser(t){set({messages:[...state.messages,{id:nextId(),role:"user",text:t,toolCalls:[],blocks:[]}],busy:true})},beginTurn(){set({busy:true})},enqueue(t){const e={id:nextQueuedId(),text:t};set({queued:[...state.queued,e]});return e},removeQueued(t){if(!state.queued.some(e=>e.id===t))return;set({queued:state.queued.filter(e=>e.id!==t)})},shiftQueued(){const[t,...e]=state.queued;if(!t)return void 0;set({queued:e});return t},takeQueuedToDraft(t){const e=state.queued.find(r=>r.id===t);if(!e)return;set({queued:state.queued.filter(r=>r.id!==t),draft:e.text})},requestStop(){if(!state.busy||state.stopping)return;set({stopping:true})},clearDraft(){if(state.draft===null)return;set({draft:null})},removeMessage(t){set({messages:state.messages.filter(e=>e.id!==t)})},recordAnswer(t){set({answers:{...state.answers,[t.blockId]:t},awaitingBlockId:state.awaitingBlockId===t.blockId?null:state.awaitingBlockId})},setActionState(t,e){const r={...state.actionState};if(e===null)delete r[t];else r[t]=e;set({actionState:r})},startAssistant(){const t=nextId();set({messages:[...state.messages,{id:t,role:"assistant",text:"",toolCalls:[],blocks:[],streaming:true,startedAt:(0,_sharedUi.realNow)()}]});return t},applyEvent(t,e){if(e.type==="history-trimmed"){const s=state.messages.findIndex(o=>o.id===t);const n={id:nextId(),role:"assistant",system:true,text:`Older messages were trimmed from the agent's memory (${e.droppedRounds} exchange${e.droppedRounds===1?"":"s"}).`+(e.droppedPinned?` ${e.droppedPinned===1?"One of them made a change that is":`${e.droppedPinned} of them made changes that are`} still applied \u2014 the changes bar still lists ${e.droppedPinned===1?"it":"them"}, but the agent no longer remembers making ${e.droppedPinned===1?"it":"them"}.`:" It may not recall setup from earlier in this chat \u2014 the changes bar still lists what is applied."),toolCalls:[],blocks:[],endedAt:(0,_sharedUi.realNow)()};const a=[...state.messages];a.splice(s<0?a.length:s,0,n);set({messages:a});return}const r=state.messages.map(s=>{if(s.id!==t)return s;switch(e.type){case"retrying":{const n=Math.max(1,Math.round(e.inMs/1e3));return{...s,waiting:e.reason==="throttled"?`Endpoint busy \u2014 retrying in ${n}s (${e.attempt} of ${e.maxAttempts})`:`Request too large \u2014 trimming older messages and retrying`}}case"text":return{...s,waiting:void 0,text:(s.breakBeforeNextText||e.breakBefore)&&s.text?`${s.text.trimEnd()}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.askBuoyStore=void 0;exports.queuePauseReason=queuePauseReason;exports.turnPhaseOf=turnPhaseOf;var _agentCore=require("@buoy-gg/agent-core");var _sharedUi=require("@buoy-gg/shared-ui");const nextId=(0,_agentCore.idFactory)("m");const MAX_PARAM_CHARS=400;function encodeParams(t){if(!t||Object.keys(t).length===0)return void 0;try{const e=JSON.stringify(t);return e.length>MAX_PARAM_CHARS?`${e.slice(0,MAX_PARAM_CHARS)}\u2026`:e}catch{return void 0}}const nextQueuedId=(0,_agentCore.idFactory)("q");let state={messages:[],busy:false,stopping:false,pending:null,trusted:[],notice:null,configured:false,answers:{},awaitingBlockId:null,actionState:{},queued:[],draft:null,usage:{input:0,output:0,requests:0},credits:null,claudeOut:false,meta:{}};let reporter=null;let usageReader=null;const listeners=new Set;let ledgerRef=null;let heldSession=null;function set(t){state={...state,...t};for(const e of listeners){try{e()}catch{}}}const askBuoyStore=exports.askBuoyStore={getState:()=>state,subscribe(t){listeners.add(t);return()=>listeners.delete(t)},setLedger(t){ledgerRef=t},getLedger(){return ledgerRef},holdSession(t,e){heldSession={session:t,signature:e}},getHeldSession(){return heldSession},setReporter(t){reporter=t},getReporter(){return reporter},setUsageReader(t){usageReader=t},getUsageReader(){return usageReader},setNotice(t){set({notice:t})},setConfigured(t){if(t!==state.configured)set({configured:t})},setClaudeOut(t){if(state.claudeOut!==t)set({claudeOut:t})},setMeta(t){const e=Object.keys(t).every(r=>state.meta[r]===t[r]);if(!e)set({meta:{...state.meta,...t}})},addUser(t){set({messages:[...state.messages,{id:nextId(),role:"user",text:t,toolCalls:[],blocks:[]}],busy:true})},beginTurn(){set({busy:true})},enqueue(t){const e={id:nextQueuedId(),text:t};set({queued:[...state.queued,e]});return e},removeQueued(t){if(!state.queued.some(e=>e.id===t))return;set({queued:state.queued.filter(e=>e.id!==t)})},shiftQueued(){const[t,...e]=state.queued;if(!t)return void 0;set({queued:e});return t},takeQueuedToDraft(t){const e=state.queued.find(r=>r.id===t);if(!e)return;set({queued:state.queued.filter(r=>r.id!==t),draft:e.text})},requestStop(){if(!state.busy||state.stopping)return;set({stopping:true})},clearDraft(){if(state.draft===null)return;set({draft:null})},removeMessage(t){set({messages:state.messages.filter(e=>e.id!==t)})},recordAnswer(t){set({answers:{...state.answers,[t.blockId]:t},awaitingBlockId:state.awaitingBlockId===t.blockId?null:state.awaitingBlockId})},setActionState(t,e){const r={...state.actionState};if(e===null)delete r[t];else r[t]=e;set({actionState:r})},startAssistant(){const t=nextId();set({messages:[...state.messages,{id:t,role:"assistant",text:"",toolCalls:[],blocks:[],streaming:true,startedAt:(0,_sharedUi.realNow)()}]});return t},applyEvent(t,e){if(e.type==="history-trimmed"){const s=state.messages.findIndex(u=>u.id===t);const n={id:nextId(),role:"assistant",system:true,text:`Older messages were trimmed from the agent's memory (${e.droppedRounds} exchange${e.droppedRounds===1?"":"s"}).`+(e.droppedPinned?` ${e.droppedPinned===1?"One of them made a change that is":`${e.droppedPinned} of them made changes that are`} still applied \u2014 the changes bar still lists ${e.droppedPinned===1?"it":"them"}, but the agent no longer remembers making ${e.droppedPinned===1?"it":"them"}.`:" It may not recall setup from earlier in this chat \u2014 the changes bar still lists what is applied."),toolCalls:[],blocks:[],endedAt:(0,_sharedUi.realNow)()};const a=[...state.messages];a.splice(s<0?a.length:s,0,n);set({messages:a});return}const r=state.messages.map(s=>{if(s.id!==t)return s;switch(e.type){case"retrying":{const n=Math.max(1,Math.round(e.inMs/1e3));return{...s,waiting:e.reason==="throttled"?`Endpoint busy \u2014 retrying in ${n}s (${e.attempt} of ${e.maxAttempts})`:`Request too large \u2014 trimming older messages and retrying`}}case"text":return{...s,waiting:void 0,text:(s.breakBeforeNextText||e.breakBefore)&&s.text?`${s.text.trimEnd()}
2
2
 
3
- ${e.delta}`:s.text+e.delta,breakBeforeNextText:false};case"reasoning":return{...s,breakBeforeNextText:true,reasoning:[...s.reasoning??[],{text:e.text,afterCalls:s.toolCalls.length}]};case"tool-start":return{...s,waiting:void 0,toolCalls:[...s.toolCalls,{id:e.id,toolId:e.toolId,action:e.action,label:e.label,description:e.description,status:"running",params:encodeParams(e.params),effect:e.effect,startedAt:(0,_sharedUi.realNow)()}]};case"tool-end":return{...s,breakBeforeNextText:true,toolCalls:s.toolCalls.map(n=>n.id===e.id?{...n,status:!e.ok&&e.summary==="refused"?"refused":!e.ok&&e.summary==="declined"?"declined":!e.ok&&e.summary==="stopped"?"stopped":e.ok?"ok":"failed",summary:e.summary,durationMs:e.durationMs,result:e.result}:n)};case"tool-verified":return{...s,toolCalls:s.toolCalls.map(n=>n.id===e.id?{...n,verification:e.verification}:n)};case"block":return{...s,breakBeforeNextText:true,blocks:[...s.blocks,{block:e.block,forToolCallId:e.forToolCallId}]};case"usage":return{...s,usage:{input:(s.usage?.input??0)+e.input,output:(s.usage?.output??0)+e.output,requests:(s.usage?.requests??0)+1}};case"error":return{...s,waiting:void 0,error:e.message,...e.hosted?{errorInfo:{code:e.hosted.code,requestId:e.hosted.request_id,action:e.hosted.action,resetAt:e.hosted.reset_at,helpUrl:e.hosted.help_url}}:{}};default:return s}});set({messages:r,...e.type==="awaiting-user"?{awaitingBlockId:e.blockId}:{},...e.type==="meter"&&e.frame.phase==="end"?{credits:{left:e.frame.credits_left??state.credits?.left??null,resetAt:e.frame.credits_reset??state.credits?.resetAt??null,pending:e.frame.pending===true}}:{},...e.type==="usage"?{usage:{input:state.usage.input+e.input,output:state.usage.output+e.output,requests:state.usage.requests+1}}:{}})},markStopped(t){set({messages:state.messages.map(e=>e.id===t?{...e,stopped:true}:e)})},finishAssistant(t){set({messages:state.messages.map(e=>e.id===t?{...e,streaming:false,endedAt:(0,_sharedUi.realNow)()}:e),busy:false,stopping:false})},askApproval(t){return new Promise(e=>{set({pending:{...t,resolve:e}})})},answerApproval(t){const e=state.pending;const r=typeof t==="boolean"?t:t.approved;const s=typeof t!=="boolean"&&t.trust===true&&r;const n=e?`${e.toolId}.${e.action}`:"";set({pending:null,...s&&e&&!state.trusted.some(a=>a.key===n)?{trusted:[...state.trusted,{key:n,label:e.label}]}:{}});e?.resolve(t)},untrust(t){heldSession?.session.untrust(t);set({trusted:state.trusted.filter(e=>e.key!==t)})},restorePending(t){if(state.pending||state.busy)return false;set({pending:{...t,restored:true,resolve:()=>{}}});return true},addNote(t){set({messages:[...state.messages,{id:nextId(),role:"assistant",text:t,toolCalls:[],blocks:[],endedAt:(0,_sharedUi.realNow)()}]})},hydrate(t){if(state.messages.length>0||state.busy)return false;set({messages:t.messages.map(e=>({...e,restored:true})),answers:t.answers,usage:t.usage,queued:t.queued??[]});return true},reset(){state.pending?.resolve(false);set({messages:[],busy:false,stopping:false,pending:null,trusted:[],notice:null,answers:{},awaitingBlockId:null,actionState:{},queued:[],draft:null,usage:{input:0,output:0,requests:0}})}};function turnPhaseOf(t){if(t.pending)return"awaitingApproval";if(t.busy)return t.stopping?"stopping":"running";if(t.awaitingBlockId)return"awaitingAnswer";return"idle"}function queuePauseReason(t){const e=turnPhaseOf(t);if(e==="running"||e==="stopping")return null;if(e==="awaitingApproval")return"Paused \u2014 answer the approval above first.";if(e==="awaitingAnswer")return"Paused \u2014 answer the question above first.";const r=t.messages[t.messages.length-1];if(r?.role==="assistant"&&r.error)return"Paused \u2014 the last turn failed.";if(r?.role==="assistant"&&r.stopped)return"Paused \u2014 you stopped the last turn.";return null}
3
+ ${e.delta}`:s.text+e.delta,breakBeforeNextText:false};case"reasoning":return{...s,breakBeforeNextText:true,reasoning:[...s.reasoning??[],{text:e.text,afterCalls:s.toolCalls.length}]};case"tool-start":return{...s,waiting:void 0,toolCalls:[...s.toolCalls,{id:e.id,toolId:e.toolId,action:e.action,label:e.label,description:e.description,status:"running",params:encodeParams(e.params),effect:e.effect,startedAt:(0,_sharedUi.realNow)()}]};case"tool-end":return{...s,breakBeforeNextText:true,toolCalls:s.toolCalls.map(n=>n.id===e.id?{...n,status:!e.ok&&e.summary==="refused"?"refused":!e.ok&&e.summary==="declined"?"declined":!e.ok&&e.summary==="stopped"?"stopped":e.ok?"ok":"failed",summary:e.summary,durationMs:e.durationMs,result:e.result}:n)};case"tool-verified":return{...s,toolCalls:s.toolCalls.map(n=>n.id===e.id?{...n,verification:e.verification}:n)};case"block":return{...s,breakBeforeNextText:true,blocks:[...s.blocks,{block:e.block,forToolCallId:e.forToolCallId}]};case"usage":return{...s,usage:{input:(s.usage?.input??0)+e.input,output:(s.usage?.output??0)+e.output,requests:(s.usage?.requests??0)+1}};case"error":return{...s,waiting:void 0,error:e.message,...e.hosted?{errorInfo:{code:e.hosted.code,requestId:e.hosted.request_id,action:e.hosted.action,resetAt:e.hosted.reset_at,helpUrl:e.hosted.help_url}}:{}};default:return s}});set({messages:r,...e.type==="awaiting-user"?{awaitingBlockId:e.blockId}:{},...e.type==="error"&&e.hosted?.code==="claude_paused"?{claudeOut:true}:{},...e.type==="meter"&&e.frame.phase==="end"?{credits:{left:e.frame.credits_left??state.credits?.left??null,resetAt:e.frame.credits_reset??state.credits?.resetAt??null,pending:e.frame.pending===true}}:{},...e.type==="usage"?{usage:{input:state.usage.input+e.input,output:state.usage.output+e.output,requests:state.usage.requests+1}}:{}})},markStopped(t){set({messages:state.messages.map(e=>e.id===t?{...e,stopped:true}:e)})},finishAssistant(t){set({messages:state.messages.map(e=>e.id===t?{...e,streaming:false,endedAt:(0,_sharedUi.realNow)()}:e),busy:false,stopping:false})},askApproval(t){return new Promise(e=>{set({pending:{...t,resolve:e}})})},answerApproval(t){const e=state.pending;const r=typeof t==="boolean"?t:t.approved;const s=typeof t!=="boolean"&&t.trust===true&&r;const n=e?`${e.toolId}.${e.action}`:"";set({pending:null,...s&&e&&!state.trusted.some(a=>a.key===n)?{trusted:[...state.trusted,{key:n,label:e.label}]}:{}});e?.resolve(t)},untrust(t){heldSession?.session.untrust(t);set({trusted:state.trusted.filter(e=>e.key!==t)})},restorePending(t){if(state.pending||state.busy)return false;set({pending:{...t,restored:true,resolve:()=>{}}});return true},addNote(t){set({messages:[...state.messages,{id:nextId(),role:"assistant",text:t,toolCalls:[],blocks:[],endedAt:(0,_sharedUi.realNow)()}]})},hydrate(t){if(state.messages.length>0||state.busy)return false;set({messages:t.messages.map(e=>({...e,restored:true})),answers:t.answers,usage:t.usage,queued:t.queued??[]});return true},reset(){state.pending?.resolve(false);set({messages:[],busy:false,stopping:false,pending:null,trusted:[],notice:null,answers:{},awaitingBlockId:null,actionState:{},queued:[],draft:null,usage:{input:0,output:0,requests:0}})}};function turnPhaseOf(t){if(t.pending)return"awaitingApproval";if(t.busy)return t.stopping?"stopping":"running";if(t.awaitingBlockId)return"awaitingAnswer";return"idle"}function queuePauseReason(t){const e=turnPhaseOf(t);if(e==="running"||e==="stopping")return null;if(e==="awaitingApproval")return"Paused \u2014 answer the approval above first.";if(e==="awaitingAnswer")return"Paused \u2014 answer the question above first.";const r=t.messages[t.messages.length-1];if(r?.role==="assistant"&&r.error)return"Paused \u2014 the last turn failed.";if(r?.role==="assistant"&&r.stopped)return"Paused \u2014 you stopped the last turn.";return null}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.hostedModelSetting=exports.DEFAULT_HOSTED_MODEL=void 0;exports.useHostedModel=useHostedModel;var _react=require("react");var _sharedUi=require("@buoy-gg/shared-ui");const DEFAULT_HOSTED_MODEL=exports.DEFAULT_HOSTED_MODEL="claude";const KEY=_sharedUi.devToolsStorageKeys.askBuoy.model();const sanitizeModel=e=>e==="luna"?"luna":"claude";const store=(0,_sharedUi.definePersistedSetting)(KEY,DEFAULT_HOSTED_MODEL,{sanitize:sanitizeModel});let current=DEFAULT_HOSTED_MODEL;let loaded=false;const listeners=new Set;function emit(){for(const e of listeners){try{e()}catch{}}}function update(e){if(e===current)return;current=e;emit()}const hostedModelSetting=exports.hostedModelSetting={get:()=>current,subscribe(e){listeners.add(e);if(!loaded){loaded=true;void store.load().then(update);(0,_sharedUi.subscribeStorageWrites)((r,t)=>{if(r!==KEY)return;try{update(sanitizeModel(t===null?DEFAULT_HOSTED_MODEL:JSON.parse(t)))}catch{}})}return()=>listeners.delete(e)},set(e){if(e===current)return;update(e);void store.save(e)}};function useHostedModel(){return(0,_react.useSyncExternalStore)(hostedModelSetting.subscribe,hostedModelSetting.get,hostedModelSetting.get)}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.useAskBuoy=useAskBuoy;exports.useAskBuoyState=useAskBuoyState;var _sharedUi=require("@buoy-gg/shared-ui");var _react=require("react");var _reactNative=require("react-native");var _agentCore=require("@buoy-gg/agent-core");var _askBuoyStore=require("./askBuoyStore");var _resetConversation=require("./resetConversation");var _transcriptPersistence=require("./transcriptPersistence");var _useAskBuoySettings=require("./useAskBuoySettings");const isRelease=typeof __DEV__!=="undefined"&&!__DEV__;function questionBehind(t){for(const r of _askBuoyStore.askBuoyStore.getState().messages){if(!r.restored)continue;for(const p of r.blocks){const c=p.block;if(c?.id!==t)continue;const l=c.question??c.label??c.title;return typeof l==="string"&&l.trim()?l.trim():void 0}}return void 0}function readAvailableActions(){try{const t=(0,_sharedUi.listToolActions)();for(const r of(0,_sharedUi.listSnapshotToolIds)()){if(!t[r])t[r]=[];if(!t[r].includes(_agentCore.SNAPSHOT_ACTION)){t[r]=[_agentCore.SNAPSHOT_ACTION,...t[r]]}}return t}catch{return{}}}const SAVE_DEBOUNCE_MS=600;function useAskBuoyState(){return(0,_react.useSyncExternalStore)(_askBuoyStore.askBuoyStore.subscribe,_askBuoyStore.askBuoyStore.getState,_askBuoyStore.askBuoyStore.getState)}function useAskBuoy(t){const r=useAskBuoyState();const p=(0,_sharedUi.getUseIsPro)()();const c=(0,_react.useRef)(p);c.current=p;const l=(0,_react.useCallback)((...e)=>{if(!(0,_sharedUi.hasAccountAccess)()||!c.current)return Promise.reject(new Error("Ask Buoy requires Pro access."));return(0,_sharedUi.dispatchToolAction)(...e)},[]);const u=(0,_react.useRef)(null);const C=(0,_react.useRef)(false);const y=(0,_react.useRef)(null);const R=(0,_react.useRef)(t);R.current=t;const f=!!t?.endpoint&&!!t?.model;const h=t?.persistTranscript!==false;const{bypassApprovals:B,readOnly:A}=(0,_useAskBuoySettings.useAskBuoySettings)();const v=(0,_react.useRef)(B);v.current=B;const b=(0,_react.useRef)(A);b.current=A;const m=(0,_react.useCallback)(()=>b.current?{...t?.policy??{},readOnly:true}:t?.policy??{},[t]);const[I,P]=(0,_react.useState)(0);(0,_react.useEffect)(()=>{let e=false;void(0,_sharedUi.waitForToolRegistry)().then(()=>{if(!e)P(s=>s+1)});return()=>{e=true}},[]);const k=(0,_react.useMemo)(()=>readAvailableActions(),[I]);const x=Object.keys(k).sort().join(",");(0,_react.useEffect)(()=>{_askBuoyStore.askBuoyStore.setConfigured(f);_askBuoyStore.askBuoyStore.setMeta({model:t?.model,protocol:t?.protocol??(t?"anthropic":void 0),appName:t?.appName,platform:_reactNative.Platform.OS,build:isRelease?"release":"development"});if(!f||!t){u.current=null;_askBuoyStore.askBuoyStore.setNotice("Ask Buoy isn't set up in this app yet. A developer needs to point it at your team's AI endpoint.");return}const e=t.protocol??"anthropic";if(isRelease&&t.apiKey){_askBuoyStore.askBuoyStore.setNotice("This build carries a plaintext apiKey \u2014 that's dev-only and it ships to every install. Use the headers callback with your own gateway for distributed builds.")}else if(e==="anthropic"&&/^https?:\/\/api\.openai\.com\//i.test(t.endpoint)){_askBuoyStore.askBuoyStore.setNotice('This endpoint is api.openai.com but the protocol is "anthropic". Set protocol: "openai" in the askBuoy config.')}else if(e==="openai"&&/^https?:\/\/api\.anthropic\.com\//i.test(t.endpoint)){_askBuoyStore.askBuoyStore.setNotice('This endpoint is api.anthropic.com but the protocol is "openai". Remove protocol or set it to "anthropic".')}else{_askBuoyStore.askBuoyStore.setNotice(null)}_askBuoyStore.askBuoyStore.setReporter(t.reportProblem??null);_askBuoyStore.askBuoyStore.setUsageReader(t.readUsage??null);const s=[t.endpoint,t.protocol??"anthropic",t.model].join("|");const o=_askBuoyStore.askBuoyStore.getHeldSession();if(o&&o.signature===s){u.current=o.session;o.session.setAvailableActions(k);o.session.setApprovalBypass(v.current);o.session.setReadOnly(b.current);_askBuoyStore.askBuoyStore.setLedger(o.session.ledger);return}const n=(0,_agentCore.createAskBuoySession)({endpoint:t.endpoint,protocol:t.protocol??"anthropic",model:t.model,headers:t.headers,apiKey:t.apiKey,maxTokens:t.maxTokens,anthropicVersion:t.anthropicVersion,requestOverrides:t.requestOverrides,hosted:t.hosted===true,catalog:t.tools?.length?[..._agentCore.CATALOG,...t.tools]:void 0,dispatch:l,readSnapshot:_sharedUi.readToolSnapshot,availableActions:k,policy:t.policy,context:t.context,isRelease,appName:t.appName,platform:_reactNative.Platform.OS,requestApproval:a=>_askBuoyStore.askBuoyStore.askApproval(a)});u.current=n;n.setApprovalBypass(v.current);n.setReadOnly(b.current);_askBuoyStore.askBuoyStore.holdSession(n,s);_askBuoyStore.askBuoyStore.setLedger(n.ledger);let i=false;void(0,_sharedUi.waitForToolRegistry)().then(a=>{if(a&&!i)void n.prime()});if(h&&!C.current){C.current=true;void(0,_transcriptPersistence.readTranscript)().then(a=>{if(!a)return;const V=_askBuoyStore.askBuoyStore.hydrate({messages:a.messages,answers:a.answers,usage:a.usage,queued:a.queued});if(!V)return;u.current?.restore(a.history);if(a.ledger?.length)u.current?.ledger.restore(a.ledger);if(a.pending)_askBuoyStore.askBuoyStore.restorePending(a.pending)})}return()=>{i=true}},[f,t,k,x,h]);(0,_react.useEffect)(()=>{u.current?.setApprovalBypass(B)},[B]);(0,_react.useEffect)(()=>{u.current?.setReadOnly(A)},[A]);const w=r.messages;const N=r.answers;const q=r.usage;const O=r.busy;const T=r.pending;const _=r.queued;(0,_react.useEffect)(()=>{if(!h)return;if(w.length===0)return;const e=()=>{void(0,_transcriptPersistence.writeTranscript)((0,_transcriptPersistence.buildPayload)({messages:w,history:u.current?.messages??[],answers:N,usage:q,pending:T,ledger:_askBuoyStore.askBuoyStore.getLedger()?.list(),queued:_}))};if(!O||T){e();return}const s=setTimeout(e,SAVE_DEBOUNCE_MS);return()=>clearTimeout(s)},[h,O,w,N,q,T,_]);const g=(0,_react.useCallback)(e=>{if(!(0,_sharedUi.hasAccountAccess)()||!c.current)return;const s=u.current;if(!s)return;_askBuoyStore.askBuoyStore.beginTurn();const o=_askBuoyStore.askBuoyStore.startAssistant();const n=new AbortController;y.current=n;s.setAvailableActions(readAvailableActions());const i=R.current?.onEvent;void(async()=>{try{for await(const a of s.send(e,n.signal)){_askBuoyStore.askBuoyStore.applyEvent(o,a);if(i){try{i(a)}catch{}}}}catch(a){if(!n.signal.aborted){_askBuoyStore.askBuoyStore.applyEvent(o,{type:"error",message:a instanceof Error?a.message:"Something went wrong talking to your AI endpoint."})}}finally{if(n.signal.aborted)_askBuoyStore.askBuoyStore.markStopped(o);_askBuoyStore.askBuoyStore.finishAssistant(o);y.current=null}})()},[]);const d=(0,_react.useCallback)(e=>{if(!(0,_sharedUi.hasAccountAccess)()||!c.current)return;const s=e.trim();if(!u.current||!s)return;if(_askBuoyStore.askBuoyStore.getState().busy){_askBuoyStore.askBuoyStore.enqueue(s);return}const o=_askBuoyStore.askBuoyStore.getState().awaitingBlockId;if(o)_askBuoyStore.askBuoyStore.recordAnswer({blockId:o,kind:"skipped"});_askBuoyStore.askBuoyStore.addUser(s);g(s)},[g]);const $=(0,_react.useCallback)(()=>{const{messages:e,busy:s}=_askBuoyStore.askBuoyStore.getState();if(s||!u.current)return;const o=[...e].reverse().find(n=>n.role==="user");if(!o)return;g(o.text)},[g]);const S=(0,_react.useCallback)(()=>{_askBuoyStore.askBuoyStore.requestStop();if(_askBuoyStore.askBuoyStore.getState().pending)_askBuoyStore.askBuoyStore.answerApproval(false);y.current?.abort();y.current=null},[]);(0,_react.useEffect)(()=>{const e=(0,_sharedUi.subscribeAccountAccess)(()=>{if(!(0,_sharedUi.hasAccountAccess)())S()});return()=>{e();S()}},[S]);(0,_react.useEffect)(()=>{if(!p)S()},[p,S]);(0,_react.useEffect)(()=>{if(r.queued.length===0)return;if((0,_askBuoyStore.turnPhaseOf)(r)!=="idle")return;if((0,_askBuoyStore.queuePauseReason)(r)!==null)return;const e=_askBuoyStore.askBuoyStore.shiftQueued();if(e)d(e.text)},[r,d]);const L=(0,_react.useCallback)(e=>_askBuoyStore.askBuoyStore.takeQueuedToDraft(e),[]);const U=(0,_react.useCallback)(e=>_askBuoyStore.askBuoyStore.removeQueued(e),[]);const j=(0,_react.useCallback)(()=>{y.current?.abort();y.current=null;(0,_resetConversation.clearConversation)()},[]);const D=(0,_react.useCallback)(()=>(0,_resetConversation.resetConversation)({dispatch:_sharedUi.dispatchToolAction,undoFirst:true}),[]);const E=(0,_react.useCallback)(async e=>{const s=_askBuoyStore.askBuoyStore.getLedger();if(!s){_askBuoyStore.askBuoyStore.setNotice(`${e.label} couldn't run \u2014 no active Ask Buoy session.`);return}const o=t?.tools?.length?[..._agentCore.CATALOG,...t.tools]:void 0;if(e.targetDigest){const i=await(0,_agentCore.readTargetDigest)({toolId:e.toolId,action:e.action,params:e.params??{},dispatch:l,catalog:o});if(i!==void 0&&i!==e.targetDigest){_askBuoyStore.askBuoyStore.addNote(`\u201C${e.label}\u201D wasn't applied \u2014 what it would change has moved on since the app restarted. Nothing was written. Ask again if you still want it.`);return}}const n=await(0,_agentCore.runGatedAction)({toolId:e.toolId,action:e.action,params:e.params??{},dispatch:l,ledger:s,policy:m(),isRelease,catalog:o});if(n.ok){_askBuoyStore.askBuoyStore.addNote(`Applied \u201C${e.label}\u201D, which was still waiting when the app restarted. Whatever else that answer was going to do is gone \u2014 ask again if there was more to it.`)}else{_askBuoyStore.askBuoyStore.setNotice(`${e.label} failed \u2014 ${n.error}`)}},[t,m]);const G=(0,_react.useCallback)(e=>{const s=_askBuoyStore.askBuoyStore.getState().pending;const o=typeof e==="boolean"?e:e.approved;_askBuoyStore.askBuoyStore.answerApproval(e);if(!s?.restored)return;if(!o){_askBuoyStore.askBuoyStore.addNote(`Skipped \u201C${s.label}\u201D. Nothing was changed.`);return}void E(s)},[E]);const K=(0,_react.useCallback)(e=>{_askBuoyStore.askBuoyStore.recordAnswer(e);const s=questionBehind(e.blockId);d(s?`${s} \u2014 ${(0,_agentCore.answerToText)(e)}`:(0,_agentCore.answerToText)(e))},[d]);const Q=(0,_react.useCallback)(async(e,s)=>{if(!(0,_sharedUi.hasAccountAccess)()||!c.current){_askBuoyStore.askBuoyStore.setNotice("Ask Buoy requires Pro access.");return}if(e.send){d(e.send);return}if(!e.dispatch)return;const o=`${s}:${e.label}`;const n=_askBuoyStore.askBuoyStore.getLedger();if(!n){_askBuoyStore.askBuoyStore.setNotice(`${e.label} failed \u2014 no active Ask Buoy session.`);return}_askBuoyStore.askBuoyStore.setActionState(o,"running");const i=await(0,_agentCore.runGatedAction)({toolId:e.dispatch.toolId,action:e.dispatch.action,params:e.dispatch.params??{},dispatch:l,ledger:n,policy:m(),isRelease,catalog:t?.tools?.length?[..._agentCore.CATALOG,...t.tools]:void 0});if(i.ok){_askBuoyStore.askBuoyStore.setActionState(o,"done")}else{_askBuoyStore.askBuoyStore.setActionState(o,"failed");_askBuoyStore.askBuoyStore.setNotice(`${e.label} failed \u2014 ${i.error}`)}},[d,t,m]);return{state:r,send:d,stop:S,reset:j,undoThenReset:D,approve:G,retry:$,answer:K,runAction:Q,editQueued:L,removeQueued:U,configured:f,toolCount:Object.keys(k).length,availableToolIds:Object.keys(k)}}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.useAskBuoy=useAskBuoy;exports.useAskBuoyState=useAskBuoyState;var _sharedUi=require("@buoy-gg/shared-ui");var _react=require("react");var _reactNative=require("react-native");var _agentCore=require("@buoy-gg/agent-core");var _askBuoyStore=require("./askBuoyStore");var _resetConversation=require("./resetConversation");var _transcriptPersistence=require("./transcriptPersistence");var _useAskBuoySettings=require("./useAskBuoySettings");var _hostedModel=require("../hostedModel");var _hostedModelSetting=require("./hostedModelSetting");const isRelease=typeof __DEV__!=="undefined"&&!__DEV__;function questionBehind(u){for(const r of _askBuoyStore.askBuoyStore.getState().messages){if(!r.restored)continue;for(const f of r.blocks){const l=f.block;if(l?.id!==u)continue;const d=l.question??l.label??l.title;return typeof d==="string"&&d.trim()?d.trim():void 0}}return void 0}function readAvailableActions(){try{const u=(0,_sharedUi.listToolActions)();for(const r of(0,_sharedUi.listSnapshotToolIds)()){if(!u[r])u[r]=[];if(!u[r].includes(_agentCore.SNAPSHOT_ACTION)){u[r]=[_agentCore.SNAPSHOT_ACTION,...u[r]]}}return u}catch{return{}}}const SAVE_DEBOUNCE_MS=600;function useAskBuoyState(){return(0,_react.useSyncExternalStore)(_askBuoyStore.askBuoyStore.subscribe,_askBuoyStore.askBuoyStore.getState,_askBuoyStore.askBuoyStore.getState)}function useAskBuoy(u){const r=useAskBuoyState();const f=(0,_hostedModelSetting.useHostedModel)();const l=(0,_react.useMemo)(()=>(0,_hostedModel.pickHostedModel)(u,f,r.claudeOut),[u,f,r.claudeOut]);const d=(0,_react.useRef)(l);if(r.messages.length===0||!d.current)d.current=l;const t=d.current;const q=(0,_react.useRef)(u);q.current=u;const E=!!u?.hostedModels?.claude;const I=r.messages.length===0;(0,_react.useEffect)(()=>{const e=q.current?.readUsage;if(!E||f!=="claude"||!I||!e)return;let o=true;e().then(s=>{if(o&&s.claude)_askBuoyStore.askBuoyStore.setClaudeOut(!s.claude.available)}).catch(()=>{});return()=>{o=false}},[E,f,I]);const S=(0,_sharedUi.getUseIsPro)()();const g=(0,_react.useRef)(S);g.current=S;const B=(0,_react.useCallback)((...e)=>{if(!(0,_sharedUi.hasAccountAccess)()||!g.current)return Promise.reject(new Error("Ask Buoy requires Pro access."));return(0,_sharedUi.dispatchToolAction)(...e)},[]);const i=(0,_react.useRef)(null);const P=(0,_react.useRef)(false);const y=(0,_react.useRef)(null);const M=(0,_react.useRef)(t);M.current=t;const b=!!t?.endpoint&&!!t?.model;const A=t?.persistTranscript!==false;const{bypassApprovals:m,readOnly:v}=(0,_useAskBuoySettings.useAskBuoySettings)();const O=(0,_react.useRef)(m);O.current=m;const w=(0,_react.useRef)(v);w.current=v;const T=(0,_react.useCallback)(()=>w.current?{...t?.policy??{},readOnly:true}:t?.policy??{},[t]);const[j,F]=(0,_react.useState)(0);(0,_react.useEffect)(()=>{let e=false;void(0,_sharedUi.waitForToolRegistry)().then(()=>{if(!e)F(o=>o+1)});return()=>{e=true}},[]);const k=(0,_react.useMemo)(()=>readAvailableActions(),[j]);const H=Object.keys(k).sort().join(",");(0,_react.useEffect)(()=>{_askBuoyStore.askBuoyStore.setConfigured(b);_askBuoyStore.askBuoyStore.setMeta({model:t?.model,protocol:t?.protocol??(t?"anthropic":void 0),appName:t?.appName,platform:_reactNative.Platform.OS,build:isRelease?"release":"development"});if(!b||!t){i.current=null;_askBuoyStore.askBuoyStore.setNotice("Ask Buoy isn't set up in this app yet. A developer needs to point it at your team's AI endpoint.");return}const e=t.protocol??"anthropic";if(isRelease&&t.apiKey){_askBuoyStore.askBuoyStore.setNotice("This build carries a plaintext apiKey \u2014 that's dev-only and it ships to every install. Use the headers callback with your own gateway for distributed builds.")}else if(e==="anthropic"&&/^https?:\/\/api\.openai\.com\//i.test(t.endpoint)){_askBuoyStore.askBuoyStore.setNotice('This endpoint is api.openai.com but the protocol is "anthropic". Set protocol: "openai" in the askBuoy config.')}else if(e==="openai"&&/^https?:\/\/api\.anthropic\.com\//i.test(t.endpoint)){_askBuoyStore.askBuoyStore.setNotice('This endpoint is api.anthropic.com but the protocol is "openai". Remove protocol or set it to "anthropic".')}else{_askBuoyStore.askBuoyStore.setNotice(null)}_askBuoyStore.askBuoyStore.setReporter(t.reportProblem??null);_askBuoyStore.askBuoyStore.setUsageReader(t.readUsage??null);const o=[t.endpoint,t.protocol??"anthropic",t.model].join("|");const s=_askBuoyStore.askBuoyStore.getHeldSession();if(s&&s.signature===o){i.current=s.session;s.session.setAvailableActions(k);s.session.setApprovalBypass(O.current);s.session.setReadOnly(w.current);_askBuoyStore.askBuoyStore.setLedger(s.session.ledger);return}const n=(0,_agentCore.createAskBuoySession)({endpoint:t.endpoint,protocol:t.protocol??"anthropic",model:t.model,headers:t.headers,apiKey:t.apiKey,maxTokens:t.maxTokens,anthropicVersion:t.anthropicVersion,requestOverrides:t.requestOverrides,hosted:t.hosted===true,...t.hostedFallback?{fallback:t.hostedFallback,onModelSwitch:a=>{const R=(a==="fallback"?t.hostedFallback?.model:t.model)===_hostedModel.HOSTED_CLAUDE_MODEL;_askBuoyStore.askBuoyStore.setMeta({model:a==="fallback"?t.hostedFallback?.model:t.model});if(a==="fallback"){if(!R)_askBuoyStore.askBuoyStore.setClaudeOut(true);_askBuoyStore.askBuoyStore.setNotice(R?"Luna is down right now, so Claude is answering.":"Claude is busy right now, so Luna is answering.")}else{_askBuoyStore.askBuoyStore.setNotice(null)}}}:{},catalog:t.tools?.length?[..._agentCore.CATALOG,...t.tools]:void 0,dispatch:B,readSnapshot:_sharedUi.readToolSnapshot,availableActions:k,policy:t.policy,context:t.context,isRelease,appName:t.appName,platform:_reactNative.Platform.OS,requestApproval:a=>_askBuoyStore.askBuoyStore.askApproval(a)});i.current=n;n.setApprovalBypass(O.current);n.setReadOnly(w.current);_askBuoyStore.askBuoyStore.holdSession(n,o);_askBuoyStore.askBuoyStore.setLedger(n.ledger);let c=false;void(0,_sharedUi.waitForToolRegistry)().then(a=>{if(a&&!c)void n.prime()});if(A&&!P.current){P.current=true;void(0,_transcriptPersistence.readTranscript)().then(a=>{if(!a)return;const R=_askBuoyStore.askBuoyStore.hydrate({messages:a.messages,answers:a.answers,usage:a.usage,queued:a.queued});if(!R)return;i.current?.restore(a.history);if(a.ledger?.length)i.current?.ledger.restore(a.ledger);if(a.pending)_askBuoyStore.askBuoyStore.restorePending(a.pending)})}return()=>{c=true}},[b,t,k,H,A]);(0,_react.useEffect)(()=>{i.current?.setApprovalBypass(m)},[m]);(0,_react.useEffect)(()=>{i.current?.setReadOnly(v)},[v]);const N=r.messages;const x=r.answers;const $=r.usage;const L=r.busy;const _=r.pending;const D=r.queued;(0,_react.useEffect)(()=>{if(!A)return;if(N.length===0)return;const e=()=>{void(0,_transcriptPersistence.writeTranscript)((0,_transcriptPersistence.buildPayload)({messages:N,history:i.current?.messages??[],answers:x,usage:$,pending:_,ledger:_askBuoyStore.askBuoyStore.getLedger()?.list(),queued:D}))};if(!L||_){e();return}const o=setTimeout(e,SAVE_DEBOUNCE_MS);return()=>clearTimeout(o)},[A,L,N,x,$,_,D]);const C=(0,_react.useCallback)(e=>{if(!(0,_sharedUi.hasAccountAccess)()||!g.current)return;const o=i.current;if(!o)return;_askBuoyStore.askBuoyStore.beginTurn();const s=_askBuoyStore.askBuoyStore.startAssistant();const n=new AbortController;y.current=n;o.setAvailableActions(readAvailableActions());const c=M.current?.onEvent;void(async()=>{try{for await(const a of o.send(e,n.signal)){_askBuoyStore.askBuoyStore.applyEvent(s,a);if(c){try{c(a)}catch{}}}}catch(a){if(!n.signal.aborted){_askBuoyStore.askBuoyStore.applyEvent(s,{type:"error",message:a instanceof Error?a.message:"Something went wrong talking to your AI endpoint."})}}finally{if(n.signal.aborted)_askBuoyStore.askBuoyStore.markStopped(s);_askBuoyStore.askBuoyStore.finishAssistant(s);y.current=null}})()},[]);const p=(0,_react.useCallback)(e=>{if(!(0,_sharedUi.hasAccountAccess)()||!g.current)return;const o=e.trim();if(!i.current||!o)return;if(_askBuoyStore.askBuoyStore.getState().busy){_askBuoyStore.askBuoyStore.enqueue(o);return}const s=_askBuoyStore.askBuoyStore.getState().awaitingBlockId;if(s)_askBuoyStore.askBuoyStore.recordAnswer({blockId:s,kind:"skipped"});_askBuoyStore.askBuoyStore.addUser(o);C(o)},[C]);const G=(0,_react.useCallback)(()=>{const{messages:e,busy:o}=_askBuoyStore.askBuoyStore.getState();if(o||!i.current)return;const s=[...e].reverse().find(n=>n.role==="user");if(!s)return;C(s.text)},[C]);const h=(0,_react.useCallback)(()=>{_askBuoyStore.askBuoyStore.requestStop();if(_askBuoyStore.askBuoyStore.getState().pending)_askBuoyStore.askBuoyStore.answerApproval(false);y.current?.abort();y.current=null},[]);(0,_react.useEffect)(()=>{const e=(0,_sharedUi.subscribeAccountAccess)(()=>{if(!(0,_sharedUi.hasAccountAccess)())h()});return()=>{e();h()}},[h]);(0,_react.useEffect)(()=>{if(!S)h()},[S,h]);(0,_react.useEffect)(()=>{if(r.queued.length===0)return;if((0,_askBuoyStore.turnPhaseOf)(r)!=="idle")return;if((0,_askBuoyStore.queuePauseReason)(r)!==null)return;const e=_askBuoyStore.askBuoyStore.shiftQueued();if(e)p(e.text)},[r,p]);const K=(0,_react.useCallback)(e=>_askBuoyStore.askBuoyStore.takeQueuedToDraft(e),[]);const Q=(0,_react.useCallback)(e=>_askBuoyStore.askBuoyStore.removeQueued(e),[]);const V=(0,_react.useCallback)(()=>{y.current?.abort();y.current=null;(0,_resetConversation.clearConversation)()},[]);const W=(0,_react.useCallback)(()=>(0,_resetConversation.resetConversation)({dispatch:_sharedUi.dispatchToolAction,undoFirst:true}),[]);const U=(0,_react.useCallback)(async e=>{const o=_askBuoyStore.askBuoyStore.getLedger();if(!o){_askBuoyStore.askBuoyStore.setNotice(`${e.label} couldn't run \u2014 no active Ask Buoy session.`);return}const s=t?.tools?.length?[..._agentCore.CATALOG,...t.tools]:void 0;if(e.targetDigest){const c=await(0,_agentCore.readTargetDigest)({toolId:e.toolId,action:e.action,params:e.params??{},dispatch:B,catalog:s});if(c!==void 0&&c!==e.targetDigest){_askBuoyStore.askBuoyStore.addNote(`\u201C${e.label}\u201D wasn't applied \u2014 what it would change has moved on since the app restarted. Nothing was written. Ask again if you still want it.`);return}}const n=await(0,_agentCore.runGatedAction)({toolId:e.toolId,action:e.action,params:e.params??{},dispatch:B,ledger:o,policy:T(),isRelease,catalog:s});if(n.ok){_askBuoyStore.askBuoyStore.addNote(`Applied \u201C${e.label}\u201D, which was still waiting when the app restarted. Whatever else that answer was going to do is gone \u2014 ask again if there was more to it.`)}else{_askBuoyStore.askBuoyStore.setNotice(`${e.label} failed \u2014 ${n.error}`)}},[t,T]);const z=(0,_react.useCallback)(e=>{const o=_askBuoyStore.askBuoyStore.getState().pending;const s=typeof e==="boolean"?e:e.approved;_askBuoyStore.askBuoyStore.answerApproval(e);if(!o?.restored)return;if(!s){_askBuoyStore.askBuoyStore.addNote(`Skipped \u201C${o.label}\u201D. Nothing was changed.`);return}void U(o)},[U]);const J=(0,_react.useCallback)(e=>{_askBuoyStore.askBuoyStore.recordAnswer(e);const o=questionBehind(e.blockId);p(o?`${o} \u2014 ${(0,_agentCore.answerToText)(e)}`:(0,_agentCore.answerToText)(e))},[p]);const X=(0,_react.useCallback)(async(e,o)=>{if(!(0,_sharedUi.hasAccountAccess)()||!g.current){_askBuoyStore.askBuoyStore.setNotice("Ask Buoy requires Pro access.");return}if(e.send){p(e.send);return}if(!e.dispatch)return;const s=`${o}:${e.label}`;const n=_askBuoyStore.askBuoyStore.getLedger();if(!n){_askBuoyStore.askBuoyStore.setNotice(`${e.label} failed \u2014 no active Ask Buoy session.`);return}_askBuoyStore.askBuoyStore.setActionState(s,"running");const c=await(0,_agentCore.runGatedAction)({toolId:e.dispatch.toolId,action:e.dispatch.action,params:e.dispatch.params??{},dispatch:B,ledger:n,policy:T(),isRelease,catalog:t?.tools?.length?[..._agentCore.CATALOG,...t.tools]:void 0});if(c.ok){_askBuoyStore.askBuoyStore.setActionState(s,"done")}else{_askBuoyStore.askBuoyStore.setActionState(s,"failed");_askBuoyStore.askBuoyStore.setNotice(`${e.label} failed \u2014 ${c.error}`)}},[p,t,T]);return{state:r,send:p,stop:h,reset:V,undoThenReset:W,approve:z,retry:G,answer:J,runAction:X,editQueued:K,removeQueued:Q,configured:b,toolCount:Object.keys(k).length,availableToolIds:Object.keys(k)}}
@@ -1,3 +1,3 @@
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}
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 Pe,StyleSheet as $e,Text as N,View as B}from"react-native";import{isReversible as He}from"@buoy-gg/agent-core";import{dispatchToolAction as Re,FeatureGate as Ne,Info as X,JsModal as Oe,ModalHeader as O,night as i,setToolAttention as u,useSafeAreaInsets as De}from"@buoy-gg/shared-ui";import{BlockHandlersProvider as Le}from"../blocks";import{ApprovalCard as Me}from"./ApprovalCard";import{ChangesBar as Ke,useLedgerEntries as Ee}from"./ChangesBar";import{ruleStatesFrom as je}from"../blocks/ruleStates";import{Composer as qe}from"./Composer";import{KeyboardPad as ze}from"./KeyboardPad";import{AskBuoyHeader as Fe}from"./AskBuoyHeader";import{AskBuoySettings as Ve}from"./AskBuoySettings";import{EmptyState as Ue}from"./EmptyState";import{MessageList as _e,transcriptToText as Qe}from"./MessageList";import{QueuedMessages as We}from"./QueuedMessages";import{askBuoyStore as l,queuePauseReason as Ge}from"../store/askBuoyStore";import{TICKET_PROMPT as Je}from"../store/ticketPrompt";import{useAskBuoy as Ye}from"../store/useAskBuoy";import{useAskBuoySettings as Xe}from"../store/useAskBuoySettings";import{askBuoySettings as Z}from"../store/askBuoySettings";import{jsx as o,jsxs as d,Fragment as Ze}from"react/jsx-runtime";export function AskBuoyModal({visible:x,onClose:D,onBack:L,onMinimize:b,persistenceKey:ee,config:g}){const{state:e,send:k,stop:C,reset:S,undoThenReset:M,approve:te,retry:oe,answer:K,runAction:E,editQueued:ne,removeQueued:se,configured:I,toolCount:re,availableToolIds:ae}=Ye(g);const[ie,le]=H(false);const ce=a(()=>{C();D()},[C,D]);const ue=a(()=>l.clearDraft(),[]);const[v,P]=H(false);const de=a(()=>P(false),[]);const ge=a(()=>P(true),[]);const p=!x;const j=!!e.pending||!!e.awaitingBlockId;const s=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&&s?.role==="assistant"&&s.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,s]);y(()=>()=>u("ask-buoy",null),[]);y(()=>{if(e.pending&&v)P(false)},[e.pending,v]);const pe=De({minBottom:16});const[fe,me]=H("bottomSheet");const ye=fe==="floating"?0:pe.bottom;const he=a(()=>{const t=l.getLedger();if(!t)return;void(async()=>{const n=await t.revertAll(Re);if(n.failed.length===0){l.setNotice(null);return}const r=n.failed.map(m=>`${m.label} (${m.error})`).join("; ");l.setNotice(`Couldn't undo ${n.failed.length} change${n.failed.length===1?"":"s"} \u2014 ${r}`)})()},[]);const be=a(()=>le(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 n=typeof t==="number"&&t>200?t:560;R.timing(f,{toValue:120,duration:220,useNativeDriver:false}).start(()=>{setTimeout(()=>{R.spring(f,{toValue:n,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 n=t.blocks.filter(r=>r.block.kind==="peek").length;return n>0?`${t.id}:${n}`:""},[e.messages]);const F=h("");y(()=>{if(A&&A!==F.current&&g?.peek!==false)z();F.current=A},[A,z,g]);const ke=a(()=>{const t=l.getLedger();const n=t?.list().filter(r=>!r.undoneAt&&He(r.effect)).length??0;if(n===0){S();return}Y.alert("Undo changes first?",`Ask Buoy made ${n} change${n===1?"":"s"} that can still be undone. Clearing the conversation loses the only automatic undo for ${n===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 Ie=r.failed.map(J=>`${J.label} (${J.error})`).join("\n");Y.alert(`Couldn't undo ${r.failed.length} of ${m} change${m===1?"":"s"}`,`${Ie}
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=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"}});
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 Se=T(()=>{const t=e.messages[e.messages.length-1];if(!t||t.role!=="assistant"||!t.streaming)return null;return t.toolCalls.find(n=>n.status==="running")?.label??null},[e.messages]);const V=Ee(l.getLedger());const U=T(()=>je(V),[V]);const ve=T(()=>({answer:K,runAction:(t,n)=>{if(t.minimize){b?.({mode:"bottomSheet"});return}return E(t,n)},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 we=!e.busy&&s?.role==="assistant"&&!!s.error;const _=!e.busy&&s?.role==="assistant"&&s.stopped&&!s.error?s.id:null;const Ae=Ge(e);const{showThinking:Q,readOnly:W,claudeNoticeSeen:Te}=Xe();const G=g?.hosted===true&&!Te;const Be=a(()=>Qe(e.messages,Q),[e.messages,Q]);const xe=a(()=>k(Je),[k]);const Ce=v?d(O,{children:[o(O.Navigation,{onBack:de}),o(O.Content,{title:"Settings",subtitle:"Ask Buoy",testID:"buoy-ask-buoy-settings-title"})]}):o(Fe,{configured:I,busy:e.busy,failed:we,activeLabel:Se,awaitingApproval:!!e.pending,startedAt:s?.role==="assistant"&&s.streaming?s.startedAt??null:null,stopping:e.stopping,readOnly:W,toolCount:re,hasMessages:e.messages.length>0,onBack:L,onReset:ke,getTranscript:Be,onOpenSettings:ge,onTicket:xe});return o(Oe,{visible:x,onClose:ce,disableScrollWrapper:true,background:false,initialHeight:560,animatedHeight:f,header:{customContent:Ce,showToggleButton:false},onBack:L,onMinimize:b,onModeChange:me,persistenceKey:ee??"buoy-ask-buoy",enablePersistence:true,children:o(Le,{value:ve,children:d(B,{style:[c.root,{paddingBottom:ye}],children:[o(Ke,{ledger:l.getLedger(),onUndoAll:he,expanded:ie,onToggle:be}),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(N,{style:c.noticeText,children:"Hosted Ask Buoy (beta) sends your question, and the app data it needs to answer, to Anthropic for Claude. If Claude is down, it goes to OpenAI through OpenRouter for Luna instead. Buoy keeps the cost of each answer, never the chat. Your plan includes weekly credits."}),o(Pe,{testID:"buoy-ask-buoy-hosted-notice-ok",accessibilityRole:"button",onPress:()=>{Z.set("claudeNoticeSeen",true);Z.set("hostedNoticeSeen",true)},style:({pressed:t})=>[c.noticeButton,t&&{opacity:.7}],children:o(N,{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(N,{style:c.noticeText,children:e.notice})]}),o(ze,{children:o(Ne,{featureName:"Ask Buoy",children:v?o(Ve,{}):d(Ze,{children:[o(_e,{messages:e.messages,onRetry:oe,waitingOnUser:!!e.pending||!!e.awaitingBlockId,ListEmptyComponent:o(Ue,{configured:I,onPick:k,availableToolIds:ae,readOnly:W||!!g?.policy?.readOnly})}),o(We,{items:e.queued,paused:Ae,onEdit:ne,onRemove:se}),e.pending&&o(Me,{pending:e.pending,onAnswer:te}),o(qe,{onSend:k,onStop:C,busy:e.busy,draft:e.draft,onDraftTaken:ue,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=$e.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{useCallback as b,useEffect as f,useState as m}from"react";import{Pressable as g,ScrollView as w,StyleSheet as u,Text as s,View as c}from"react-native";import{AlertTriangle as k,night as t,ToggleRow as h}from"@buoy-gg/shared-ui";import{useAskBuoySettings as S}from"../store/useAskBuoySettings";import{askBuoySettings as y}from"../store/askBuoySettings";import{askBuoyStore as p}from"../store/askBuoyStore";import{useAskBuoyState as T}from"../store/useAskBuoy";import{jsx as o,jsxs as i,Fragment as x}from"react/jsx-runtime";export function AskBuoySettings(){const n=S();const a=T().trusted;return i(w,{style:e.scroll,contentContainerStyle:e.content,testID:"buoy-ask-buoy-settings",children:[o(I,{}),o(s,{style:e.section,children:"CHAT"}),o(h,{testID:"buoy-ask-buoy-setting-thinking",label:"Show agent thinking",description:"Adds a collapsed strip under each answer: the model's reasoning, and every step it ran with its parameters, result, duration and token cost. Copy includes the steps and reasoning while this is on, so you can read the run outside the app.",value:n.showThinking,onValueChange:r=>y.set("showThinking",r)}),o(s,{style:e.note,children:"Not every model reports reasoning. Claude thinks by default; most OpenAI models return none, and the strip then shows the steps alone."}),o(s,{style:e.section,children:"PERMISSIONS"}),o(h,{testID:"buoy-ask-buoy-setting-readonly",label:"Read only",description:"Inspect the app, change nothing. Every write and simulation is refused until this is off. Undo still works \u2014 putting things back is the safe direction.",value:n.readOnly,onValueChange:r=>y.set("readOnly",r)}),n.readOnly?o(s,{style:e.note,testID:"buoy-ask-buoy-readonly-note",children:"On across reloads until you turn it off here. The header says \u201CRead only\u201D while it is on. It adds to whatever this app already restricts \u2014 it never loosens anything."}):null,o(h,{testID:"buoy-ask-buoy-setting-bypass",tone:"warning",emphasizeLabel:n.bypassApprovals,disabled:n.readOnly,label:"Skip approval prompts",description:n.readOnly?"Unavailable while Read only is on \u2014 nothing can be approved.":"Runs every action the moment the agent calls it, with no Allow card \u2014 including the destructive ones that wipe or reset data.",value:n.bypassApprovals,onValueChange:r=>y.set("bypassApprovals",r)}),n.bypassApprovals&&!n.readOnly?i(c,{style:e.warning,testID:"buoy-ask-buoy-bypass-warning",children:[o(k,{size:14,color:t.color.warning}),o(s,{style:e.warningText,children:"Nothing will ask before a change lands. A wipe or a reset runs on the model's word alone, and Undo only reverses the changes that can be reversed \u2014 the bar tells you which. This stays on across reloads and app restarts until you turn it off here, so leave it off on any device holding data you care about. It does not widen what the agent may reach: read-only mode, this app's deny list and the release-build refusals still apply."})]}):o(s,{style:e.note,children:"On by default for destructive actions only \u2014 reads and ordinary writes already run without asking, and the changes bar records them."}),a.length>0?i(c,{style:e.trustedBox,testID:"buoy-ask-buoy-trusted",children:[o(s,{style:e.section,children:"ALLOWED WITHOUT ASKING \u2014 THIS CHAT"}),a.map(r=>i(c,{style:e.trustedRow,children:[o(s,{style:e.trustedLabel,numberOfLines:2,children:r.label}),o(g,{testID:`buoy-ask-buoy-untrust-${r.key}`,accessibilityRole:"button",onPress:()=>p.untrust(r.key),style:({pressed:l})=>[e.forget,l&&{opacity:.7}],hitSlop:6,children:o(s,{style:e.forgetText,children:"Ask again"})})]},r.key))]}):null]})}function A(n){const a=Date.parse(n.length===10?`${n}T00:00:00Z`:n);if(Number.isNaN(a))return n;const r=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];const l=new Date(a);return`${r[l.getUTCMonth()]} ${l.getUTCDate()}`}function I(){const n=p.getUsageReader();const[a,r]=m({kind:"loading"});const l=b(()=>{if(!n)return;r({kind:"loading"});n().then(d=>r({kind:"ok",usage:d})).catch(d=>r({kind:"error",message:d instanceof Error?d.message:"Could not load credits."}))},[n]);f(l,[l]);if(!n)return null;return i(c,{testID:"buoy-ask-buoy-credits",children:[o(s,{style:e.section,children:"HOSTED AI CREDITS"}),i(c,{style:e.trustedRow,children:[o(c,{style:{flex:1,gap:2},children:a.kind==="ok"?i(x,{children:[i(s,{style:e.trustedLabel,children:[a.usage.creditsLeft.toLocaleString()," of ",a.usage.grantCredits.toLocaleString()," credits left"]}),i(s,{style:e.note,children:[a.usage.resetAt?`Resets ${A(a.usage.resetAt)}. `:"","1 credit is $0.001 of AI use, included with your plan."]})]}):a.kind==="error"?o(s,{style:e.note,children:a.message}):o(s,{style:e.note,children:"Loading\u2026"})}),o(g,{testID:"buoy-ask-buoy-credits-refresh",accessibilityRole:"button",onPress:l,style:({pressed:d})=>[e.forget,d&&{opacity:.7}],hitSlop:6,children:o(s,{style:e.forgetText,children:"Refresh"})})]})]})}const e=u.create({scroll:{flex:1},content:{padding:t.gutter,gap:10,paddingBottom:32},section:{color:t.color.textTertiary,fontSize:10,fontWeight:"700",letterSpacing:.6,paddingHorizontal:4,paddingTop:4},warning:{flexDirection:"row",gap:8,padding:t.cardPad,borderRadius:t.radius.card,borderWidth:u.hairlineWidth,borderColor:t.color.warning,backgroundColor:t.color.surface},warningText:{flex:1,color:t.color.warning,fontSize:t.font.micro,lineHeight:16},trustedBox:{gap:6,paddingTop:6},trustedRow:{flexDirection:"row",alignItems:"center",gap:10,padding:t.cardPad,borderRadius:t.radius.card,borderWidth:u.hairlineWidth,borderColor:t.color.border,backgroundColor:t.color.surface},trustedLabel:{flex:1,color:t.color.text,fontSize:t.font.caption,lineHeight:18},forget:{paddingHorizontal:10,paddingVertical:6,borderRadius:t.radius.row,backgroundColor:t.color.button},forgetText:{color:t.color.text,fontSize:t.font.micro,fontWeight:"600"},note:{color:t.color.textTertiary,fontSize:t.font.micro,lineHeight:16,paddingHorizontal:4}});
1
+ "use strict";import{useCallback as f,useEffect as k,useState as w}from"react";import{Pressable as h,ScrollView as m,StyleSheet as y,Text as r,View as c}from"react-native";import{AlertTriangle as S,night as s,ToggleRow as g}from"@buoy-gg/shared-ui";import{useAskBuoySettings as T}from"../store/useAskBuoySettings";import{askBuoySettings as p}from"../store/askBuoySettings";import{hostedModelSetting as x,useHostedModel as I}from"../store/hostedModelSetting";import{askBuoyStore as u}from"../store/askBuoyStore";import{useAskBuoyState as b}from"../store/useAskBuoy";import{jsx as t,jsxs as l,Fragment as A}from"react/jsx-runtime";export function AskBuoySettings(){const n=T();const a=b().trusted;return l(m,{style:e.scroll,contentContainerStyle:e.content,testID:"buoy-ask-buoy-settings",children:[t(D,{}),t(v,{}),t(r,{style:e.section,children:"CHAT"}),t(g,{testID:"buoy-ask-buoy-setting-thinking",label:"Show agent thinking",description:"Adds a collapsed strip under each answer: the model's reasoning, and every step it ran with its parameters, result, duration and token cost. Copy includes the steps and reasoning while this is on, so you can read the run outside the app.",value:n.showThinking,onValueChange:o=>p.set("showThinking",o)}),t(r,{style:e.note,children:"Not every model reports reasoning. Claude thinks by default; most OpenAI models return none, and the strip then shows the steps alone."}),t(r,{style:e.section,children:"PERMISSIONS"}),t(g,{testID:"buoy-ask-buoy-setting-readonly",label:"Read only",description:"Inspect the app, change nothing. Every write and simulation is refused until this is off. Undo still works \u2014 putting things back is the safe direction.",value:n.readOnly,onValueChange:o=>p.set("readOnly",o)}),n.readOnly?t(r,{style:e.note,testID:"buoy-ask-buoy-readonly-note",children:"On across reloads until you turn it off here. The header says \u201CRead only\u201D while it is on. It adds to whatever this app already restricts \u2014 it never loosens anything."}):null,t(g,{testID:"buoy-ask-buoy-setting-bypass",tone:"warning",emphasizeLabel:n.bypassApprovals,disabled:n.readOnly,label:"Skip approval prompts",description:n.readOnly?"Unavailable while Read only is on \u2014 nothing can be approved.":"Runs every action the moment the agent calls it, with no Allow card \u2014 including the destructive ones that wipe or reset data.",value:n.bypassApprovals,onValueChange:o=>p.set("bypassApprovals",o)}),n.bypassApprovals&&!n.readOnly?l(c,{style:e.warning,testID:"buoy-ask-buoy-bypass-warning",children:[t(S,{size:14,color:s.color.warning}),t(r,{style:e.warningText,children:"Nothing will ask before a change lands. A wipe or a reset runs on the model's word alone, and Undo only reverses the changes that can be reversed \u2014 the bar tells you which. This stays on across reloads and app restarts until you turn it off here, so leave it off on any device holding data you care about. It does not widen what the agent may reach: read-only mode, this app's deny list and the release-build refusals still apply."})]}):t(r,{style:e.note,children:"On by default for destructive actions only \u2014 reads and ordinary writes already run without asking, and the changes bar records them."}),a.length>0?l(c,{style:e.trustedBox,testID:"buoy-ask-buoy-trusted",children:[t(r,{style:e.section,children:"ALLOWED WITHOUT ASKING \u2014 THIS CHAT"}),a.map(o=>l(c,{style:e.trustedRow,children:[t(r,{style:e.trustedLabel,numberOfLines:2,children:o.label}),t(h,{testID:`buoy-ask-buoy-untrust-${o.key}`,accessibilityRole:"button",onPress:()=>u.untrust(o.key),style:({pressed:i})=>[e.forget,i&&{opacity:.7}],hitSlop:6,children:t(r,{style:e.forgetText,children:"Ask again"})})]},o.key))]}):null]})}function C(n){const a=Date.parse(n.length===10?`${n}T00:00:00Z`:n);if(Number.isNaN(a))return n;const o=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];const i=new Date(a);return`${o[i.getUTCMonth()]} ${i.getUTCDate()}`}function D(){const n=u.getUsageReader();const[a,o]=w({kind:"loading"});const i=f(()=>{if(!n)return;o({kind:"loading"});n().then(d=>{if(d.claude)u.setClaudeOut(!d.claude.available);o({kind:"ok",usage:d})}).catch(d=>o({kind:"error",message:d instanceof Error?d.message:"Could not load credits."}))},[n]);k(i,[i]);if(!n)return null;return l(c,{testID:"buoy-ask-buoy-credits",children:[t(r,{style:e.section,children:"HOSTED AI CREDITS"}),l(c,{style:e.trustedRow,children:[t(c,{style:{flex:1,gap:2},children:a.kind==="ok"?l(A,{children:[l(r,{style:e.trustedLabel,children:[a.usage.creditsLeft.toLocaleString()," of ",a.usage.grantCredits.toLocaleString()," credits left"]}),l(r,{style:e.note,children:[a.usage.resetAt?`Resets ${C(a.usage.resetAt)}. `:"","That's about ",Math.floor(a.usage.creditsLeft/5).toLocaleString()," more asks, included with your plan."]})]}):a.kind==="error"?t(r,{style:e.note,children:a.message}):t(r,{style:e.note,children:"Loading\u2026"})}),t(h,{testID:"buoy-ask-buoy-credits-refresh",accessibilityRole:"button",onPress:i,style:({pressed:d})=>[e.forget,d&&{opacity:.7}],hitSlop:6,children:t(r,{style:e.forgetText,children:"Refresh"})})]})]})}const R=[{id:"claude",label:"Claude Haiku 5.5",note:"The default. Answers faster."},{id:"luna",label:"GPT-6 Luna",note:"Uses fewer credits."}];function v(){const n=I();const{claudeOut:a}=b();if(!u.getUsageReader())return null;return l(c,{testID:"buoy-ask-buoy-model",style:{gap:6},children:[t(r,{style:e.section,children:"MODEL"}),R.map(o=>{const i=n===o.id;return l(h,{testID:`buoy-ask-buoy-model-${o.id}`,accessibilityRole:"radio",accessibilityState:{checked:i},onPress:()=>x.set(o.id),style:({pressed:d})=>[e.trustedRow,i&&e.picked,d&&{opacity:.7}],children:[l(c,{style:{flex:1,gap:2},children:[t(r,{style:e.trustedLabel,children:o.label}),t(r,{style:e.note,children:o.note})]}),t(r,{style:e.forgetText,children:i?"On":""})]},o.id)}),t(r,{style:e.note,testID:"buoy-ask-buoy-model-note",children:n==="claude"&&a?"Claude is busy right now. New chats use Luna until it opens again.":"If one model is down, the other answers. A change starts with your next new chat. You can also pick it on buoy.gg, on the Ask Buoy tab."})]})}const e=y.create({picked:{borderColor:s.color.info},scroll:{flex:1},content:{padding:s.gutter,gap:10,paddingBottom:32},section:{color:s.color.textTertiary,fontSize:10,fontWeight:"700",letterSpacing:.6,paddingHorizontal:4,paddingTop:4},warning:{flexDirection:"row",gap:8,padding:s.cardPad,borderRadius:s.radius.card,borderWidth:y.hairlineWidth,borderColor:s.color.warning,backgroundColor:s.color.surface},warningText:{flex:1,color:s.color.warning,fontSize:s.font.micro,lineHeight:16},trustedBox:{gap:6,paddingTop:6},trustedRow:{flexDirection:"row",alignItems:"center",gap:10,padding:s.cardPad,borderRadius:s.radius.card,borderWidth:y.hairlineWidth,borderColor:s.color.border,backgroundColor:s.color.surface},trustedLabel:{flex:1,color:s.color.text,fontSize:s.font.caption,lineHeight:18},forget:{paddingHorizontal:10,paddingVertical:6,borderRadius:s.radius.row,backgroundColor:s.color.button},forgetText:{color:s.color.text,fontSize:s.font.micro,fontWeight:"600"},note:{color:s.color.textTertiary,fontSize:s.font.micro,lineHeight:16,paddingHorizontal:4}});
@@ -1 +1 @@
1
- "use strict";import{Platform as g}from"react-native";import*as h from"@buoy-gg/license";import{HOSTED_HEADERS as o,HOSTED_PROTOCOL_VERSION as l}from"@buoy-gg/agent-core";export const HOSTED_ENDPOINT="https://ai.buoy.gg/v1/chat/completions";export const HOSTED_MODEL="openai/gpt-6-luna";const w=10*6e4;async function c(s=false){const t=h.SessionManager;if(!t)return null;const i=t.getStatus();if(i.state==="active"&&(s||i.grace||(i.validUntil??0)-Date.now()<w)){await t.refresh(true)}return t.getAccessToken()}function f(s){const t=/^(https?:\/\/[^/]+)/.exec(s);return t?t[1]:s}export function hostedAskBuoy(s={}){const{endpoint:t=HOSTED_ENDPOINT,...i}=s;const u=`ask-buoy ${g.OS}`;return{...i,endpoint:t,hosted:true,protocol:"openai",model:HOSTED_MODEL,maxTokens:8192,headers:async e=>{const r=await c((e?.attempt??1)>1);return{...r?{[o.session]:r}:{},[o.ask]:e?.askId??"ask_unknown000",[o.round]:String(e?.round??1),[o.attempt]:String(e?.attempt??1),[o.protocol]:String(l),[o.client]:u}},readUsage:async()=>{const e=await c();if(!e)throw new Error("Sign in to see hosted credits.");const r=await fetch(`${f(t)}/v1/usage`,{headers:{[o.session]:e}});if(!r.ok)throw new Error(`usage HTTP ${r.status}`);const n=await r.json();return{creditsLeft:n.creditsLeft??0,grantCredits:n.grantCredits??0,spentCredits:n.spentCredits??0,resetAt:n.resetAt??null}},reportProblem:async({requestId:e,code:r,note:n})=>{const p=await c();const a=await fetch(`${f(t)}/v1/report`,{method:"POST",headers:{"Content-Type":"application/json",...p?{[o.session]:p}:{}},body:JSON.stringify({request_id:e,code:r,note:n,client:u})});if(!a.ok)throw new Error(`report HTTP ${a.status}`);const d=await a.json();if(!d.report_id)throw new Error("report: no id");return{reportId:d.report_id}}}}
1
+ "use strict";import{Platform as f}from"react-native";import*as g from"@buoy-gg/license";import{HOSTED_HEADERS as s,HOSTED_PROTOCOL_VERSION as E}from"@buoy-gg/agent-core";import{HOSTED_CLAUDE_MODEL as h}from"./hostedModel";export{HOSTED_CLAUDE_MODEL,pickHostedModel}from"./hostedModel";export const HOSTED_ENDPOINT="https://ai.buoy.gg/v1/chat/completions";export const HOSTED_MODEL="openai/gpt-6-luna";const S=10*6e4;async function c(n=false){const t=g.SessionManager;if(!t)return null;const a=t.getStatus();if(a.state==="active"&&(n||a.grace||(a.validUntil??0)-Date.now()<S)){await t.refresh(true)}return t.getAccessToken()}function d(n){const t=/^(https?:\/\/[^/]+)/.exec(n);return t?t[1]:n}export function hostedAskBuoy(n={}){const{endpoint:t=HOSTED_ENDPOINT,...a}=n;const u=`ask-buoy ${f.OS}`;return{...a,endpoint:t,hosted:true,protocol:"openai",model:HOSTED_MODEL,hostedModels:{luna:{endpoint:t,protocol:"openai",model:HOSTED_MODEL},claude:{endpoint:`${d(t)}/v1/messages`,protocol:"anthropic",model:h}},maxTokens:8192,headers:async o=>{const r=await c((o?.attempt??1)>1);return{...r?{[s.session]:r}:{},[s.ask]:o?.askId??"ask_unknown000",[s.round]:String(o?.round??1),[s.attempt]:String(o?.attempt??1),[s.protocol]:String(E),[s.client]:u}},readUsage:async()=>{const o=await c();if(!o)throw new Error("Sign in to see hosted credits.");const r=await fetch(`${d(t)}/v1/usage`,{headers:{[s.session]:o}});if(!r.ok)throw new Error(`usage HTTP ${r.status}`);const e=await r.json();return{creditsLeft:e.creditsLeft??0,grantCredits:e.grantCredits??0,spentCredits:e.spentCredits??0,resetAt:e.resetAt??null,...e.claude?{claude:{available:e.claude.available===true,reason:typeof e.claude.reason==="string"?e.claude.reason:null}}:{}}},reportProblem:async({requestId:o,code:r,note:e})=>{const p=await c();const i=await fetch(`${d(t)}/v1/report`,{method:"POST",headers:{"Content-Type":"application/json",...p?{[s.session]:p}:{}},body:JSON.stringify({request_id:o,code:r,note:e,client:u})});if(!i.ok)throw new Error(`report HTTP ${i.status}`);const l=await i.json();if(!l.report_id)throw new Error("report: no id");return{reportId:l.report_id}}}}
@@ -0,0 +1 @@
1
+ "use strict";export const HOSTED_CLAUDE_MODEL="claude-haiku-5-5";export function pickHostedModel(t,a,l){const e=t?.hostedModels;if(!t||!e?.claude||!e.luna)return t;const u=a==="claude"&&!l?e.claude:e.luna;const c=u===e.claude?e.luna:e.claude;return{...t,...u,hostedFallback:c}}
@@ -0,0 +1 @@
1
+ "use strict";import{describe as l,expect as t,it as o}from"vitest";import{HOSTED_CLAUDE_MODEL as d,pickHostedModel as a}from"./hostedModel";const e={endpoint:"https://ai.buoy.gg/v1/chat/completions",protocol:"openai",model:"openai/gpt-6-luna"};const s={endpoint:"https://ai.buoy.gg/v1/messages",protocol:"anthropic",model:d};const n={...e,hosted:true,hostedModels:{luna:e,claude:s}};l("pickHostedModel",()=>{o("answers on Claude with Luna as the backup",()=>{t(a(n,"claude",false)).toMatchObject({...s,hostedFallback:e})});o("starts on Luna while Claude's budget is out, keeping Claude as the backup",()=>{t(a(n,"claude",true)).toMatchObject({...e,hostedFallback:s})});o("answers on Luna when Luna is picked, with Claude as the backup",()=>{t(a(n,"luna",false)).toMatchObject({...e,hostedFallback:s})});o("leaves your own endpoint alone",()=>{const c={endpoint:"https://gw.acme.test/v1/messages",model:"m"};t(a(c,"claude",false)).toBe(c)})});
@@ -1 +1 @@
1
- "use strict";import{definePersistedSetting as a,devToolsStorageKeys as l}from"@buoy-gg/shared-ui";export const DEFAULT_SETTINGS={showThinking:false,bypassApprovals:false,readOnly:false,hostedNoticeSeen:false};const o=a(`${l.base}_ask_buoy_settings`,DEFAULT_SETTINGS,{sanitize:e=>({showThinking:e.showThinking===true,bypassApprovals:e.bypassApprovals===true,readOnly:e.readOnly===true,hostedNoticeSeen:e.hostedNoticeSeen===true})});let s=DEFAULT_SETTINGS;let n=false;const r=new Set;function i(){for(const e of r){try{e()}catch{}}}export const askBuoySettings={get:()=>s,subscribe(e){r.add(e);if(!n){n=true;void o.load().then(t=>{s=t;i()})}return()=>r.delete(e)},set(e,t){if(s[e]===t)return;s={...s,[e]:t};i();void o.save(s)},reset(){s=DEFAULT_SETTINGS;n=false;void o.clear()}};
1
+ "use strict";import{definePersistedSetting as r,devToolsStorageKeys as l}from"@buoy-gg/shared-ui";export const DEFAULT_SETTINGS={showThinking:false,bypassApprovals:false,readOnly:false,hostedNoticeSeen:false,claudeNoticeSeen:false};const o=r(`${l.base}_ask_buoy_settings`,DEFAULT_SETTINGS,{sanitize:e=>({showThinking:e.showThinking===true,bypassApprovals:e.bypassApprovals===true,readOnly:e.readOnly===true,hostedNoticeSeen:e.hostedNoticeSeen===true,claudeNoticeSeen:e.claudeNoticeSeen===true})});let t=DEFAULT_SETTINGS;let n=false;const i=new Set;function a(){for(const e of i){try{e()}catch{}}}export const askBuoySettings={get:()=>t,subscribe(e){i.add(e);if(!n){n=true;void o.load().then(s=>{t=s;a()})}return()=>i.delete(e)},set(e,s){if(t[e]===s)return;t={...t,[e]:s};a();void o.save(t)},reset(){t=DEFAULT_SETTINGS;n=false;void o.clear()}};
@@ -1,3 +1,3 @@
1
- "use strict";import{idFactory as p}from"@buoy-gg/agent-core";import{realNow as i}from"@buoy-gg/shared-ui";const l=p("m");const c=400;function y(t){if(!t||Object.keys(t).length===0)return void 0;try{const e=JSON.stringify(t);return e.length>c?`${e.slice(0,c)}\u2026`:e}catch{return void 0}}const k=p("q");let s={messages:[],busy:false,stopping:false,pending:null,trusted:[],notice:null,configured:false,answers:{},awaitingBlockId:null,actionState:{},queued:[],draft:null,usage:{input:0,output:0,requests:0},credits:null,meta:{}};let f=null;let m=null;const d=new Set;let h=null;let g=null;function n(t){s={...s,...t};for(const e of d){try{e()}catch{}}}export const askBuoyStore={getState:()=>s,subscribe(t){d.add(t);return()=>d.delete(t)},setLedger(t){h=t},getLedger(){return h},holdSession(t,e){g={session:t,signature:e}},getHeldSession(){return g},setReporter(t){f=t},getReporter(){return f},setUsageReader(t){m=t},getUsageReader(){return m},setNotice(t){n({notice:t})},setConfigured(t){if(t!==s.configured)n({configured:t})},setMeta(t){const e=Object.keys(t).every(a=>s.meta[a]===t[a]);if(!e)n({meta:{...s.meta,...t}})},addUser(t){n({messages:[...s.messages,{id:l(),role:"user",text:t,toolCalls:[],blocks:[]}],busy:true})},beginTurn(){n({busy:true})},enqueue(t){const e={id:k(),text:t};n({queued:[...s.queued,e]});return e},removeQueued(t){if(!s.queued.some(e=>e.id===t))return;n({queued:s.queued.filter(e=>e.id!==t)})},shiftQueued(){const[t,...e]=s.queued;if(!t)return void 0;n({queued:e});return t},takeQueuedToDraft(t){const e=s.queued.find(a=>a.id===t);if(!e)return;n({queued:s.queued.filter(a=>a.id!==t),draft:e.text})},requestStop(){if(!s.busy||s.stopping)return;n({stopping:true})},clearDraft(){if(s.draft===null)return;n({draft:null})},removeMessage(t){n({messages:s.messages.filter(e=>e.id!==t)})},recordAnswer(t){n({answers:{...s.answers,[t.blockId]:t},awaitingBlockId:s.awaitingBlockId===t.blockId?null:s.awaitingBlockId})},setActionState(t,e){const a={...s.actionState};if(e===null)delete a[t];else a[t]=e;n({actionState:a})},startAssistant(){const t=l();n({messages:[...s.messages,{id:t,role:"assistant",text:"",toolCalls:[],blocks:[],streaming:true,startedAt:i()}]});return t},applyEvent(t,e){if(e.type==="history-trimmed"){const r=s.messages.findIndex(b=>b.id===t);const o={id:l(),role:"assistant",system:true,text:`Older messages were trimmed from the agent's memory (${e.droppedRounds} exchange${e.droppedRounds===1?"":"s"}).`+(e.droppedPinned?` ${e.droppedPinned===1?"One of them made a change that is":`${e.droppedPinned} of them made changes that are`} still applied \u2014 the changes bar still lists ${e.droppedPinned===1?"it":"them"}, but the agent no longer remembers making ${e.droppedPinned===1?"it":"them"}.`:" It may not recall setup from earlier in this chat \u2014 the changes bar still lists what is applied."),toolCalls:[],blocks:[],endedAt:i()};const u=[...s.messages];u.splice(r<0?u.length:r,0,o);n({messages:u});return}const a=s.messages.map(r=>{if(r.id!==t)return r;switch(e.type){case"retrying":{const o=Math.max(1,Math.round(e.inMs/1e3));return{...r,waiting:e.reason==="throttled"?`Endpoint busy \u2014 retrying in ${o}s (${e.attempt} of ${e.maxAttempts})`:`Request too large \u2014 trimming older messages and retrying`}}case"text":return{...r,waiting:void 0,text:(r.breakBeforeNextText||e.breakBefore)&&r.text?`${r.text.trimEnd()}
1
+ "use strict";import{idFactory as g}from"@buoy-gg/agent-core";import{realNow as i}from"@buoy-gg/shared-ui";const l=g("m");const c=400;function y(t){if(!t||Object.keys(t).length===0)return void 0;try{const e=JSON.stringify(t);return e.length>c?`${e.slice(0,c)}\u2026`:e}catch{return void 0}}const k=g("q");let s={messages:[],busy:false,stopping:false,pending:null,trusted:[],notice:null,configured:false,answers:{},awaitingBlockId:null,actionState:{},queued:[],draft:null,usage:{input:0,output:0,requests:0},credits:null,claudeOut:false,meta:{}};let f=null;let m=null;const d=new Set;let h=null;let p=null;function n(t){s={...s,...t};for(const e of d){try{e()}catch{}}}export const askBuoyStore={getState:()=>s,subscribe(t){d.add(t);return()=>d.delete(t)},setLedger(t){h=t},getLedger(){return h},holdSession(t,e){p={session:t,signature:e}},getHeldSession(){return p},setReporter(t){f=t},getReporter(){return f},setUsageReader(t){m=t},getUsageReader(){return m},setNotice(t){n({notice:t})},setConfigured(t){if(t!==s.configured)n({configured:t})},setClaudeOut(t){if(s.claudeOut!==t)n({claudeOut:t})},setMeta(t){const e=Object.keys(t).every(a=>s.meta[a]===t[a]);if(!e)n({meta:{...s.meta,...t}})},addUser(t){n({messages:[...s.messages,{id:l(),role:"user",text:t,toolCalls:[],blocks:[]}],busy:true})},beginTurn(){n({busy:true})},enqueue(t){const e={id:k(),text:t};n({queued:[...s.queued,e]});return e},removeQueued(t){if(!s.queued.some(e=>e.id===t))return;n({queued:s.queued.filter(e=>e.id!==t)})},shiftQueued(){const[t,...e]=s.queued;if(!t)return void 0;n({queued:e});return t},takeQueuedToDraft(t){const e=s.queued.find(a=>a.id===t);if(!e)return;n({queued:s.queued.filter(a=>a.id!==t),draft:e.text})},requestStop(){if(!s.busy||s.stopping)return;n({stopping:true})},clearDraft(){if(s.draft===null)return;n({draft:null})},removeMessage(t){n({messages:s.messages.filter(e=>e.id!==t)})},recordAnswer(t){n({answers:{...s.answers,[t.blockId]:t},awaitingBlockId:s.awaitingBlockId===t.blockId?null:s.awaitingBlockId})},setActionState(t,e){const a={...s.actionState};if(e===null)delete a[t];else a[t]=e;n({actionState:a})},startAssistant(){const t=l();n({messages:[...s.messages,{id:t,role:"assistant",text:"",toolCalls:[],blocks:[],streaming:true,startedAt:i()}]});return t},applyEvent(t,e){if(e.type==="history-trimmed"){const r=s.messages.findIndex(b=>b.id===t);const u={id:l(),role:"assistant",system:true,text:`Older messages were trimmed from the agent's memory (${e.droppedRounds} exchange${e.droppedRounds===1?"":"s"}).`+(e.droppedPinned?` ${e.droppedPinned===1?"One of them made a change that is":`${e.droppedPinned} of them made changes that are`} still applied \u2014 the changes bar still lists ${e.droppedPinned===1?"it":"them"}, but the agent no longer remembers making ${e.droppedPinned===1?"it":"them"}.`:" It may not recall setup from earlier in this chat \u2014 the changes bar still lists what is applied."),toolCalls:[],blocks:[],endedAt:i()};const o=[...s.messages];o.splice(r<0?o.length:r,0,u);n({messages:o});return}const a=s.messages.map(r=>{if(r.id!==t)return r;switch(e.type){case"retrying":{const u=Math.max(1,Math.round(e.inMs/1e3));return{...r,waiting:e.reason==="throttled"?`Endpoint busy \u2014 retrying in ${u}s (${e.attempt} of ${e.maxAttempts})`:`Request too large \u2014 trimming older messages and retrying`}}case"text":return{...r,waiting:void 0,text:(r.breakBeforeNextText||e.breakBefore)&&r.text?`${r.text.trimEnd()}
2
2
 
3
- ${e.delta}`:r.text+e.delta,breakBeforeNextText:false};case"reasoning":return{...r,breakBeforeNextText:true,reasoning:[...r.reasoning??[],{text:e.text,afterCalls:r.toolCalls.length}]};case"tool-start":return{...r,waiting:void 0,toolCalls:[...r.toolCalls,{id:e.id,toolId:e.toolId,action:e.action,label:e.label,description:e.description,status:"running",params:y(e.params),effect:e.effect,startedAt:i()}]};case"tool-end":return{...r,breakBeforeNextText:true,toolCalls:r.toolCalls.map(o=>o.id===e.id?{...o,status:!e.ok&&e.summary==="refused"?"refused":!e.ok&&e.summary==="declined"?"declined":!e.ok&&e.summary==="stopped"?"stopped":e.ok?"ok":"failed",summary:e.summary,durationMs:e.durationMs,result:e.result}:o)};case"tool-verified":return{...r,toolCalls:r.toolCalls.map(o=>o.id===e.id?{...o,verification:e.verification}:o)};case"block":return{...r,breakBeforeNextText:true,blocks:[...r.blocks,{block:e.block,forToolCallId:e.forToolCallId}]};case"usage":return{...r,usage:{input:(r.usage?.input??0)+e.input,output:(r.usage?.output??0)+e.output,requests:(r.usage?.requests??0)+1}};case"error":return{...r,waiting:void 0,error:e.message,...e.hosted?{errorInfo:{code:e.hosted.code,requestId:e.hosted.request_id,action:e.hosted.action,resetAt:e.hosted.reset_at,helpUrl:e.hosted.help_url}}:{}};default:return r}});n({messages:a,...e.type==="awaiting-user"?{awaitingBlockId:e.blockId}:{},...e.type==="meter"&&e.frame.phase==="end"?{credits:{left:e.frame.credits_left??s.credits?.left??null,resetAt:e.frame.credits_reset??s.credits?.resetAt??null,pending:e.frame.pending===true}}:{},...e.type==="usage"?{usage:{input:s.usage.input+e.input,output:s.usage.output+e.output,requests:s.usage.requests+1}}:{}})},markStopped(t){n({messages:s.messages.map(e=>e.id===t?{...e,stopped:true}:e)})},finishAssistant(t){n({messages:s.messages.map(e=>e.id===t?{...e,streaming:false,endedAt:i()}:e),busy:false,stopping:false})},askApproval(t){return new Promise(e=>{n({pending:{...t,resolve:e}})})},answerApproval(t){const e=s.pending;const a=typeof t==="boolean"?t:t.approved;const r=typeof t!=="boolean"&&t.trust===true&&a;const o=e?`${e.toolId}.${e.action}`:"";n({pending:null,...r&&e&&!s.trusted.some(u=>u.key===o)?{trusted:[...s.trusted,{key:o,label:e.label}]}:{}});e?.resolve(t)},untrust(t){g?.session.untrust(t);n({trusted:s.trusted.filter(e=>e.key!==t)})},restorePending(t){if(s.pending||s.busy)return false;n({pending:{...t,restored:true,resolve:()=>{}}});return true},addNote(t){n({messages:[...s.messages,{id:l(),role:"assistant",text:t,toolCalls:[],blocks:[],endedAt:i()}]})},hydrate(t){if(s.messages.length>0||s.busy)return false;n({messages:t.messages.map(e=>({...e,restored:true})),answers:t.answers,usage:t.usage,queued:t.queued??[]});return true},reset(){s.pending?.resolve(false);n({messages:[],busy:false,stopping:false,pending:null,trusted:[],notice:null,answers:{},awaitingBlockId:null,actionState:{},queued:[],draft:null,usage:{input:0,output:0,requests:0}})}};export function turnPhaseOf(t){if(t.pending)return"awaitingApproval";if(t.busy)return t.stopping?"stopping":"running";if(t.awaitingBlockId)return"awaitingAnswer";return"idle"}export function queuePauseReason(t){const e=turnPhaseOf(t);if(e==="running"||e==="stopping")return null;if(e==="awaitingApproval")return"Paused \u2014 answer the approval above first.";if(e==="awaitingAnswer")return"Paused \u2014 answer the question above first.";const a=t.messages[t.messages.length-1];if(a?.role==="assistant"&&a.error)return"Paused \u2014 the last turn failed.";if(a?.role==="assistant"&&a.stopped)return"Paused \u2014 you stopped the last turn.";return null}
3
+ ${e.delta}`:r.text+e.delta,breakBeforeNextText:false};case"reasoning":return{...r,breakBeforeNextText:true,reasoning:[...r.reasoning??[],{text:e.text,afterCalls:r.toolCalls.length}]};case"tool-start":return{...r,waiting:void 0,toolCalls:[...r.toolCalls,{id:e.id,toolId:e.toolId,action:e.action,label:e.label,description:e.description,status:"running",params:y(e.params),effect:e.effect,startedAt:i()}]};case"tool-end":return{...r,breakBeforeNextText:true,toolCalls:r.toolCalls.map(u=>u.id===e.id?{...u,status:!e.ok&&e.summary==="refused"?"refused":!e.ok&&e.summary==="declined"?"declined":!e.ok&&e.summary==="stopped"?"stopped":e.ok?"ok":"failed",summary:e.summary,durationMs:e.durationMs,result:e.result}:u)};case"tool-verified":return{...r,toolCalls:r.toolCalls.map(u=>u.id===e.id?{...u,verification:e.verification}:u)};case"block":return{...r,breakBeforeNextText:true,blocks:[...r.blocks,{block:e.block,forToolCallId:e.forToolCallId}]};case"usage":return{...r,usage:{input:(r.usage?.input??0)+e.input,output:(r.usage?.output??0)+e.output,requests:(r.usage?.requests??0)+1}};case"error":return{...r,waiting:void 0,error:e.message,...e.hosted?{errorInfo:{code:e.hosted.code,requestId:e.hosted.request_id,action:e.hosted.action,resetAt:e.hosted.reset_at,helpUrl:e.hosted.help_url}}:{}};default:return r}});n({messages:a,...e.type==="awaiting-user"?{awaitingBlockId:e.blockId}:{},...e.type==="error"&&e.hosted?.code==="claude_paused"?{claudeOut:true}:{},...e.type==="meter"&&e.frame.phase==="end"?{credits:{left:e.frame.credits_left??s.credits?.left??null,resetAt:e.frame.credits_reset??s.credits?.resetAt??null,pending:e.frame.pending===true}}:{},...e.type==="usage"?{usage:{input:s.usage.input+e.input,output:s.usage.output+e.output,requests:s.usage.requests+1}}:{}})},markStopped(t){n({messages:s.messages.map(e=>e.id===t?{...e,stopped:true}:e)})},finishAssistant(t){n({messages:s.messages.map(e=>e.id===t?{...e,streaming:false,endedAt:i()}:e),busy:false,stopping:false})},askApproval(t){return new Promise(e=>{n({pending:{...t,resolve:e}})})},answerApproval(t){const e=s.pending;const a=typeof t==="boolean"?t:t.approved;const r=typeof t!=="boolean"&&t.trust===true&&a;const u=e?`${e.toolId}.${e.action}`:"";n({pending:null,...r&&e&&!s.trusted.some(o=>o.key===u)?{trusted:[...s.trusted,{key:u,label:e.label}]}:{}});e?.resolve(t)},untrust(t){p?.session.untrust(t);n({trusted:s.trusted.filter(e=>e.key!==t)})},restorePending(t){if(s.pending||s.busy)return false;n({pending:{...t,restored:true,resolve:()=>{}}});return true},addNote(t){n({messages:[...s.messages,{id:l(),role:"assistant",text:t,toolCalls:[],blocks:[],endedAt:i()}]})},hydrate(t){if(s.messages.length>0||s.busy)return false;n({messages:t.messages.map(e=>({...e,restored:true})),answers:t.answers,usage:t.usage,queued:t.queued??[]});return true},reset(){s.pending?.resolve(false);n({messages:[],busy:false,stopping:false,pending:null,trusted:[],notice:null,answers:{},awaitingBlockId:null,actionState:{},queued:[],draft:null,usage:{input:0,output:0,requests:0}})}};export function turnPhaseOf(t){if(t.pending)return"awaitingApproval";if(t.busy)return t.stopping?"stopping":"running";if(t.awaitingBlockId)return"awaitingAnswer";return"idle"}export function queuePauseReason(t){const e=turnPhaseOf(t);if(e==="running"||e==="stopping")return null;if(e==="awaitingApproval")return"Paused \u2014 answer the approval above first.";if(e==="awaitingAnswer")return"Paused \u2014 answer the question above first.";const a=t.messages[t.messages.length-1];if(a?.role==="assistant"&&a.error)return"Paused \u2014 the last turn failed.";if(a?.role==="assistant"&&a.stopped)return"Paused \u2014 you stopped the last turn.";return null}
@@ -0,0 +1 @@
1
+ "use strict";import{useSyncExternalStore as l}from"react";import{definePersistedSetting as a,devToolsStorageKeys as f,subscribeStorageWrites as S}from"@buoy-gg/shared-ui";export const DEFAULT_HOSTED_MODEL="claude";const n=f.askBuoy.model();const i=e=>e==="luna"?"luna":"claude";const u=a(n,DEFAULT_HOSTED_MODEL,{sanitize:i});let t=DEFAULT_HOSTED_MODEL;let c=false;const o=new Set;function g(){for(const e of o){try{e()}catch{}}}function r(e){if(e===t)return;t=e;g()}export const hostedModelSetting={get:()=>t,subscribe(e){o.add(e);if(!c){c=true;void u.load().then(r);S((d,s)=>{if(d!==n)return;try{r(i(s===null?DEFAULT_HOSTED_MODEL:JSON.parse(s)))}catch{}})}return()=>o.delete(e)},set(e){if(e===t)return;r(e);void u.save(e)}};export function useHostedModel(){return l(hostedModelSetting.subscribe,hostedModelSetting.get,hostedModelSetting.get)}
@@ -1 +1 @@
1
- "use strict";import{getUseIsPro as ne,hasAccountAccess as k,subscribeAccountAccess as ae}from"@buoy-gg/shared-ui";import{useCallback as c,useEffect as h,useMemo as ie,useRef as m,useState as ce,useSyncExternalStore as le}from"react";import{Platform as L}from"react-native";import{dispatchToolAction as K,listSnapshotToolIds as ue,listToolActions as de,readToolSnapshot as pe,waitForToolRegistry as Q}from"@buoy-gg/shared-ui";import{answerToText as V,CATALOG as P,createAskBuoySession as he,readTargetDigest as fe,runGatedAction as M,SNAPSHOT_ACTION as F}from"@buoy-gg/agent-core";import{askBuoyStore as t,queuePauseReason as me,turnPhaseOf as ye}from"./askBuoyStore";import{clearConversation as Ae,resetConversation as be}from"./resetConversation";import{buildPayload as ge,readTranscript as ke,writeTranscript as we}from"./transcriptPersistence";import{useAskBuoySettings as ve}from"./useAskBuoySettings";const w=typeof __DEV__!=="undefined"&&!__DEV__;function Se(s){for(const n of t.getState().messages){if(!n.restored)continue;for(const y of n.blocks){const d=y.block;if(d?.id!==s)continue;const p=d.question??d.label??d.title;return typeof p==="string"&&p.trim()?p.trim():void 0}}return void 0}function G(){try{const s=de();for(const n of ue()){if(!s[n])s[n]=[];if(!s[n].includes(F)){s[n]=[F,...s[n]]}}return s}catch{return{}}}const Te=600;export function useAskBuoyState(){return le(t.subscribe,t.getState,t.getState)}export function useAskBuoy(s){const n=useAskBuoyState();const y=ne()();const d=m(y);d.current=y;const p=c((...e)=>{if(!k()||!d.current)return Promise.reject(new Error("Ask Buoy requires Pro access."));return K(...e)},[]);const l=m(null);const $=m(false);const A=m(null);const x=m(s);x.current=s;const v=!!s?.endpoint&&!!s?.model;const S=s?.persistTranscript!==false;const{bypassApprovals:T,readOnly:N}=ve();const I=m(T);I.current=T;const R=m(N);R.current=N;const O=c(()=>R.current?{...s?.policy??{},readOnly:true}:s?.policy??{},[s]);const[H,W]=ce(0);h(()=>{let e=false;void Q().then(()=>{if(!e)W(r=>r+1)});return()=>{e=true}},[]);const b=ie(()=>G(),[H]);const z=Object.keys(b).sort().join(",");h(()=>{t.setConfigured(v);t.setMeta({model:s?.model,protocol:s?.protocol??(s?"anthropic":void 0),appName:s?.appName,platform:L.OS,build:w?"release":"development"});if(!v||!s){l.current=null;t.setNotice("Ask Buoy isn't set up in this app yet. A developer needs to point it at your team's AI endpoint.");return}const e=s.protocol??"anthropic";if(w&&s.apiKey){t.setNotice("This build carries a plaintext apiKey \u2014 that's dev-only and it ships to every install. Use the headers callback with your own gateway for distributed builds.")}else if(e==="anthropic"&&/^https?:\/\/api\.openai\.com\//i.test(s.endpoint)){t.setNotice('This endpoint is api.openai.com but the protocol is "anthropic". Set protocol: "openai" in the askBuoy config.')}else if(e==="openai"&&/^https?:\/\/api\.anthropic\.com\//i.test(s.endpoint)){t.setNotice('This endpoint is api.anthropic.com but the protocol is "openai". Remove protocol or set it to "anthropic".')}else{t.setNotice(null)}t.setReporter(s.reportProblem??null);t.setUsageReader(s.readUsage??null);const r=[s.endpoint,s.protocol??"anthropic",s.model].join("|");const o=t.getHeldSession();if(o&&o.signature===r){l.current=o.session;o.session.setAvailableActions(b);o.session.setApprovalBypass(I.current);o.session.setReadOnly(R.current);t.setLedger(o.session.ledger);return}const i=he({endpoint:s.endpoint,protocol:s.protocol??"anthropic",model:s.model,headers:s.headers,apiKey:s.apiKey,maxTokens:s.maxTokens,anthropicVersion:s.anthropicVersion,requestOverrides:s.requestOverrides,hosted:s.hosted===true,catalog:s.tools?.length?[...P,...s.tools]:void 0,dispatch:p,readSnapshot:pe,availableActions:b,policy:s.policy,context:s.context,isRelease:w,appName:s.appName,platform:L.OS,requestApproval:a=>t.askApproval(a)});l.current=i;i.setApprovalBypass(I.current);i.setReadOnly(R.current);t.holdSession(i,r);t.setLedger(i.ledger);let u=false;void Q().then(a=>{if(a&&!u)void i.prime()});if(S&&!$.current){$.current=true;void ke().then(a=>{if(!a)return;const oe=t.hydrate({messages:a.messages,answers:a.answers,usage:a.usage,queued:a.queued});if(!oe)return;l.current?.restore(a.history);if(a.ledger?.length)l.current?.ledger.restore(a.ledger);if(a.pending)t.restorePending(a.pending)})}return()=>{u=true}},[v,s,b,z,S]);h(()=>{l.current?.setApprovalBypass(T)},[T]);h(()=>{l.current?.setReadOnly(N)},[N]);const q=n.messages;const _=n.answers;const C=n.usage;const D=n.busy;const E=n.pending;const U=n.queued;h(()=>{if(!S)return;if(q.length===0)return;const e=()=>{void we(ge({messages:q,history:l.current?.messages??[],answers:_,usage:C,pending:E,ledger:t.getLedger()?.list(),queued:U}))};if(!D||E){e();return}const r=setTimeout(e,Te);return()=>clearTimeout(r)},[S,D,q,_,C,E,U]);const B=c(e=>{if(!k()||!d.current)return;const r=l.current;if(!r)return;t.beginTurn();const o=t.startAssistant();const i=new AbortController;A.current=i;r.setAvailableActions(G());const u=x.current?.onEvent;void(async()=>{try{for await(const a of r.send(e,i.signal)){t.applyEvent(o,a);if(u){try{u(a)}catch{}}}}catch(a){if(!i.signal.aborted){t.applyEvent(o,{type:"error",message:a instanceof Error?a.message:"Something went wrong talking to your AI endpoint."})}}finally{if(i.signal.aborted)t.markStopped(o);t.finishAssistant(o);A.current=null}})()},[]);const f=c(e=>{if(!k()||!d.current)return;const r=e.trim();if(!l.current||!r)return;if(t.getState().busy){t.enqueue(r);return}const o=t.getState().awaitingBlockId;if(o)t.recordAnswer({blockId:o,kind:"skipped"});t.addUser(r);B(r)},[B]);const J=c(()=>{const{messages:e,busy:r}=t.getState();if(r||!l.current)return;const o=[...e].reverse().find(i=>i.role==="user");if(!o)return;B(o.text)},[B]);const g=c(()=>{t.requestStop();if(t.getState().pending)t.answerApproval(false);A.current?.abort();A.current=null},[]);h(()=>{const e=ae(()=>{if(!k())g()});return()=>{e();g()}},[g]);h(()=>{if(!y)g()},[y,g]);h(()=>{if(n.queued.length===0)return;if(ye(n)!=="idle")return;if(me(n)!==null)return;const e=t.shiftQueued();if(e)f(e.text)},[n,f]);const X=c(e=>t.takeQueuedToDraft(e),[]);const Y=c(e=>t.removeQueued(e),[]);const Z=c(()=>{A.current?.abort();A.current=null;Ae()},[]);const ee=c(()=>be({dispatch:K,undoFirst:true}),[]);const j=c(async e=>{const r=t.getLedger();if(!r){t.setNotice(`${e.label} couldn't run \u2014 no active Ask Buoy session.`);return}const o=s?.tools?.length?[...P,...s.tools]:void 0;if(e.targetDigest){const u=await fe({toolId:e.toolId,action:e.action,params:e.params??{},dispatch:p,catalog:o});if(u!==void 0&&u!==e.targetDigest){t.addNote(`\u201C${e.label}\u201D wasn't applied \u2014 what it would change has moved on since the app restarted. Nothing was written. Ask again if you still want it.`);return}}const i=await M({toolId:e.toolId,action:e.action,params:e.params??{},dispatch:p,ledger:r,policy:O(),isRelease:w,catalog:o});if(i.ok){t.addNote(`Applied \u201C${e.label}\u201D, which was still waiting when the app restarted. Whatever else that answer was going to do is gone \u2014 ask again if there was more to it.`)}else{t.setNotice(`${e.label} failed \u2014 ${i.error}`)}},[s,O]);const te=c(e=>{const r=t.getState().pending;const o=typeof e==="boolean"?e:e.approved;t.answerApproval(e);if(!r?.restored)return;if(!o){t.addNote(`Skipped \u201C${r.label}\u201D. Nothing was changed.`);return}void j(r)},[j]);const se=c(e=>{t.recordAnswer(e);const r=Se(e.blockId);f(r?`${r} \u2014 ${V(e)}`:V(e))},[f]);const re=c(async(e,r)=>{if(!k()||!d.current){t.setNotice("Ask Buoy requires Pro access.");return}if(e.send){f(e.send);return}if(!e.dispatch)return;const o=`${r}:${e.label}`;const i=t.getLedger();if(!i){t.setNotice(`${e.label} failed \u2014 no active Ask Buoy session.`);return}t.setActionState(o,"running");const u=await M({toolId:e.dispatch.toolId,action:e.dispatch.action,params:e.dispatch.params??{},dispatch:p,ledger:i,policy:O(),isRelease:w,catalog:s?.tools?.length?[...P,...s.tools]:void 0});if(u.ok){t.setActionState(o,"done")}else{t.setActionState(o,"failed");t.setNotice(`${e.label} failed \u2014 ${u.error}`)}},[f,s,O]);return{state:n,send:f,stop:g,reset:Z,undoThenReset:ee,approve:te,retry:J,answer:se,runAction:re,editQueued:X,removeQueued:Y,configured:v,toolCount:Object.keys(b).length,availableToolIds:Object.keys(b)}}
1
+ "use strict";import{getUseIsPro as fe,hasAccountAccess as v,subscribeAccountAccess as he}from"@buoy-gg/shared-ui";import{useCallback as l,useEffect as p,useMemo as W,useRef as f,useState as ge,useSyncExternalStore as me}from"react";import{Platform as z}from"react-native";import{dispatchToolAction as J,listSnapshotToolIds as ye,listToolActions as be,readToolSnapshot as Ae,waitForToolRegistry as X}from"@buoy-gg/shared-ui";import{answerToText as Y,CATALOG as D,createAskBuoySession as ke,readTargetDigest as we,runGatedAction as Z,SNAPSHOT_ACTION as ee}from"@buoy-gg/agent-core";import{askBuoyStore as t,queuePauseReason as ve,turnPhaseOf as Se}from"./askBuoyStore";import{clearConversation as Te,resetConversation as Ne}from"./resetConversation";import{buildPayload as Oe,readTranscript as Re,writeTranscript as Be}from"./transcriptPersistence";import{useAskBuoySettings as Ie}from"./useAskBuoySettings";import{HOSTED_CLAUDE_MODEL as Ee,pickHostedModel as qe}from"../hostedModel";import{useHostedModel as Ce}from"./hostedModelSetting";const S=typeof __DEV__!=="undefined"&&!__DEV__;function Pe(c){for(const n of t.getState().messages){if(!n.restored)continue;for(const y of n.blocks){const h=y.block;if(h?.id!==c)continue;const g=h.question??h.label??h.title;return typeof g==="string"&&g.trim()?g.trim():void 0}}return void 0}function te(){try{const c=be();for(const n of ye()){if(!c[n])c[n]=[];if(!c[n].includes(ee)){c[n]=[ee,...c[n]]}}return c}catch{return{}}}const _e=600;export function useAskBuoyState(){return me(t.subscribe,t.getState,t.getState)}export function useAskBuoy(c){const n=useAskBuoyState();const y=Ce();const h=W(()=>qe(c,y,n.claudeOut),[c,y,n.claudeOut]);const g=f(h);if(n.messages.length===0||!g.current)g.current=h;const o=g.current;const L=f(c);L.current=c;const M=!!c?.hostedModels?.claude;const U=n.messages.length===0;p(()=>{const e=L.current?.readUsage;if(!M||y!=="claude"||!U||!e)return;let s=true;e().then(r=>{if(s&&r.claude)t.setClaudeOut(!r.claude.available)}).catch(()=>{});return()=>{s=false}},[M,y,U]);const T=fe()();const w=f(T);w.current=T;const N=l((...e)=>{if(!v()||!w.current)return Promise.reject(new Error("Ask Buoy requires Pro access."));return J(...e)},[]);const u=f(null);const j=f(false);const b=f(null);const F=f(o);F.current=o;const O=!!o?.endpoint&&!!o?.model;const R=o?.persistTranscript!==false;const{bypassApprovals:B,readOnly:I}=Ie();const _=f(B);_.current=B;const E=f(I);E.current=I;const q=l(()=>E.current?{...o?.policy??{},readOnly:true}:o?.policy??{},[o]);const[oe,se]=ge(0);p(()=>{let e=false;void X().then(()=>{if(!e)se(s=>s+1)});return()=>{e=true}},[]);const A=W(()=>te(),[oe]);const re=Object.keys(A).sort().join(",");p(()=>{t.setConfigured(O);t.setMeta({model:o?.model,protocol:o?.protocol??(o?"anthropic":void 0),appName:o?.appName,platform:z.OS,build:S?"release":"development"});if(!O||!o){u.current=null;t.setNotice("Ask Buoy isn't set up in this app yet. A developer needs to point it at your team's AI endpoint.");return}const e=o.protocol??"anthropic";if(S&&o.apiKey){t.setNotice("This build carries a plaintext apiKey \u2014 that's dev-only and it ships to every install. Use the headers callback with your own gateway for distributed builds.")}else if(e==="anthropic"&&/^https?:\/\/api\.openai\.com\//i.test(o.endpoint)){t.setNotice('This endpoint is api.openai.com but the protocol is "anthropic". Set protocol: "openai" in the askBuoy config.')}else if(e==="openai"&&/^https?:\/\/api\.anthropic\.com\//i.test(o.endpoint)){t.setNotice('This endpoint is api.anthropic.com but the protocol is "openai". Remove protocol or set it to "anthropic".')}else{t.setNotice(null)}t.setReporter(o.reportProblem??null);t.setUsageReader(o.readUsage??null);const s=[o.endpoint,o.protocol??"anthropic",o.model].join("|");const r=t.getHeldSession();if(r&&r.signature===s){u.current=r.session;r.session.setAvailableActions(A);r.session.setApprovalBypass(_.current);r.session.setReadOnly(E.current);t.setLedger(r.session.ledger);return}const a=ke({endpoint:o.endpoint,protocol:o.protocol??"anthropic",model:o.model,headers:o.headers,apiKey:o.apiKey,maxTokens:o.maxTokens,anthropicVersion:o.anthropicVersion,requestOverrides:o.requestOverrides,hosted:o.hosted===true,...o.hostedFallback?{fallback:o.hostedFallback,onModelSwitch:i=>{const P=(i==="fallback"?o.hostedFallback?.model:o.model)===Ee;t.setMeta({model:i==="fallback"?o.hostedFallback?.model:o.model});if(i==="fallback"){if(!P)t.setClaudeOut(true);t.setNotice(P?"Luna is down right now, so Claude is answering.":"Claude is busy right now, so Luna is answering.")}else{t.setNotice(null)}}}:{},catalog:o.tools?.length?[...D,...o.tools]:void 0,dispatch:N,readSnapshot:Ae,availableActions:A,policy:o.policy,context:o.context,isRelease:S,appName:o.appName,platform:z.OS,requestApproval:i=>t.askApproval(i)});u.current=a;a.setApprovalBypass(_.current);a.setReadOnly(E.current);t.holdSession(a,s);t.setLedger(a.ledger);let d=false;void X().then(i=>{if(i&&!d)void a.prime()});if(R&&!j.current){j.current=true;void Re().then(i=>{if(!i)return;const P=t.hydrate({messages:i.messages,answers:i.answers,usage:i.usage,queued:i.queued});if(!P)return;u.current?.restore(i.history);if(i.ledger?.length)u.current?.ledger.restore(i.ledger);if(i.pending)t.restorePending(i.pending)})}return()=>{d=true}},[O,o,A,re,R]);p(()=>{u.current?.setApprovalBypass(B)},[B]);p(()=>{u.current?.setReadOnly(I)},[I]);const $=n.messages;const H=n.answers;const K=n.usage;const Q=n.busy;const x=n.pending;const V=n.queued;p(()=>{if(!R)return;if($.length===0)return;const e=()=>{void Be(Oe({messages:$,history:u.current?.messages??[],answers:H,usage:K,pending:x,ledger:t.getLedger()?.list(),queued:V}))};if(!Q||x){e();return}const s=setTimeout(e,_e);return()=>clearTimeout(s)},[R,Q,$,H,K,x,V]);const C=l(e=>{if(!v()||!w.current)return;const s=u.current;if(!s)return;t.beginTurn();const r=t.startAssistant();const a=new AbortController;b.current=a;s.setAvailableActions(te());const d=F.current?.onEvent;void(async()=>{try{for await(const i of s.send(e,a.signal)){t.applyEvent(r,i);if(d){try{d(i)}catch{}}}}catch(i){if(!a.signal.aborted){t.applyEvent(r,{type:"error",message:i instanceof Error?i.message:"Something went wrong talking to your AI endpoint."})}}finally{if(a.signal.aborted)t.markStopped(r);t.finishAssistant(r);b.current=null}})()},[]);const m=l(e=>{if(!v()||!w.current)return;const s=e.trim();if(!u.current||!s)return;if(t.getState().busy){t.enqueue(s);return}const r=t.getState().awaitingBlockId;if(r)t.recordAnswer({blockId:r,kind:"skipped"});t.addUser(s);C(s)},[C]);const ne=l(()=>{const{messages:e,busy:s}=t.getState();if(s||!u.current)return;const r=[...e].reverse().find(a=>a.role==="user");if(!r)return;C(r.text)},[C]);const k=l(()=>{t.requestStop();if(t.getState().pending)t.answerApproval(false);b.current?.abort();b.current=null},[]);p(()=>{const e=he(()=>{if(!v())k()});return()=>{e();k()}},[k]);p(()=>{if(!T)k()},[T,k]);p(()=>{if(n.queued.length===0)return;if(Se(n)!=="idle")return;if(ve(n)!==null)return;const e=t.shiftQueued();if(e)m(e.text)},[n,m]);const ie=l(e=>t.takeQueuedToDraft(e),[]);const ae=l(e=>t.removeQueued(e),[]);const ce=l(()=>{b.current?.abort();b.current=null;Te()},[]);const le=l(()=>Ne({dispatch:J,undoFirst:true}),[]);const G=l(async e=>{const s=t.getLedger();if(!s){t.setNotice(`${e.label} couldn't run \u2014 no active Ask Buoy session.`);return}const r=o?.tools?.length?[...D,...o.tools]:void 0;if(e.targetDigest){const d=await we({toolId:e.toolId,action:e.action,params:e.params??{},dispatch:N,catalog:r});if(d!==void 0&&d!==e.targetDigest){t.addNote(`\u201C${e.label}\u201D wasn't applied \u2014 what it would change has moved on since the app restarted. Nothing was written. Ask again if you still want it.`);return}}const a=await Z({toolId:e.toolId,action:e.action,params:e.params??{},dispatch:N,ledger:s,policy:q(),isRelease:S,catalog:r});if(a.ok){t.addNote(`Applied \u201C${e.label}\u201D, which was still waiting when the app restarted. Whatever else that answer was going to do is gone \u2014 ask again if there was more to it.`)}else{t.setNotice(`${e.label} failed \u2014 ${a.error}`)}},[o,q]);const ue=l(e=>{const s=t.getState().pending;const r=typeof e==="boolean"?e:e.approved;t.answerApproval(e);if(!s?.restored)return;if(!r){t.addNote(`Skipped \u201C${s.label}\u201D. Nothing was changed.`);return}void G(s)},[G]);const de=l(e=>{t.recordAnswer(e);const s=Pe(e.blockId);m(s?`${s} \u2014 ${Y(e)}`:Y(e))},[m]);const pe=l(async(e,s)=>{if(!v()||!w.current){t.setNotice("Ask Buoy requires Pro access.");return}if(e.send){m(e.send);return}if(!e.dispatch)return;const r=`${s}:${e.label}`;const a=t.getLedger();if(!a){t.setNotice(`${e.label} failed \u2014 no active Ask Buoy session.`);return}t.setActionState(r,"running");const d=await Z({toolId:e.dispatch.toolId,action:e.dispatch.action,params:e.dispatch.params??{},dispatch:N,ledger:a,policy:q(),isRelease:S,catalog:o?.tools?.length?[...D,...o.tools]:void 0});if(d.ok){t.setActionState(r,"done")}else{t.setActionState(r,"failed");t.setNotice(`${e.label} failed \u2014 ${d.error}`)}},[m,o,q]);return{state:n,send:m,stop:k,reset:ce,undoThenReset:le,approve:ue,retry:ne,answer:de,runAction:pe,editQueued:ie,removeQueued:ae,configured:O,toolCount:Object.keys(A).length,availableToolIds:Object.keys(A)}}
@@ -1,8 +1,9 @@
1
1
  import type { AskBuoyConfig } from "./types";
2
+ export { HOSTED_CLAUDE_MODEL, pickHostedModel } from "./hostedModel";
2
3
  export declare const HOSTED_ENDPOINT = "https://ai.buoy.gg/v1/chat/completions";
3
4
  /** The model the gateway serves. The gateway pins it; this is only a label. */
4
5
  export declare const HOSTED_MODEL = "openai/gpt-6-luna";
5
- export type HostedAskBuoyOptions = Omit<AskBuoyConfig, "endpoint" | "protocol" | "model" | "headers" | "apiKey" | "requestOverrides" | "anthropicVersion" | "reportProblem" | "readUsage"> & {
6
+ export type HostedAskBuoyOptions = Omit<AskBuoyConfig, "endpoint" | "protocol" | "model" | "headers" | "apiKey" | "requestOverrides" | "anthropicVersion" | "reportProblem" | "readUsage" | "hostedModels"> & {
6
7
  /** For local testing against `wrangler dev`. Defaults to ai.buoy.gg. */
7
8
  endpoint?: string;
8
9
  };
@@ -0,0 +1,10 @@
1
+ import type { AskBuoyConfig, HostedModelId } from "./types";
2
+ /** The Claude the gateway serves on `/v1/messages`. The gateway pins it. */
3
+ export declare const HOSTED_CLAUDE_MODEL = "claude-haiku-5-5";
4
+ /**
5
+ * The route a new chat uses, and its backup. The person's pick answers first
6
+ * and the other model takes over when it is down (agent-core's fallback
7
+ * provider). While Claude's prepaid budget is out, Luna answers first.
8
+ */
9
+ export declare function pickHostedModel(config: AskBuoyConfig | undefined, choice: HostedModelId, claudeOut: boolean): AskBuoyConfig | undefined;
10
+ //# sourceMappingURL=hostedModel.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hostedModel.test.d.ts.map
@@ -38,6 +38,12 @@ export interface AskBuoySettings {
38
38
  * OpenRouter. Hosted asks wait until this is true.
39
39
  */
40
40
  hostedNoticeSeen: boolean;
41
+ /**
42
+ * The person read the newer hosted notice, which names both models: Claude
43
+ * (Anthropic) first and Luna (OpenAI, through OpenRouter) as its backup.
44
+ * This one gates hosted asks now; `hostedNoticeSeen` was the Luna-only one.
45
+ */
46
+ claudeNoticeSeen: boolean;
41
47
  }
42
48
  export declare const DEFAULT_SETTINGS: AskBuoySettings;
43
49
  export declare const askBuoySettings: {
@@ -13,6 +13,11 @@ export interface HostedUsage {
13
13
  grantCredits: number;
14
14
  spentCredits: number;
15
15
  resetAt: string | null;
16
+ /** Whether a new Claude chat would start now. Absent from gateways without Claude. */
17
+ claude?: {
18
+ available: boolean;
19
+ reason: string | null;
20
+ };
16
21
  }
17
22
  /** Reads hosted credits. Set by `hostedAskBuoy()`; absent for your own endpoint. */
18
23
  export type ReadUsage = () => Promise<HostedUsage>;
@@ -248,6 +253,12 @@ export interface AskBuoyState {
248
253
  resetAt: string | null;
249
254
  pending: boolean;
250
255
  } | null;
256
+ /**
257
+ * Hosted only: Claude's prepaid budget is out for now, so new chats use
258
+ * Luna. Set by a `claude_paused` answer, cleared when the gateway's usage
259
+ * read says Claude is open again.
260
+ */
261
+ claudeOut: boolean;
251
262
  /**
252
263
  * Which model, app and build this conversation happened against.
253
264
  *
@@ -283,6 +294,7 @@ export declare const askBuoyStore: {
283
294
  getUsageReader(): ReadUsage | null;
284
295
  setNotice(notice: string | null): void;
285
296
  setConfigured(configured: boolean): void;
297
+ setClaudeOut(next: boolean): void;
286
298
  setMeta(meta: SessionMeta): void;
287
299
  addUser(text: string): void;
288
300
  /** Mark a turn as running without adding a bubble — the retry path. */