@buoy-gg/ask-buoy 7.0.78 → 7.0.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/blocks/ExtraBlocks.js +1 -1
- package/lib/commonjs/blocks/choiceLogic.js +1 -0
- package/lib/commonjs/blocks/choiceLogic.test.js +1 -0
- package/lib/commonjs/components/AskBuoyModal.js +1 -1
- package/lib/commonjs/components/EmptyState.js +1 -1
- package/lib/commonjs/components/sheetText.test.js +1 -0
- package/lib/commonjs/store/askBuoyStore.js +2 -2
- package/lib/commonjs/store/transcriptPersistence.js +1 -1
- package/lib/commonjs/store/transcriptPersistence.test.js +1 -1
- package/lib/commonjs/store/useAskBuoy.js +1 -1
- package/lib/module/blocks/ExtraBlocks.js +1 -1
- package/lib/module/blocks/choiceLogic.js +1 -0
- package/lib/module/blocks/choiceLogic.test.js +1 -0
- package/lib/module/components/AskBuoyModal.js +1 -1
- package/lib/module/components/EmptyState.js +1 -1
- package/lib/module/components/sheetText.test.js +1 -0
- package/lib/module/store/askBuoyStore.js +1 -1
- package/lib/module/store/transcriptPersistence.js +1 -1
- package/lib/module/store/transcriptPersistence.test.js +1 -1
- package/lib/module/store/useAskBuoy.js +1 -1
- package/lib/typescript/blocks/choiceLogic.d.ts +41 -0
- package/lib/typescript/blocks/choiceLogic.test.d.ts +2 -0
- package/lib/typescript/components/sheetText.test.d.ts +2 -0
- package/lib/typescript/store/askBuoyStore.d.ts +6 -0
- package/lib/typescript/store/transcriptPersistence.d.ts +14 -0
- package/lib/typescript/sync/wire.d.ts +8 -6
- package/lib/web/index.mjs +11 -11
- package/package.json +7 -7
- package/lib/commonjs/blocks/AskBlocks.test.js +0 -1
- package/lib/commonjs/components/Composer.test.js +0 -1
- package/lib/commonjs/components/EmptyState.test.js +0 -1
- package/lib/module/blocks/AskBlocks.test.js +0 -1
- package/lib/module/components/Composer.test.js +0 -1
- package/lib/module/components/EmptyState.test.js +0 -1
- package/lib/typescript/blocks/AskBlocks.test.d.ts +0 -2
- package/lib/typescript/components/Composer.test.d.ts +0 -2
- package/lib/typescript/components/EmptyState.test.d.ts +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{monoFont as R}from"@buoy-gg/shared-ui";import{Platform as H}from"react-native";import{useEffect as U,useRef as W,useState as w}from"react";import{Animated as T,Image as $,Pressable as f,ScrollView as _,StyleSheet as I,Switch as K,Text as l,TextInput as P,View as s}from"react-native";import{AlertTriangle as J,Check as V,ChevronDown as L,ChevronRight as B,night as o,NightSegmented as G,RefreshCw as X,Search as Q,X as Y}from"@buoy-gg/shared-ui";import{useBlockHandlers as S}from"./context";import{ActionRow as D,blockStyles as b,Card as m,More as Z,toneColor as O,usePaged as k}from"./primitives";import{jsx as n,jsxs as a,Fragment as v}from"react/jsx-runtime";const E=e=>typeof e==="string"?e:e===void 0?"\u2014":JSON.stringify(e);export function Suggestions({block:e}){const r=S();return n(s,{style:t.chips,testID:`buoy-block-suggestions-${e.id}`,children:e.chips.slice(0,4).map(i=>n(f,{disabled:!!r.busy,onPress:()=>void r.runAction({label:i.label,send:i.send??i.label},e.id),style:({pressed:d})=>[t.chip,d&&t.pressed],accessibilityRole:"button",children:n(l,{style:t.chipText,children:i.label})},i.label))})}export function MultiSelect({block:e}){const r=S();const i=r.answered[e.id];const d=!!i;const[c,p]=w([]);const u=i?.kind==="multi"?i.optionIds:c;const y=g=>p(h=>h.includes(g)?h.filter(x=>x!==g):[...h,g]);const C=c.length>=(e.min??1)&&(!e.max||c.length<=e.max);return a(m,{testID:`buoy-block-multi-${e.id}`,tone:d?void 0:"info",children:[n(l,{style:t.question,children:e.question}),n(s,{style:t.optionsBox,children:e.options.map((g,h)=>{const x=u.includes(g.id);return a(f,{disabled:d,onPress:()=>y(g.id),style:[t.option,h>0&&b.rowDivider,d&&!x&&t.dim],accessibilityRole:"checkbox",accessibilityState:{checked:x},children:[n(s,{style:[t.checkbox,x&&t.checkboxOn],children:x?n(V,{size:11,color:o.color.onAccent}):null}),a(s,{style:t.flex,children:[n(l,{style:t.optionLabel,children:g.label}),g.detail?n(l,{style:t.optionDetail,children:g.detail}):null]})]},g.id)})}),d?null:n(s,{style:t.rowEnd,children:n(f,{testID:"buoy-block-multi-submit",disabled:!C,onPress:()=>r.answer({blockId:e.id,kind:"multi",optionIds:c,labels:e.options.filter(g=>c.includes(g.id)).map(g=>g.label)}),style:[t.button,t.buttonPrimary,!C&&t.disabled],children:n(l,{style:t.buttonTextPrimary,children:e.submitLabel??`Use ${c.length||""}`.trim()})})})]})}const ee=8;export function Select({block:e}){const r=S();const i=r.answered[e.id];const[d,c]=w("");const[p,u]=w(false);const y=i?.kind==="select"?i.label:null;const C=e.options.filter(h=>!d||h.label.toLowerCase().includes(d.toLowerCase())).slice(0,8);const g=e.searchable!==false&&e.options.length>=ee;return a(m,{testID:`buoy-block-select-${e.id}`,tone:i?void 0:"info",children:[n(l,{style:t.question,children:e.label}),y?n(l,{style:t.decided,children:y}):a(s,{style:t.selectBox,children:[a(f,{onPress:()=>u(h=>!h),style:t.selectField,accessibilityRole:"button",children:[n(l,{style:t.selectPlaceholder,children:p?"Choose\u2026":`${e.options.length} options`}),n(L,{size:14,color:o.color.textSecondary})]}),p&&a(v,{children:[g&&a(s,{style:t.search,children:[n(Q,{size:12,color:o.color.textSecondary}),n(P,{style:t.searchInput,value:d,onChangeText:c,placeholder:"Search",placeholderTextColor:o.color.placeholder,autoCapitalize:"none",autoCorrect:false})]}),C.map((h,x)=>a(f,{onPress:()=>r.answer({blockId:e.id,kind:"select",optionId:h.id,label:h.label}),style:[t.option,x>0&&b.rowDivider],accessibilityRole:"button",children:[a(s,{style:t.flex,children:[n(l,{style:t.optionLabel,children:h.label}),h.detail?n(l,{style:t.optionDetail,children:h.detail}):null]}),n(B,{size:12,color:o.color.textTertiary})]},h.id)),e.options.length>C.length?a(l,{style:t.hint,children:[e.options.length-C.length," more \u2014 keep typing"]}):null]})]})]})}function j({min:e,max:r,step:i=1,value:d,unit:c,onChange:p,disabled:u}){const y=W(0);const C=r>e?(d-e)/(r-e)*100:0;const g=z=>Math.min(r,Math.max(e,+(Math.round((z-e)/i)*i+e).toFixed(6)));const h=z=>!u&&p(g(d+z*i));const x=z=>{if(u||y.current<=0)return;const N=Math.min(1,Math.max(0,z.nativeEvent.locationX/y.current));p(g(e+N*(r-e)))};const q=z=>{y.current=z.nativeEvent.layout.width};return a(s,{style:t.sliderRow,children:[n(f,{disabled:u,onPress:()=>h(-1),style:t.stepBtn,accessibilityRole:"button",accessibilityLabel:"Decrease",children:n(l,{style:t.stepText,children:"\u2212"})}),n(f,{style:t.trackHit,onLayout:q,onPress:x,disabled:u,accessibilityRole:"adjustable",accessibilityValue:{min:e,max:r,now:d},accessibilityActions:[{name:"increment"},{name:"decrement"}],onAccessibilityAction:z=>h(z.nativeEvent.actionName==="increment"?1:-1),children:a(s,{style:t.track,pointerEvents:"none",children:[n(s,{style:[t.fill,{width:`${C}%`}]}),n(s,{style:[t.thumb,{left:`${C}%`}]})]})}),n(f,{disabled:u,onPress:()=>h(1),style:t.stepBtn,accessibilityRole:"button",accessibilityLabel:"Increase",children:n(l,{style:t.stepText,children:"+"})}),a(l,{style:t.sliderValue,children:[d,c?` ${c}`:""]})]})}export function Slider({block:e}){const r=S();const i=r.answered[e.id];const[d,c]=w(e.value??e.min);return a(m,{testID:`buoy-block-slider-${e.id}`,tone:i?void 0:"info",children:[n(l,{style:t.question,children:e.label}),n(s,{style:t.pad,children:n(j,{min:e.min,max:e.max,step:e.step,value:i?.kind==="slider"?i.value:d,unit:e.unit,onChange:c,disabled:!!i})}),i?null:n(s,{style:t.rowEnd,children:n(f,{onPress:()=>r.answer({blockId:e.id,kind:"slider",value:d,unit:e.unit}),style:[t.button,t.buttonPrimary],children:a(l,{style:t.buttonTextPrimary,children:["Use ",d,e.unit?` ${e.unit}`:""]})})})]})}export function DateTime({block:e}){const r=S();const i=r.answered[e.id];const[d,c]=w("");const p=e.presets??[{label:"Now",value:new Date().toISOString()},{label:"Tomorrow",value:new Date(Date.now()+864e5).toISOString()},{label:"+7 days",value:new Date(Date.now()+7*864e5).toISOString()},{label:"+30 days",value:new Date(Date.now()+30*864e5).toISOString()}];return a(m,{testID:`buoy-block-datetime-${e.id}`,tone:i?void 0:"info",children:[n(l,{style:t.question,children:e.label}),i?n(l,{style:t.decided,children:i.kind==="dateTime"?i.label??i.value:"(skipped)"}):a(v,{children:[n(s,{style:[t.chips,t.pad],children:p.map(u=>n(f,{onPress:()=>r.answer({blockId:e.id,kind:"dateTime",value:u.value,label:u.label}),style:t.chip,children:n(l,{style:t.chipText,children:u.label})},u.label))}),a(s,{style:[t.inputRow,t.pad],children:[n(P,{style:t.input,value:d,onChangeText:c,placeholder:e.mode==="time"?"14:30":"2026-09-15",placeholderTextColor:o.color.placeholder,keyboardType:"numbers-and-punctuation"}),n(f,{disabled:!d.trim(),onPress:()=>r.answer({blockId:e.id,kind:"dateTime",value:d.trim()}),style:[t.button,t.buttonPrimary,!d.trim()&&t.disabled],children:n(l,{style:t.buttonTextPrimary,children:"Use"})})]})]})]})}function A({f:e,value:r,onChange:i,disabled:d}){if(e.type==="toggle"){return a(s,{style:t.fieldRow,children:[n(l,{style:t.fieldLabel,children:e.label}),n(K,{value:!!r,onValueChange:i,disabled:d,trackColor:{true:o.color.accent,false:o.color.fillTertiary},thumbColor:o.color.knob})]})}if(e.type==="select"){return a(s,{style:t.fieldCol,children:[n(l,{style:t.fieldLabel,children:e.label}),n(s,{style:t.chips,children:e.options.map(c=>n(f,{disabled:d,onPress:()=>i(c.id),style:[t.chip,r===c.id&&t.chipOn],children:n(l,{style:[t.chipText,r===c.id&&t.chipTextOn],children:c.label})},c.id))})]})}if(e.type==="slider"){return a(s,{style:t.fieldCol,children:[n(l,{style:t.fieldLabel,children:e.label}),n(j,{min:e.min,max:e.max,step:e.step,unit:e.unit,value:typeof r==="number"?r:e.min,onChange:i,disabled:d})]})}return a(s,{style:t.fieldRow,children:[n(l,{style:t.fieldLabel,children:e.label}),n(P,{style:t.fieldInput,value:r===void 0||r===null?"":String(r),onChangeText:i,editable:!d,placeholder:e.placeholder??"",placeholderTextColor:o.color.placeholder,keyboardType:e.type==="number"?"numeric":"ascii-capable",secureTextEntry:e.type==="secret",autoCapitalize:"none",autoCorrect:false})]})}function F(e){const[r,i]=w(()=>Object.fromEntries(e.map(d=>[d.key,d.value])));return{values:r,set:(d,c)=>i(p=>({...p,[d]:c}))}}export function Form({block:e}){const r=S();const i=r.answered[e.id];const{values:d,set:c}=F(e.fields);return a(m,{title:e.title,testID:`buoy-block-form-${e.id}`,tone:i?void 0:"info",children:[n(s,{style:t.pad,children:e.fields.map(p=>n(A,{f:p,value:d[p.key],onChange:u=>c(p.key,u),disabled:!!i},p.key))}),i?n(l,{style:t.decided,children:"Sent"}):n(s,{style:t.rowEnd,children:n(f,{onPress:()=>r.answer({blockId:e.id,kind:"form",values:d}),style:[t.button,t.buttonPrimary],children:n(l,{style:t.buttonTextPrimary,children:e.submitLabel??"Send"})})})]})}export function Proposal({block:e}){const r=S();const i=r.answered[e.id];const{values:d,set:c}=F(e.fields);const p=i?.kind==="proposal"?i.approved:null;return a(m,{testID:`buoy-block-proposal-${e.id}`,tone:i?void 0:"warning",children:[n(l,{style:t.confirmTitle,children:e.title}),n(l,{style:t.confirmChange,children:e.change}),n(s,{style:t.pad,children:e.fields.map(u=>n(A,{f:u,value:d[u.key],onChange:y=>c(u.key,y),disabled:!!i},u.key))}),e.consequence?n(l,{style:t.consequence,children:e.consequence}):null,n(s,{style:t.actionsRow,children:i?n(l,{style:t.decided,children:p?"\u2713 Applied with these values":"\u2715 Not applied"}):a(v,{children:[n(f,{onPress:()=>r.answer({blockId:e.id,kind:"proposal",approved:true,values:d}),style:[t.button,t.buttonPrimary],children:n(l,{style:t.buttonTextPrimary,children:e.approveLabel??"Apply"})}),n(f,{onPress:()=>r.answer({blockId:e.id,kind:"proposal",approved:false}),style:[t.button,t.buttonPlate],children:n(l,{style:t.buttonTextPlate,children:"Not now"})})]})})]})}export function Claim({block:e}){const r=S();const[i,d]=w(null);const c=r.actionState?.[`${e.id}:${e.verify?.label??"Verify"}`];const p=e.verified||c==="done";return a(s,{style:t.claim,testID:`buoy-block-claim-${e.id}`,children:[a(l,{style:t.claimText,children:[e.text,e.sources.map((u,y)=>a(l,{onPress:()=>d(i===y?null:y),style:t.cite,children:[" [",y+1,"]"]},y)),p?n(l,{style:t.verified,children:" \u2713 verified"}):null]}),i!==null&&e.sources[i]?a(s,{style:t.citeBox,children:[a(l,{style:t.citeLabel,children:["[",i+1,"] ",e.sources[i].label]}),e.sources[i].detail?n(l,{style:t.citeDetail,children:e.sources[i].detail}):null]}):null,e.verify&&!p?a(f,{onPress:()=>void r.runAction(e.verify,e.id),style:t.verifyBtn,accessibilityRole:"button",children:[n(X,{size:11,color:o.color.accent}),n(l,{style:t.verifyText,children:c==="running"?"Checking\u2026":e.verify.label})]}):null]})}export function Details({block:e}){const[r,i]=w(!!e.open);return a(m,{testID:`buoy-block-details-${e.id}`,children:[a(f,{onPress:()=>i(d=>!d),style:b.row,accessibilityRole:"button",accessibilityState:{expanded:r},children:[n(l,{style:b.rowTitle,children:e.summary}),r?n(L,{size:12,color:o.color.textTertiary}):n(B,{size:12,color:o.color.textTertiary})]}),r?n(l,{style:t.detailsBody,selectable:true,children:e.body}):null]})}export function Tabs({block:e,renderNested:r}){const[i,d]=w(0);const c=e.tabs.map((u,y)=>({key:String(y),label:u.label}));const p=e.tabs[i];return a(s,{style:t.tabsWrap,testID:`buoy-block-tabs-${e.id}`,children:[n(G,{tabs:c,activeKey:String(i),onChange:u=>d(Number(u)),size:"sm"}),p&&r?r(p.block):null]})}const te=10;export function Timeline({block:e}){const r=k(e.events,te);return a(m,{title:e.title??"What happened",source:e.source,testID:`buoy-block-timeline-${e.id}`,children:[r.visible.map((i,d)=>a(s,{style:t.tlRow,children:[n(l,{style:t.tlAt,children:i.at}),a(s,{style:t.tlLine,children:[n(s,{style:[t.tlDot,{backgroundColor:O(i.tone??"muted")}]}),d<r.visible.length-1?n(s,{style:t.tlStem}):null]}),a(s,{style:t.flex,children:[n(l,{style:t.tlSource,children:i.source.toUpperCase()}),n(l,{style:b.rowTitle,numberOfLines:2,children:i.label})]})]},d)),n(Z,{paged:r})]})}export function Sparkline({block:e}){const r=e.series.slice(-60);const i=Math.max(1,...r);const d=Math.min(...r,0);const c=44;return a(m,{title:e.title,source:e.source,testID:`buoy-block-sparkline-${e.id}`,children:[a(s,{style:t.sparkHead,children:[a(l,{style:t.sparkCurrent,children:[e.current??r[r.length-1],e.unit?a(l,{style:t.sparkUnit,children:[" ",e.unit]}):null]}),e.live?n(LivePill,{}):null]}),n(s,{style:[t.spark,{height:c}],children:r.map((p,u)=>n(s,{style:[t.sparkBar,{height:Math.max(2,(p-d)/(i-d||1)*c),backgroundColor:u===r.length-1?o.color.accent:o.color.accentBorderStrong}]},u))}),e.label?n(l,{style:t.hint,children:e.label}):null]})}export function LivePill(){const e=W(new T.Value(1)).current;U(()=>{const r=T.loop(T.sequence([T.timing(e,{toValue:.3,duration:700,useNativeDriver:true}),T.timing(e,{toValue:1,duration:700,useNativeDriver:true})]));r.start();return()=>r.stop()},[e]);return a(s,{style:t.live,children:[n(T.View,{style:[t.liveDot,{opacity:e}]}),n(l,{style:t.liveText,children:"LIVE"})]})}export function Compare({block:e}){const r=[...new Set([...e.left.pairs.map(c=>c.key),...e.right.pairs.map(c=>c.key)])];const i=Object.fromEntries(e.left.pairs.map(c=>[c.key,c.value]));const d=Object.fromEntries(e.right.pairs.map(c=>[c.key,c.value]));return a(m,{title:e.title,source:e.source,testID:`buoy-block-compare-${e.id}`,children:[a(s,{style:t.cmpHead,children:[n(l,{style:t.cmpKey}),n(l,{style:t.cmpCol,children:e.left.title}),n(l,{style:t.cmpCol,children:e.right.title})]}),r.map((c,p)=>{const u=JSON.stringify(i[c])!==JSON.stringify(d[c]);return a(s,{style:[t.cmpRow,p>0&&b.rowDivider,u&&t.cmpDiff],children:[n(l,{style:t.cmpKey,numberOfLines:1,children:c}),n(l,{style:[t.cmpVal,u&&{color:o.color.danger}],numberOfLines:2,children:E(i[c])}),n(l,{style:[t.cmpVal,u&&{color:o.color.accent}],numberOfLines:2,children:E(d[c])})]},c)})]})}function M({c:e,id:r}){return a(v,{children:[e.image?n($,{testID:"buoy-internal-chat-image",source:{uri:e.image},style:t.cardImage,resizeMode:"contain",accessibilityLabel:e.title}):null,a(s,{style:t.cardText,children:[n(l,{style:t.cardTitle,numberOfLines:2,children:e.title}),e.subtitle?n(l,{style:b.rowSub,numberOfLines:2,children:e.subtitle}):null,e.facts?.slice(0,4).map(i=>a(s,{style:t.fact,children:[n(l,{style:t.factLabel,children:i.label}),n(l,{style:[t.factValue,i.tone&&{color:O(i.tone)}],children:i.value})]},i.label))]}),n(D,{actions:e.actions,blockId:r})]})}export function CardView({block:e}){return n(m,{source:e.source,testID:`buoy-block-card-${e.id}`,children:n(M,{c:e,id:e.id})})}export function CardCarousel({block:e}){return a(s,{testID:`buoy-block-cardcarousel-${e.id}`,children:[e.title?n(l,{style:t.carouselTitle,children:e.title}):null,n(_,{horizontal:true,showsHorizontalScrollIndicator:false,snapToInterval:232,decelerationRate:"fast",contentContainerStyle:t.carousel,children:e.cards.slice(0,8).map((r,i)=>n(s,{style:t.carouselCard,children:n(M,{c:r,id:`${e.id}-${i}`})},`${r.title}-${i}`))})]})}export function Screenshot({block:e}){const[r,i]=w(false);return a(m,{title:e.route?`Screen \xB7 ${e.route}`:"Screen",source:e.source,testID:`buoy-block-screenshot-${e.id}`,children:[n(f,{onPress:()=>i(d=>!d),accessibilityRole:"imagebutton",accessibilityLabel:e.caption??"Screenshot",children:n($,{testID:"buoy-internal-chat-image",source:{uri:e.url},style:[t.shot,r&&t.shotBig],resizeMode:"contain"})}),e.caption?n(l,{style:t.hint,children:e.caption}):null]})}export function Plan({block:e}){const r=e.steps.filter(i=>i.state==="done").length;return a(m,{title:e.title,testID:`buoy-block-plan-${e.id}`,tone:e.pending?"info":void 0,children:[n(s,{style:t.progress,children:n(s,{style:[t.progressFill,{width:`${r/Math.max(1,e.steps.length)*100}%`}]})}),e.steps.map((i,d)=>a(s,{style:[t.step,d>0&&b.rowDivider],children:[n(s,{style:[t.stepMark,i.state==="done"&&t.stepDone,i.state==="failed"&&t.stepFailed,i.state==="running"&&t.stepRunning],children:i.state==="done"?n(V,{size:10,color:o.color.onAccent}):i.state==="failed"?n(Y,{size:10,color:o.color.text}):n(l,{style:t.stepNum,children:d+1})}),a(l,{style:[b.rowTitle,i.state==="done"&&t.muted],children:[i.text,i.state==="running"?"\u2026":""]})]},d)),n(D,{actions:e.actions??(e.pending?[{label:"Go",primary:true,send:"Go ahead with the plan."},{label:"Change it",send:"Let me change the plan first."}]:void 0),blockId:e.id})]})}export function Checkpoint({block:e}){const r=S();return a(f,{testID:`buoy-block-checkpoint-${e.id}`,disabled:!e.restore,onPress:()=>e.restore&&void r.runAction(e.restore,e.id),style:t.checkpoint,accessibilityRole:"button",children:[n(s,{style:t.cpFlag}),a(l,{style:t.cpText,children:[e.label,e.at?` \xB7 ${e.at}`:""]}),e.restore?n(l,{style:t.cpRestore,children:e.restore.label}):null]})}export function Task({block:e}){const r=e.status==="failed"?o.color.danger:e.status==="done"?o.color.accent:o.color.info;return a(m,{testID:`buoy-block-task-${e.id}`,children:[a(s,{style:t.taskHead,children:[n(l,{style:t.cardTitle,children:e.title}),n(s,{style:[b.pill,{backgroundColor:`${r}22`}],children:n(l,{style:[b.pillText,{color:r}],children:e.status.toUpperCase()})})]}),e.progress!==void 0?n(s,{style:[t.progress,t.pad],children:n(s,{style:[t.progressFill,{width:`${Math.round(e.progress*100)}%`,backgroundColor:r}]})}):null,e.detail?n(l,{style:t.hint,children:e.detail}):null,n(D,{actions:e.actions,blockId:e.id})]})}export function Banner({block:e}){const r=e.tone==="danger"?o.color.danger:e.tone==="warning"?o.color.warning:o.color.info;return a(s,{style:[t.banner,{borderColor:r,backgroundColor:`${r}1A`}],testID:`buoy-block-banner-${e.id}`,children:[n(J,{size:13,color:r}),n(l,{style:[t.bannerText,{color:r}],numberOfLines:2,children:e.text}),e.actions?.[0]?n(D,{actions:e.actions.slice(0,1),blockId:e.id,align:"right"}):null]})}export function File({block:e}){return a(m,{testID:`buoy-block-file-${e.id}`,children:[a(s,{style:b.row,children:[n(s,{style:t.fileIcon,children:n(l,{style:t.fileExt,children:(e.type??e.name.split(".").pop()??"file").toUpperCase().slice(0,4)})}),a(s,{style:t.flex,children:[n(l,{style:b.rowTitle,numberOfLines:1,children:e.name}),e.size?n(l,{style:b.rowSub,children:e.size}):null]})]}),n(D,{actions:e.actions,blockId:e.id})]})}export function Upload({block:e}){const r=S();const i=r.answered[e.id];return a(m,{testID:`buoy-block-upload-${e.id}`,tone:i?void 0:"info",children:[n(l,{style:t.question,children:e.label}),i?n(l,{style:t.decided,children:i.kind==="upload"?`Attached ${i.name}`:"(skipped)"}):n(s,{style:[t.rowEnd,{justifyContent:"flex-start"}],children:n(f,{onPress:()=>r.answer({blockId:e.id,kind:"upload",name:"screenshot.png"}),style:[t.button,t.buttonPlate],children:n(l,{style:t.buttonTextPlate,children:e.accept==="image"?"Choose a screenshot":"Choose a file"})})})]})}const t=I.create({flex:{flex:1},pad:{paddingHorizontal:12,paddingBottom:8},muted:{color:o.color.textSecondary},dim:{opacity:.45},disabled:{opacity:.5},pressed:{opacity:.7},question:{color:o.color.text,fontSize:o.font.row,fontWeight:"600",lineHeight:20,paddingHorizontal:12,paddingTop:12,paddingBottom:8},hint:{color:o.color.textTertiary,fontSize:o.font.micro,paddingHorizontal:12,paddingBottom:10},decided:{color:o.color.textSecondary,fontSize:o.font.caption,fontWeight:"600",paddingHorizontal:12,paddingVertical:10},rowEnd:{flexDirection:"row",justifyContent:"flex-end",paddingHorizontal:12,paddingBottom:12,gap:8},actionsRow:{flexDirection:"row",gap:8,paddingHorizontal:12,paddingVertical:12,alignItems:"center"},button:{paddingHorizontal:14,paddingVertical:9,borderRadius:o.radius.row},buttonPrimary:{backgroundColor:o.color.accent},buttonPlate:{backgroundColor:o.color.button},buttonTextPrimary:{color:o.color.onAccent,fontSize:o.font.caption,fontWeight:"600"},buttonTextPlate:{color:o.color.text,fontSize:o.font.caption,fontWeight:"600"},chips:{flexDirection:"row",flexWrap:"wrap",gap:6},chip:{paddingHorizontal:12,paddingVertical:7,borderRadius:o.radius.chip,backgroundColor:o.color.fill,borderWidth:I.hairlineWidth,borderColor:o.color.border},chipOn:{backgroundColor:o.color.accentSoft,borderColor:o.color.accentBorderStrong},chipText:{color:o.color.text,fontSize:o.font.caption},chipTextOn:{color:o.color.accent,fontWeight:"600"},optionsBox:{marginHorizontal:8,marginBottom:8,borderRadius:o.radius.row,backgroundColor:o.color.surfaceElevated,overflow:"hidden"},option:{flexDirection:"row",alignItems:"center",gap:10,paddingHorizontal:12,paddingVertical:10},optionLabel:{color:o.color.text,fontSize:o.font.body,fontWeight:"500"},optionDetail:{color:o.color.textSecondary,fontSize:o.font.caption},checkbox:{width:20,height:20,borderRadius:6,borderWidth:1.5,borderColor:o.color.textTertiary,alignItems:"center",justifyContent:"center"},checkboxOn:{backgroundColor:o.color.accent,borderColor:o.color.accent},selectBox:{marginHorizontal:12,marginBottom:12,borderRadius:o.radius.row,backgroundColor:o.color.surfaceElevated,overflow:"hidden"},selectField:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",paddingHorizontal:12,paddingVertical:11},selectPlaceholder:{color:o.color.textSecondary,fontSize:o.font.body},search:{flexDirection:"row",alignItems:"center",gap:6,paddingHorizontal:12,paddingVertical:6,borderTopWidth:I.hairlineWidth,borderTopColor:o.color.separator},searchInput:{flex:1,color:o.color.text,fontSize:o.font.caption,paddingVertical:4},sliderRow:{flexDirection:"row",alignItems:"center",gap:8},stepBtn:{width:28,height:28,borderRadius:14,backgroundColor:o.color.fill,alignItems:"center",justifyContent:"center"},stepText:{color:o.color.text,fontSize:16,fontWeight:"600"},trackHit:{flex:1,height:36,justifyContent:"center"},track:{height:6,borderRadius:3,backgroundColor:o.color.fillTertiary,justifyContent:"center"},fill:{height:6,borderRadius:3,backgroundColor:o.color.accent},thumb:{position:"absolute",width:20,height:20,borderRadius:10,backgroundColor:o.color.knob,marginLeft:-10,shadowColor:"#000",shadowOpacity:.35,shadowRadius:3,shadowOffset:{width:0,height:1}},sliderValue:{width:64,textAlign:"right",color:o.color.text,fontSize:o.font.caption,fontWeight:"600"},inputRow:{flexDirection:"row",alignItems:"center",gap:8,paddingBottom:12},input:{flex:1,height:38,paddingHorizontal:12,borderRadius:o.radius.row,backgroundColor:o.color.surfaceElevated,color:o.color.text,fontSize:o.font.body},fieldRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:10,paddingVertical:6},fieldCol:{paddingVertical:6,gap:6},fieldLabel:{color:o.color.textSecondary,fontSize:o.font.caption,width:110},fieldInput:{flex:1,height:34,paddingHorizontal:10,borderRadius:o.radius.segment,backgroundColor:o.color.surfaceElevated,color:o.color.text,fontSize:o.font.caption},confirmTitle:{color:o.color.text,fontSize:o.font.row,fontWeight:"600",paddingHorizontal:12,paddingTop:12},confirmChange:{color:o.color.text,fontSize:o.font.body,lineHeight:19,paddingHorizontal:12,paddingTop:6,paddingBottom:6},consequence:{color:o.color.warning,fontSize:o.font.caption,paddingHorizontal:12},claim:{gap:6,paddingHorizontal:2},claimText:{color:o.color.text,fontSize:o.font.row,lineHeight:22},cite:{color:o.color.accent,fontSize:o.font.caption,fontWeight:"700"},verified:{color:o.color.accent,fontSize:o.font.caption,fontWeight:"600"},citeBox:{padding:10,borderRadius:o.radius.row,backgroundColor:o.color.surface,borderWidth:I.hairlineWidth,borderColor:o.color.border,gap:2},citeLabel:{color:o.color.text,fontSize:o.font.caption,fontWeight:"600"},citeDetail:{color:o.color.textSecondary,fontSize:o.font.caption,fontFamily:H.OS==="web"?R:"Menlo"},verifyBtn:{alignSelf:"flex-start",flexDirection:"row",alignItems:"center",gap:5,paddingHorizontal:10,paddingVertical:5,borderRadius:o.radius.chip,backgroundColor:o.color.accentSoft},verifyText:{color:o.color.accent,fontSize:o.font.micro,fontWeight:"700"},detailsBody:{color:o.color.textSecondary,fontSize:o.font.caption,lineHeight:18,paddingHorizontal:12,paddingBottom:12},tabsWrap:{gap:8},tabBar:{flexDirection:"row",gap:6},tab:{paddingHorizontal:12,paddingVertical:6,borderRadius:o.radius.badge,backgroundColor:o.color.fill},tabOn:{backgroundColor:o.color.accentSoft},tabText:{color:o.color.textSecondary,fontSize:o.font.caption,fontWeight:"600"},tabTextOn:{color:o.color.accent},tlRow:{flexDirection:"row",gap:10,paddingHorizontal:12,paddingVertical:4,alignItems:"flex-start"},tlAt:{width:46,color:o.color.textTertiary,fontSize:o.font.micro,fontFamily:H.OS==="web"?R:"Menlo",paddingTop:3},tlLine:{width:12,alignItems:"center"},tlDot:{width:8,height:8,borderRadius:4,marginTop:5},tlStem:{width:1,flex:1,minHeight:18,backgroundColor:o.color.separator,marginTop:2},tlSource:{color:o.color.textTertiary,fontSize:9,fontWeight:"700",letterSpacing:.6},sparkHead:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",paddingHorizontal:12,paddingTop:8},sparkCurrent:{color:o.color.text,fontSize:22,fontWeight:"700"},sparkUnit:{color:o.color.textSecondary,fontSize:o.font.caption,fontWeight:"500"},spark:{flexDirection:"row",alignItems:"flex-end",gap:2,paddingHorizontal:12,paddingTop:6,paddingBottom:6},sparkBar:{flex:1,borderRadius:1},live:{flexDirection:"row",alignItems:"center",gap:5,paddingHorizontal:8,paddingVertical:3,borderRadius:o.radius.badge,backgroundColor:o.color.dangerSoft},liveDot:{width:6,height:6,borderRadius:3,backgroundColor:o.color.danger},liveText:{color:o.color.danger,fontSize:9,fontWeight:"800",letterSpacing:.8},cmpHead:{flexDirection:"row",paddingHorizontal:12,paddingVertical:6,backgroundColor:o.color.fill,gap:8},cmpCol:{flex:1,color:o.color.textTertiary,fontSize:o.font.micro,fontWeight:"700",letterSpacing:.6,textTransform:"uppercase"},cmpRow:{flexDirection:"row",paddingHorizontal:12,paddingVertical:8,gap:8,alignItems:"flex-start"},cmpDiff:{backgroundColor:o.color.fill},cmpKey:{width:90,color:o.color.textSecondary,fontSize:o.font.caption},cmpVal:{flex:1,color:o.color.text,fontSize:o.font.caption,fontFamily:H.OS==="web"?R:"Menlo"},cardImage:{width:"100%",height:140,backgroundColor:o.color.fill},cardText:{paddingHorizontal:12,paddingTop:10,paddingBottom:4,gap:4},cardTitle:{color:o.color.text,fontSize:o.font.row,fontWeight:"600"},fact:{flexDirection:"row",justifyContent:"space-between",gap:8},factLabel:{color:o.color.textSecondary,fontSize:o.font.caption},factValue:{color:o.color.text,fontSize:o.font.caption,fontWeight:"500"},carouselTitle:{color:o.color.text,fontSize:o.font.caption,fontWeight:"600",paddingBottom:8},carousel:{gap:8,paddingRight:8},carouselCard:{width:224,borderRadius:o.radius.card,borderWidth:I.hairlineWidth,borderColor:o.color.border,backgroundColor:o.color.surface,overflow:"hidden"},shot:{width:"100%",height:220,backgroundColor:o.color.fill},shotBig:{height:520},progress:{height:4,backgroundColor:o.color.fill,marginHorizontal:12,marginTop:6,marginBottom:8,borderRadius:2,overflow:"hidden"},progressFill:{height:4,backgroundColor:o.color.accent},step:{flexDirection:"row",alignItems:"center",gap:10,paddingHorizontal:12,paddingVertical:9},stepMark:{width:20,height:20,borderRadius:10,backgroundColor:o.color.fillTertiary,alignItems:"center",justifyContent:"center"},stepDone:{backgroundColor:o.color.accent},stepFailed:{backgroundColor:o.color.danger},stepRunning:{backgroundColor:o.color.info},stepNum:{color:o.color.text,fontSize:o.font.micro,fontWeight:"700"},checkpoint:{alignSelf:"flex-start",flexDirection:"row",alignItems:"center",gap:8,paddingHorizontal:12,paddingVertical:7,borderRadius:o.radius.chip,backgroundColor:o.color.fill},cpFlag:{width:8,height:8,borderRadius:2,backgroundColor:o.color.info},cpText:{color:o.color.textSecondary,fontSize:o.font.caption},cpRestore:{color:o.color.accent,fontSize:o.font.caption,fontWeight:"600"},taskHead:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:8,paddingHorizontal:12,paddingTop:12},banner:{flexDirection:"row",alignItems:"center",gap:8,paddingLeft:12,paddingRight:4,paddingVertical:4,borderRadius:o.radius.row,borderWidth:I.hairlineWidth},bannerText:{flex:1,fontSize:o.font.caption,fontWeight:"600"},fileIcon:{width:40,height:40,borderRadius:8,backgroundColor:o.color.fill,alignItems:"center",justifyContent:"center"},fileExt:{color:o.color.textSecondary,fontSize:10,fontWeight:"800"}});
|
|
1
|
+
"use strict";import{monoFont as P}from"@buoy-gg/shared-ui";import{Platform as H}from"react-native";import{useEffect as K,useRef as W,useState as w}from"react";import{Animated as T,Image as $,Pressable as f,ScrollView as _,StyleSheet as I,Switch as J,Text as l,TextInput as V,View as c}from"react-native";import{AlertTriangle as k,Check as B,ChevronDown as L,ChevronRight as O,night as o,NightSegmented as G,RefreshCw as X,Search as Q,X as Y}from"@buoy-gg/shared-ui";import{useBlockHandlers as S}from"./context";import{ActionRow as v,blockStyles as b,Card as y,More as Z,toneColor as E,usePaged as ee}from"./primitives";import{multiAnswer as te,selectView as oe,shownFormValues as j}from"./choiceLogic";import{jsx as n,jsxs as a,Fragment as D}from"react/jsx-runtime";const A=e=>typeof e==="string"?e:e===void 0?"\u2014":JSON.stringify(e);export function Suggestions({block:e}){const r=S();return n(c,{style:t.chips,testID:`buoy-block-suggestions-${e.id}`,children:e.chips.slice(0,4).map(i=>n(f,{disabled:!!r.busy,onPress:()=>void r.runAction({label:i.label,send:i.send??i.label},e.id),style:({pressed:d})=>[t.chip,d&&t.pressed],accessibilityRole:"button",children:n(l,{style:t.chipText,children:i.label})},i.label))})}export function MultiSelect({block:e}){const r=S();const i=r.answered[e.id];const d=!!i;const[s,p]=w([]);const u=i?.kind==="multi"?i.optionIds:s;const h=m=>p(x=>x.includes(m)?x.filter(g=>g!==m):[...x,m]);const C=s.length>=(e.min??1)&&(!e.max||s.length<=e.max);return a(y,{testID:`buoy-block-multi-${e.id}`,tone:d?void 0:"info",children:[n(l,{style:t.question,children:e.question}),n(c,{style:t.optionsBox,children:e.options.map((m,x)=>{const g=u.includes(m.id);return a(f,{disabled:d,onPress:()=>h(m.id),style:[t.option,x>0&&b.rowDivider,d&&!g&&t.dim],accessibilityRole:"checkbox",accessibilityState:{checked:g},children:[n(c,{style:[t.checkbox,g&&t.checkboxOn],children:g?n(B,{size:11,color:o.color.onAccent}):null}),a(c,{style:t.flex,children:[n(l,{style:t.optionLabel,children:m.label}),m.detail?n(l,{style:t.optionDetail,children:m.detail}):null]})]},m.id)})}),d?null:n(c,{style:t.rowEnd,children:n(f,{testID:"buoy-block-multi-submit",disabled:!C,onPress:()=>r.answer({blockId:e.id,kind:"multi",...te(e.options,s)}),style:[t.button,t.buttonPrimary,!C&&t.disabled],children:n(l,{style:t.buttonTextPrimary,children:e.submitLabel??`Use ${s.length||""}`.trim()})})})]})}export function Select({block:e}){const r=S();const i=r.answered[e.id];const[d,s]=w("");const[p,u]=w(false);const h=i?.kind==="select"?i.label:null;const{showSearch:C,items:m,hidden:x}=oe(e.options,d,e.searchable);return a(y,{testID:`buoy-block-select-${e.id}`,tone:i?void 0:"info",children:[n(l,{style:t.question,children:e.label}),h?n(l,{style:t.decided,children:h}):a(c,{style:t.selectBox,children:[a(f,{onPress:()=>u(g=>!g),style:t.selectField,accessibilityRole:"button",children:[n(l,{style:t.selectPlaceholder,children:p?"Choose\u2026":`${e.options.length} options`}),n(L,{size:14,color:o.color.textSecondary})]}),p&&a(D,{children:[C&&a(c,{style:t.search,children:[n(Q,{size:12,color:o.color.textSecondary}),n(V,{style:t.searchInput,value:d,onChangeText:s,placeholder:"Search",placeholderTextColor:o.color.placeholder,autoCapitalize:"none",autoCorrect:false})]}),m.map((g,R)=>a(f,{onPress:()=>r.answer({blockId:e.id,kind:"select",optionId:g.id,label:g.label}),style:[t.option,R>0&&b.rowDivider],accessibilityRole:"button",children:[a(c,{style:t.flex,children:[n(l,{style:t.optionLabel,children:g.label}),g.detail?n(l,{style:t.optionDetail,children:g.detail}):null]}),n(O,{size:12,color:o.color.textTertiary})]},g.id)),x>0?a(l,{style:t.hint,children:[x," more \u2014 keep typing"]}):null]})]})]})}function F({min:e,max:r,step:i=1,value:d,unit:s,onChange:p,disabled:u}){const h=W(0);const C=r>e?(d-e)/(r-e)*100:0;const m=z=>Math.min(r,Math.max(e,+(Math.round((z-e)/i)*i+e).toFixed(6)));const x=z=>!u&&p(m(d+z*i));const g=z=>{if(u||h.current<=0)return;const U=Math.min(1,Math.max(0,z.nativeEvent.locationX/h.current));p(m(e+U*(r-e)))};const R=z=>{h.current=z.nativeEvent.layout.width};return a(c,{style:t.sliderRow,children:[n(f,{disabled:u,onPress:()=>x(-1),style:t.stepBtn,accessibilityRole:"button",accessibilityLabel:"Decrease",children:n(l,{style:t.stepText,children:"\u2212"})}),n(f,{style:t.trackHit,onLayout:R,onPress:g,disabled:u,accessibilityRole:"adjustable",accessibilityValue:{min:e,max:r,now:d},accessibilityActions:[{name:"increment"},{name:"decrement"}],onAccessibilityAction:z=>x(z.nativeEvent.actionName==="increment"?1:-1),children:a(c,{style:t.track,pointerEvents:"none",children:[n(c,{style:[t.fill,{width:`${C}%`}]}),n(c,{style:[t.thumb,{left:`${C}%`}]})]})}),n(f,{disabled:u,onPress:()=>x(1),style:t.stepBtn,accessibilityRole:"button",accessibilityLabel:"Increase",children:n(l,{style:t.stepText,children:"+"})}),a(l,{style:t.sliderValue,children:[d,s?` ${s}`:""]})]})}export function Slider({block:e}){const r=S();const i=r.answered[e.id];const[d,s]=w(e.value??e.min);return a(y,{testID:`buoy-block-slider-${e.id}`,tone:i?void 0:"info",children:[n(l,{style:t.question,children:e.label}),n(c,{style:t.pad,children:n(F,{min:e.min,max:e.max,step:e.step,value:i?.kind==="slider"?i.value:d,unit:e.unit,onChange:s,disabled:!!i})}),i?null:n(c,{style:t.rowEnd,children:n(f,{onPress:()=>r.answer({blockId:e.id,kind:"slider",value:d,unit:e.unit}),style:[t.button,t.buttonPrimary],children:a(l,{style:t.buttonTextPrimary,children:["Use ",d,e.unit?` ${e.unit}`:""]})})})]})}export function DateTime({block:e}){const r=S();const i=r.answered[e.id];const[d,s]=w("");const p=e.presets??[{label:"Now",value:new Date().toISOString()},{label:"Tomorrow",value:new Date(Date.now()+864e5).toISOString()},{label:"+7 days",value:new Date(Date.now()+7*864e5).toISOString()},{label:"+30 days",value:new Date(Date.now()+30*864e5).toISOString()}];return a(y,{testID:`buoy-block-datetime-${e.id}`,tone:i?void 0:"info",children:[n(l,{style:t.question,children:e.label}),i?n(l,{style:t.decided,children:i.kind==="dateTime"?i.label??i.value:"(skipped)"}):a(D,{children:[n(c,{style:[t.chips,t.pad],children:p.map(u=>n(f,{onPress:()=>r.answer({blockId:e.id,kind:"dateTime",value:u.value,label:u.label}),style:t.chip,children:n(l,{style:t.chipText,children:u.label})},u.label))}),a(c,{style:[t.inputRow,t.pad],children:[n(V,{style:t.input,value:d,onChangeText:s,placeholder:e.mode==="time"?"14:30":"2026-09-15",placeholderTextColor:o.color.placeholder,keyboardType:"numbers-and-punctuation"}),n(f,{disabled:!d.trim(),onPress:()=>r.answer({blockId:e.id,kind:"dateTime",value:d.trim()}),style:[t.button,t.buttonPrimary,!d.trim()&&t.disabled],children:n(l,{style:t.buttonTextPrimary,children:"Use"})})]})]})]})}function M({f:e,value:r,onChange:i,disabled:d}){if(e.type==="toggle"){return a(c,{style:t.fieldRow,children:[n(l,{style:t.fieldLabel,children:e.label}),n(J,{value:!!r,onValueChange:i,disabled:d,trackColor:{true:o.color.accent,false:o.color.fillTertiary},thumbColor:o.color.knob})]})}if(e.type==="select"){return a(c,{style:t.fieldCol,children:[n(l,{style:t.fieldLabel,children:e.label}),n(c,{style:t.chips,children:e.options.map(s=>n(f,{disabled:d,onPress:()=>i(s.id),style:[t.chip,r===s.id&&t.chipOn],children:n(l,{style:[t.chipText,r===s.id&&t.chipTextOn],children:s.label})},s.id))})]})}if(e.type==="slider"){return a(c,{style:t.fieldCol,children:[n(l,{style:t.fieldLabel,children:e.label}),n(F,{min:e.min,max:e.max,step:e.step,unit:e.unit,value:typeof r==="number"?r:e.min,onChange:i,disabled:d})]})}return a(c,{style:t.fieldRow,children:[n(l,{style:t.fieldLabel,children:e.label}),n(V,{style:t.fieldInput,value:r===void 0||r===null?"":String(r),onChangeText:i,editable:!d,placeholder:e.placeholder??"",placeholderTextColor:o.color.placeholder,keyboardType:e.type==="number"?"numeric":"ascii-capable",secureTextEntry:e.type==="secret",autoCapitalize:"none",autoCorrect:false})]})}function q(e){const[r,i]=w(()=>Object.fromEntries(e.map(d=>[d.key,d.value])));return{values:r,set:(d,s)=>i(p=>({...p,[d]:s}))}}export function Form({block:e}){const r=S();const i=r.answered[e.id];const{values:d,set:s}=q(e.fields);const p=j(i,d);return a(y,{title:e.title,testID:`buoy-block-form-${e.id}`,tone:i?void 0:"info",children:[n(c,{style:t.pad,children:e.fields.map(u=>n(M,{f:u,value:p[u.key],onChange:h=>s(u.key,h),disabled:!!i},u.key))}),i?n(l,{style:t.decided,children:"Sent"}):n(c,{style:t.rowEnd,children:n(f,{onPress:()=>r.answer({blockId:e.id,kind:"form",values:p}),style:[t.button,t.buttonPrimary],children:n(l,{style:t.buttonTextPrimary,children:e.submitLabel??"Send"})})})]})}export function Proposal({block:e}){const r=S();const i=r.answered[e.id];const{values:d,set:s}=q(e.fields);const p=j(i,d);const u=i?.kind==="proposal"?i.approved:null;return a(y,{testID:`buoy-block-proposal-${e.id}`,tone:i?void 0:"warning",children:[n(l,{style:t.confirmTitle,children:e.title}),n(l,{style:t.confirmChange,children:e.change}),n(c,{style:t.pad,children:e.fields.map(h=>n(M,{f:h,value:p[h.key],onChange:C=>s(h.key,C),disabled:!!i},h.key))}),e.consequence?n(l,{style:t.consequence,children:e.consequence}):null,n(c,{style:t.actionsRow,children:i?n(l,{style:t.decided,children:u?"\u2713 Applied with these values":"\u2715 Not applied"}):a(D,{children:[n(f,{onPress:()=>r.answer({blockId:e.id,kind:"proposal",approved:true,values:p}),style:[t.button,t.buttonPrimary],children:n(l,{style:t.buttonTextPrimary,children:e.approveLabel??"Apply"})}),n(f,{onPress:()=>r.answer({blockId:e.id,kind:"proposal",approved:false}),style:[t.button,t.buttonPlate],children:n(l,{style:t.buttonTextPlate,children:"Not now"})})]})})]})}export function Claim({block:e}){const r=S();const[i,d]=w(null);const s=r.actionState?.[`${e.id}:${e.verify?.label??"Verify"}`];const p=e.verified||s==="done";return a(c,{style:t.claim,testID:`buoy-block-claim-${e.id}`,children:[a(l,{style:t.claimText,children:[e.text,e.sources.map((u,h)=>a(l,{onPress:()=>d(i===h?null:h),style:t.cite,children:[" [",h+1,"]"]},h)),p?n(l,{style:t.verified,children:" \u2713 verified"}):null]}),i!==null&&e.sources[i]?a(c,{style:t.citeBox,children:[a(l,{style:t.citeLabel,children:["[",i+1,"] ",e.sources[i].label]}),e.sources[i].detail?n(l,{style:t.citeDetail,children:e.sources[i].detail}):null]}):null,e.verify&&!p?a(f,{onPress:()=>void r.runAction(e.verify,e.id),style:t.verifyBtn,accessibilityRole:"button",children:[n(X,{size:11,color:o.color.accent}),n(l,{style:t.verifyText,children:s==="running"?"Checking\u2026":e.verify.label})]}):null]})}export function Details({block:e}){const[r,i]=w(!!e.open);return a(y,{testID:`buoy-block-details-${e.id}`,children:[a(f,{onPress:()=>i(d=>!d),style:b.row,accessibilityRole:"button",accessibilityState:{expanded:r},children:[n(l,{style:b.rowTitle,children:e.summary}),r?n(L,{size:12,color:o.color.textTertiary}):n(O,{size:12,color:o.color.textTertiary})]}),r?n(l,{style:t.detailsBody,selectable:true,children:e.body}):null]})}export function Tabs({block:e,renderNested:r}){const[i,d]=w(0);const s=e.tabs.map((u,h)=>({key:String(h),label:u.label}));const p=e.tabs[i];return a(c,{style:t.tabsWrap,testID:`buoy-block-tabs-${e.id}`,children:[n(G,{tabs:s,activeKey:String(i),onChange:u=>d(Number(u)),size:"sm"}),p&&r?r(p.block):null]})}const ne=10;export function Timeline({block:e}){const r=ee(e.events,ne);return a(y,{title:e.title??"What happened",source:e.source,testID:`buoy-block-timeline-${e.id}`,children:[r.visible.map((i,d)=>a(c,{style:t.tlRow,children:[n(l,{style:t.tlAt,children:i.at}),a(c,{style:t.tlLine,children:[n(c,{style:[t.tlDot,{backgroundColor:E(i.tone??"muted")}]}),d<r.visible.length-1?n(c,{style:t.tlStem}):null]}),a(c,{style:t.flex,children:[n(l,{style:t.tlSource,children:i.source.toUpperCase()}),n(l,{style:b.rowTitle,numberOfLines:2,children:i.label})]})]},d)),n(Z,{paged:r})]})}export function Sparkline({block:e}){const r=e.series.slice(-60);const i=Math.max(1,...r);const d=Math.min(...r,0);const s=44;return a(y,{title:e.title,source:e.source,testID:`buoy-block-sparkline-${e.id}`,children:[a(c,{style:t.sparkHead,children:[a(l,{style:t.sparkCurrent,children:[e.current??r[r.length-1],e.unit?a(l,{style:t.sparkUnit,children:[" ",e.unit]}):null]}),e.live?n(LivePill,{}):null]}),n(c,{style:[t.spark,{height:s}],children:r.map((p,u)=>n(c,{style:[t.sparkBar,{height:Math.max(2,(p-d)/(i-d||1)*s),backgroundColor:u===r.length-1?o.color.accent:o.color.accentBorderStrong}]},u))}),e.label?n(l,{style:t.hint,children:e.label}):null]})}export function LivePill(){const e=W(new T.Value(1)).current;K(()=>{const r=T.loop(T.sequence([T.timing(e,{toValue:.3,duration:700,useNativeDriver:true}),T.timing(e,{toValue:1,duration:700,useNativeDriver:true})]));r.start();return()=>r.stop()},[e]);return a(c,{style:t.live,children:[n(T.View,{style:[t.liveDot,{opacity:e}]}),n(l,{style:t.liveText,children:"LIVE"})]})}export function Compare({block:e}){const r=[...new Set([...e.left.pairs.map(s=>s.key),...e.right.pairs.map(s=>s.key)])];const i=Object.fromEntries(e.left.pairs.map(s=>[s.key,s.value]));const d=Object.fromEntries(e.right.pairs.map(s=>[s.key,s.value]));return a(y,{title:e.title,source:e.source,testID:`buoy-block-compare-${e.id}`,children:[a(c,{style:t.cmpHead,children:[n(l,{style:t.cmpKey}),n(l,{style:t.cmpCol,children:e.left.title}),n(l,{style:t.cmpCol,children:e.right.title})]}),r.map((s,p)=>{const u=JSON.stringify(i[s])!==JSON.stringify(d[s]);return a(c,{style:[t.cmpRow,p>0&&b.rowDivider,u&&t.cmpDiff],children:[n(l,{style:t.cmpKey,numberOfLines:1,children:s}),n(l,{style:[t.cmpVal,u&&{color:o.color.danger}],numberOfLines:2,children:A(i[s])}),n(l,{style:[t.cmpVal,u&&{color:o.color.accent}],numberOfLines:2,children:A(d[s])})]},s)})]})}function N({c:e,id:r}){return a(D,{children:[e.image?n($,{testID:"buoy-internal-chat-image",source:{uri:e.image},style:t.cardImage,resizeMode:"contain",accessibilityLabel:e.title}):null,a(c,{style:t.cardText,children:[n(l,{style:t.cardTitle,numberOfLines:2,children:e.title}),e.subtitle?n(l,{style:b.rowSub,numberOfLines:2,children:e.subtitle}):null,e.facts?.slice(0,4).map(i=>a(c,{style:t.fact,children:[n(l,{style:t.factLabel,children:i.label}),n(l,{style:[t.factValue,i.tone&&{color:E(i.tone)}],children:i.value})]},i.label))]}),n(v,{actions:e.actions,blockId:r})]})}export function CardView({block:e}){return n(y,{source:e.source,testID:`buoy-block-card-${e.id}`,children:n(N,{c:e,id:e.id})})}export function CardCarousel({block:e}){return a(c,{testID:`buoy-block-cardcarousel-${e.id}`,children:[e.title?n(l,{style:t.carouselTitle,children:e.title}):null,n(_,{horizontal:true,showsHorizontalScrollIndicator:false,snapToInterval:232,decelerationRate:"fast",contentContainerStyle:t.carousel,children:e.cards.slice(0,8).map((r,i)=>n(c,{style:t.carouselCard,children:n(N,{c:r,id:`${e.id}-${i}`})},`${r.title}-${i}`))})]})}export function Screenshot({block:e}){const[r,i]=w(false);return a(y,{title:e.route?`Screen \xB7 ${e.route}`:"Screen",source:e.source,testID:`buoy-block-screenshot-${e.id}`,children:[n(f,{onPress:()=>i(d=>!d),accessibilityRole:"imagebutton",accessibilityLabel:e.caption??"Screenshot",children:n($,{testID:"buoy-internal-chat-image",source:{uri:e.url},style:[t.shot,r&&t.shotBig],resizeMode:"contain"})}),e.caption?n(l,{style:t.hint,children:e.caption}):null]})}export function Plan({block:e}){const r=e.steps.filter(i=>i.state==="done").length;return a(y,{title:e.title,testID:`buoy-block-plan-${e.id}`,tone:e.pending?"info":void 0,children:[n(c,{style:t.progress,children:n(c,{style:[t.progressFill,{width:`${r/Math.max(1,e.steps.length)*100}%`}]})}),e.steps.map((i,d)=>a(c,{style:[t.step,d>0&&b.rowDivider],children:[n(c,{style:[t.stepMark,i.state==="done"&&t.stepDone,i.state==="failed"&&t.stepFailed,i.state==="running"&&t.stepRunning],children:i.state==="done"?n(B,{size:10,color:o.color.onAccent}):i.state==="failed"?n(Y,{size:10,color:o.color.text}):n(l,{style:t.stepNum,children:d+1})}),a(l,{style:[b.rowTitle,i.state==="done"&&t.muted],children:[i.text,i.state==="running"?"\u2026":""]})]},d)),n(v,{actions:e.actions??(e.pending?[{label:"Go",primary:true,send:"Go ahead with the plan."},{label:"Change it",send:"Let me change the plan first."}]:void 0),blockId:e.id})]})}export function Checkpoint({block:e}){const r=S();return a(f,{testID:`buoy-block-checkpoint-${e.id}`,disabled:!e.restore,onPress:()=>e.restore&&void r.runAction(e.restore,e.id),style:t.checkpoint,accessibilityRole:"button",children:[n(c,{style:t.cpFlag}),a(l,{style:t.cpText,children:[e.label,e.at?` \xB7 ${e.at}`:""]}),e.restore?n(l,{style:t.cpRestore,children:e.restore.label}):null]})}export function Task({block:e}){const r=e.status==="failed"?o.color.danger:e.status==="done"?o.color.accent:o.color.info;return a(y,{testID:`buoy-block-task-${e.id}`,children:[a(c,{style:t.taskHead,children:[n(l,{style:t.cardTitle,children:e.title}),n(c,{style:[b.pill,{backgroundColor:`${r}22`}],children:n(l,{style:[b.pillText,{color:r}],children:e.status.toUpperCase()})})]}),e.progress!==void 0?n(c,{style:[t.progress,t.pad],children:n(c,{style:[t.progressFill,{width:`${Math.round(e.progress*100)}%`,backgroundColor:r}]})}):null,e.detail?n(l,{style:t.hint,children:e.detail}):null,n(v,{actions:e.actions,blockId:e.id})]})}export function Banner({block:e}){const r=e.tone==="danger"?o.color.danger:e.tone==="warning"?o.color.warning:o.color.info;return a(c,{style:[t.banner,{borderColor:r,backgroundColor:`${r}1A`}],testID:`buoy-block-banner-${e.id}`,children:[n(k,{size:13,color:r}),n(l,{style:[t.bannerText,{color:r}],numberOfLines:2,children:e.text}),e.actions?.[0]?n(v,{actions:e.actions.slice(0,1),blockId:e.id,align:"right"}):null]})}export function File({block:e}){return a(y,{testID:`buoy-block-file-${e.id}`,children:[a(c,{style:b.row,children:[n(c,{style:t.fileIcon,children:n(l,{style:t.fileExt,children:(e.type??e.name.split(".").pop()??"file").toUpperCase().slice(0,4)})}),a(c,{style:t.flex,children:[n(l,{style:b.rowTitle,numberOfLines:1,children:e.name}),e.size?n(l,{style:b.rowSub,children:e.size}):null]})]}),n(v,{actions:e.actions,blockId:e.id})]})}export function Upload({block:e}){const r=S();const i=r.answered[e.id];return a(y,{testID:`buoy-block-upload-${e.id}`,tone:i?void 0:"info",children:[n(l,{style:t.question,children:e.label}),i?n(l,{style:t.decided,children:i.kind==="upload"?`Attached ${i.name}`:"(skipped)"}):n(c,{style:[t.rowEnd,{justifyContent:"flex-start"}],children:n(f,{onPress:()=>r.answer({blockId:e.id,kind:"upload",name:"screenshot.png"}),style:[t.button,t.buttonPlate],children:n(l,{style:t.buttonTextPlate,children:e.accept==="image"?"Choose a screenshot":"Choose a file"})})})]})}const t=I.create({flex:{flex:1},pad:{paddingHorizontal:12,paddingBottom:8},muted:{color:o.color.textSecondary},dim:{opacity:.45},disabled:{opacity:.5},pressed:{opacity:.7},question:{color:o.color.text,fontSize:o.font.row,fontWeight:"600",lineHeight:20,paddingHorizontal:12,paddingTop:12,paddingBottom:8},hint:{color:o.color.textTertiary,fontSize:o.font.micro,paddingHorizontal:12,paddingBottom:10},decided:{color:o.color.textSecondary,fontSize:o.font.caption,fontWeight:"600",paddingHorizontal:12,paddingVertical:10},rowEnd:{flexDirection:"row",justifyContent:"flex-end",paddingHorizontal:12,paddingBottom:12,gap:8},actionsRow:{flexDirection:"row",gap:8,paddingHorizontal:12,paddingVertical:12,alignItems:"center"},button:{paddingHorizontal:14,paddingVertical:9,borderRadius:o.radius.row},buttonPrimary:{backgroundColor:o.color.accent},buttonPlate:{backgroundColor:o.color.button},buttonTextPrimary:{color:o.color.onAccent,fontSize:o.font.caption,fontWeight:"600"},buttonTextPlate:{color:o.color.text,fontSize:o.font.caption,fontWeight:"600"},chips:{flexDirection:"row",flexWrap:"wrap",gap:6},chip:{paddingHorizontal:12,paddingVertical:7,borderRadius:o.radius.chip,backgroundColor:o.color.fill,borderWidth:I.hairlineWidth,borderColor:o.color.border},chipOn:{backgroundColor:o.color.accentSoft,borderColor:o.color.accentBorderStrong},chipText:{color:o.color.text,fontSize:o.font.caption},chipTextOn:{color:o.color.accent,fontWeight:"600"},optionsBox:{marginHorizontal:8,marginBottom:8,borderRadius:o.radius.row,backgroundColor:o.color.surfaceElevated,overflow:"hidden"},option:{flexDirection:"row",alignItems:"center",gap:10,paddingHorizontal:12,paddingVertical:10},optionLabel:{color:o.color.text,fontSize:o.font.body,fontWeight:"500"},optionDetail:{color:o.color.textSecondary,fontSize:o.font.caption},checkbox:{width:20,height:20,borderRadius:6,borderWidth:1.5,borderColor:o.color.textTertiary,alignItems:"center",justifyContent:"center"},checkboxOn:{backgroundColor:o.color.accent,borderColor:o.color.accent},selectBox:{marginHorizontal:12,marginBottom:12,borderRadius:o.radius.row,backgroundColor:o.color.surfaceElevated,overflow:"hidden"},selectField:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",paddingHorizontal:12,paddingVertical:11},selectPlaceholder:{color:o.color.textSecondary,fontSize:o.font.body},search:{flexDirection:"row",alignItems:"center",gap:6,paddingHorizontal:12,paddingVertical:6,borderTopWidth:I.hairlineWidth,borderTopColor:o.color.separator},searchInput:{flex:1,color:o.color.text,fontSize:o.font.caption,paddingVertical:4},sliderRow:{flexDirection:"row",alignItems:"center",gap:8},stepBtn:{width:28,height:28,borderRadius:14,backgroundColor:o.color.fill,alignItems:"center",justifyContent:"center"},stepText:{color:o.color.text,fontSize:16,fontWeight:"600"},trackHit:{flex:1,height:36,justifyContent:"center"},track:{height:6,borderRadius:3,backgroundColor:o.color.fillTertiary,justifyContent:"center"},fill:{height:6,borderRadius:3,backgroundColor:o.color.accent},thumb:{position:"absolute",width:20,height:20,borderRadius:10,backgroundColor:o.color.knob,marginLeft:-10,shadowColor:"#000",shadowOpacity:.35,shadowRadius:3,shadowOffset:{width:0,height:1}},sliderValue:{width:64,textAlign:"right",color:o.color.text,fontSize:o.font.caption,fontWeight:"600"},inputRow:{flexDirection:"row",alignItems:"center",gap:8,paddingBottom:12},input:{flex:1,height:38,paddingHorizontal:12,borderRadius:o.radius.row,backgroundColor:o.color.surfaceElevated,color:o.color.text,fontSize:o.font.body},fieldRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:10,paddingVertical:6},fieldCol:{paddingVertical:6,gap:6},fieldLabel:{color:o.color.textSecondary,fontSize:o.font.caption,width:110},fieldInput:{flex:1,height:34,paddingHorizontal:10,borderRadius:o.radius.segment,backgroundColor:o.color.surfaceElevated,color:o.color.text,fontSize:o.font.caption},confirmTitle:{color:o.color.text,fontSize:o.font.row,fontWeight:"600",paddingHorizontal:12,paddingTop:12},confirmChange:{color:o.color.text,fontSize:o.font.body,lineHeight:19,paddingHorizontal:12,paddingTop:6,paddingBottom:6},consequence:{color:o.color.warning,fontSize:o.font.caption,paddingHorizontal:12},claim:{gap:6,paddingHorizontal:2},claimText:{color:o.color.text,fontSize:o.font.row,lineHeight:22},cite:{color:o.color.accent,fontSize:o.font.caption,fontWeight:"700"},verified:{color:o.color.accent,fontSize:o.font.caption,fontWeight:"600"},citeBox:{padding:10,borderRadius:o.radius.row,backgroundColor:o.color.surface,borderWidth:I.hairlineWidth,borderColor:o.color.border,gap:2},citeLabel:{color:o.color.text,fontSize:o.font.caption,fontWeight:"600"},citeDetail:{color:o.color.textSecondary,fontSize:o.font.caption,fontFamily:H.OS==="web"?P:"Menlo"},verifyBtn:{alignSelf:"flex-start",flexDirection:"row",alignItems:"center",gap:5,paddingHorizontal:10,paddingVertical:5,borderRadius:o.radius.chip,backgroundColor:o.color.accentSoft},verifyText:{color:o.color.accent,fontSize:o.font.micro,fontWeight:"700"},detailsBody:{color:o.color.textSecondary,fontSize:o.font.caption,lineHeight:18,paddingHorizontal:12,paddingBottom:12},tabsWrap:{gap:8},tabBar:{flexDirection:"row",gap:6},tab:{paddingHorizontal:12,paddingVertical:6,borderRadius:o.radius.badge,backgroundColor:o.color.fill},tabOn:{backgroundColor:o.color.accentSoft},tabText:{color:o.color.textSecondary,fontSize:o.font.caption,fontWeight:"600"},tabTextOn:{color:o.color.accent},tlRow:{flexDirection:"row",gap:10,paddingHorizontal:12,paddingVertical:4,alignItems:"flex-start"},tlAt:{width:46,color:o.color.textTertiary,fontSize:o.font.micro,fontFamily:H.OS==="web"?P:"Menlo",paddingTop:3},tlLine:{width:12,alignItems:"center"},tlDot:{width:8,height:8,borderRadius:4,marginTop:5},tlStem:{width:1,flex:1,minHeight:18,backgroundColor:o.color.separator,marginTop:2},tlSource:{color:o.color.textTertiary,fontSize:9,fontWeight:"700",letterSpacing:.6},sparkHead:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",paddingHorizontal:12,paddingTop:8},sparkCurrent:{color:o.color.text,fontSize:22,fontWeight:"700"},sparkUnit:{color:o.color.textSecondary,fontSize:o.font.caption,fontWeight:"500"},spark:{flexDirection:"row",alignItems:"flex-end",gap:2,paddingHorizontal:12,paddingTop:6,paddingBottom:6},sparkBar:{flex:1,borderRadius:1},live:{flexDirection:"row",alignItems:"center",gap:5,paddingHorizontal:8,paddingVertical:3,borderRadius:o.radius.badge,backgroundColor:o.color.dangerSoft},liveDot:{width:6,height:6,borderRadius:3,backgroundColor:o.color.danger},liveText:{color:o.color.danger,fontSize:9,fontWeight:"800",letterSpacing:.8},cmpHead:{flexDirection:"row",paddingHorizontal:12,paddingVertical:6,backgroundColor:o.color.fill,gap:8},cmpCol:{flex:1,color:o.color.textTertiary,fontSize:o.font.micro,fontWeight:"700",letterSpacing:.6,textTransform:"uppercase"},cmpRow:{flexDirection:"row",paddingHorizontal:12,paddingVertical:8,gap:8,alignItems:"flex-start"},cmpDiff:{backgroundColor:o.color.fill},cmpKey:{width:90,color:o.color.textSecondary,fontSize:o.font.caption},cmpVal:{flex:1,color:o.color.text,fontSize:o.font.caption,fontFamily:H.OS==="web"?P:"Menlo"},cardImage:{width:"100%",height:140,backgroundColor:o.color.fill},cardText:{paddingHorizontal:12,paddingTop:10,paddingBottom:4,gap:4},cardTitle:{color:o.color.text,fontSize:o.font.row,fontWeight:"600"},fact:{flexDirection:"row",justifyContent:"space-between",gap:8},factLabel:{color:o.color.textSecondary,fontSize:o.font.caption},factValue:{color:o.color.text,fontSize:o.font.caption,fontWeight:"500"},carouselTitle:{color:o.color.text,fontSize:o.font.caption,fontWeight:"600",paddingBottom:8},carousel:{gap:8,paddingRight:8},carouselCard:{width:224,borderRadius:o.radius.card,borderWidth:I.hairlineWidth,borderColor:o.color.border,backgroundColor:o.color.surface,overflow:"hidden"},shot:{width:"100%",height:220,backgroundColor:o.color.fill},shotBig:{height:520},progress:{height:4,backgroundColor:o.color.fill,marginHorizontal:12,marginTop:6,marginBottom:8,borderRadius:2,overflow:"hidden"},progressFill:{height:4,backgroundColor:o.color.accent},step:{flexDirection:"row",alignItems:"center",gap:10,paddingHorizontal:12,paddingVertical:9},stepMark:{width:20,height:20,borderRadius:10,backgroundColor:o.color.fillTertiary,alignItems:"center",justifyContent:"center"},stepDone:{backgroundColor:o.color.accent},stepFailed:{backgroundColor:o.color.danger},stepRunning:{backgroundColor:o.color.info},stepNum:{color:o.color.text,fontSize:o.font.micro,fontWeight:"700"},checkpoint:{alignSelf:"flex-start",flexDirection:"row",alignItems:"center",gap:8,paddingHorizontal:12,paddingVertical:7,borderRadius:o.radius.chip,backgroundColor:o.color.fill},cpFlag:{width:8,height:8,borderRadius:2,backgroundColor:o.color.info},cpText:{color:o.color.textSecondary,fontSize:o.font.caption},cpRestore:{color:o.color.accent,fontSize:o.font.caption,fontWeight:"600"},taskHead:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:8,paddingHorizontal:12,paddingTop:12},banner:{flexDirection:"row",alignItems:"center",gap:8,paddingLeft:12,paddingRight:4,paddingVertical:4,borderRadius:o.radius.row,borderWidth:I.hairlineWidth},bannerText:{flex:1,fontSize:o.font.caption,fontWeight:"600"},fileIcon:{width:40,height:40,borderRadius:8,backgroundColor:o.color.fill,alignItems:"center",justifyContent:"center"},fileExt:{color:o.color.textSecondary,fontSize:10,fontWeight:"800"}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";export const SEARCH_FROM=8;export const SEARCH_CAP=8;export function selectView(e,o,n){const t=n!==false&&e.length>=SEARCH_FROM;const r=t?o.trim().toLowerCase():"";const l=r?e.filter(i=>i.label.toLowerCase().includes(r)):e;const s=t?l.slice(0,SEARCH_CAP):l;return{showSearch:t,items:s,hidden:l.length-s.length}}export function multiAnswer(e,o){const n=e.filter(t=>o.includes(t.id));return{optionIds:n.map(t=>t.id),labels:n.map(t=>t.label)}}export function shownFormValues(e,o){if(e?.kind==="form")return e.values;if(e?.kind==="proposal"&&e.approved&&e.values)return e.values;return o}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{describe as r,expect as t,it as s}from"vitest";import{multiAnswer as d,selectView as l,shownFormValues as i}from"./choiceLogic";const n=(e,o=a=>`Option ${a}`)=>Array.from({length:e},(a,h)=>({id:`o${h}`,label:o(h)}));r("selectView",()=>{s("a short list shows every option and no search",()=>{const e=l(n(5),"");t(e.showSearch).toBe(false);t(e.items).toHaveLength(5);t(e.hidden).toBe(0)});s("searchable:false with a long list shows ALL options, not 8",()=>{const e=l(n(20),"",false);t(e.showSearch).toBe(false);t(e.items).toHaveLength(20);t(e.hidden).toBe(0)});s("searchable:false ignores a stale query",()=>{t(l(n(20),"zzz",false).items).toHaveLength(20)});s("a long searchable list caps at 8 and counts the rest",()=>{const e=l(n(20),"");t(e.showSearch).toBe(true);t(e.items.map(o=>o.id)).toEqual(n(8).map(o=>o.id));t(e.hidden).toBe(12)});s("the 'more' count only counts options that match the query",()=>{const e=n(20,a=>a%2?`odd ${a}`:`even ${a}`);const o=l(e,"EVEN");t(o.items).toHaveLength(8);t(o.items.every(a=>a.label.startsWith("even"))).toBe(true);t(o.hidden).toBe(2)});s("no 'more' count when every match fits",()=>{const e=n(20,a=>a<3?`pick ${a}`:`other ${a}`);const o=l(e,"pick");t(o.items).toHaveLength(3);t(o.hidden).toBe(0)});s("no matches shows nothing and counts nothing",()=>{const e=l(n(20),"nope");t(e.items).toHaveLength(0);t(e.hidden).toBe(0)})});r("multiAnswer",()=>{const e=[{id:"a",label:"Alpha"},{id:"b",label:"Bravo"},{id:"c",label:"Charlie"}];s("ids and labels are both in list order, whatever the tap order",()=>{t(d(e,["c","a"])).toEqual({optionIds:["a","c"],labels:["Alpha","Charlie"]})});s("drops ids that are not options so ids and labels pair up",()=>{t(d(e,["zz","b"])).toEqual({optionIds:["b"],labels:["Bravo"]})})});r("shownFormValues",()=>{const e={port:8080};s("unanswered shows local edits",()=>{t(i(void 0,e)).toBe(e)});s("a sent form shows the sent values, not reset local state",()=>{const o={port:9e3};t(i({blockId:"f",kind:"form",values:o},{})).toBe(o)});s("an applied proposal shows the applied values",()=>{const o={port:9e3};t(i({blockId:"p",kind:"proposal",approved:true,values:o},e)).toBe(o)});s("a declined proposal keeps local values (nothing was applied)",()=>{t(i({blockId:"p",kind:"proposal",approved:false},e)).toBe(e)})});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
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 Ke}from"./ApprovalCard";import{ChangesBar as Me,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:K,approve:te,retry:oe,answer:M,runAction:E,editQueued:se,removeQueued:ne,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 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 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 s=await t.revertAll(Re);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 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 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 ke=a(()=>{const t=l.getLedger();const s=t?.list().filter(r=>!r.undoneAt&&He(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 K();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,K]);const Se=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=Ee(l.getLedger());const U=T(()=>je(V),[V]);const ve=T(()=>({answer:M,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}),[M,E,b,e.answers,e.actionState,e.busy,U]);const we=!e.busy&&n?.role==="assistant"&&!!n.error;const _=!e.busy&&n?.role==="assistant"&&n.stopped&&!n.error?n.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:n?.role==="assistant"&&n.streaming?n.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,
|
|
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,K]);const Se=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=Ee(l.getLedger());const U=T(()=>je(V),[V]);const ve=T(()=>({answer:M,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}),[M,E,b,e.answers,e.actionState,e.busy,U]);const we=!e.busy&&n?.role==="assistant"&&!!n.error;const _=!e.busy&&n?.role==="assistant"&&n.stopped&&!n.error?n.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:n?.role==="assistant"&&n.streaming?n.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,{avoidKeyboard:false,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(Me,{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:se,onRemove:ne}),e.pending&&o(Ke,{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{monoFont as y}from"@buoy-gg/shared-ui";import{Platform as b}from"react-native";import{Pressable as f,StyleSheet as c,Text as r,View as l}from"react-native";import{ChevronRight as m,night as e,NightSectionLabel as w}from"@buoy-gg/shared-ui";import{pickSuggestions as x}from"../store/suggestions";import{jsx as t,jsxs as n}from"react/jsx-runtime";const h=typeof __DEV__!=="undefined"&&!__DEV__;export function EmptyState({configured:i,onPick:s,availableToolIds:a=[],readOnly:d=false}){const u=x({availableToolIds:a,readOnly:d,isRelease:h});if(!i){return n(l,{style:o.wrap,testID:"buoy-ask-buoy-unconfigured",children:[t(r,{style:o.title,children:"Not set up yet"}),n(r,{style:o.body,children:["Ask Buoy talks to your own company's AI endpoint. Nothing goes to Buoy. A developer needs to add the ",t(r,{style:o.code,children:"askBuoy"})," setting where the app sets up Buoy. It needs your team's endpoint and model."]})]})}return n(l,{style:o.wrap,testID:"buoy-ask-buoy-empty",children:[t(r,{style:o.title,children:"Ask about this app"}),t(r,{style:o.body,children:"Ask what the app is doing right now, or tell it to change something. Anything it changes shows up in a bar at the top, with what can be undone."}),t(r,{style:o.buildLine,testID:"buoy-ask-buoy-build-line",children:h?"Release build \u2014 reading, impersonation and navigation work; simulations (forcing server responses, driving the screen) need a dev build.":"Dev build \u2014 full simulation available."}),n(l,{style:o.group,children:[t(w,{children:"Try asking"}),t(l,{style:o.card,children:u.map((g,p)=>t(k,{text:g,onPick:s,last:p===u.length-1},g))})]})]})}function k({text:i,onPick:s,last:a}){return n(f,{testID:`buoy-ask-buoy-suggestion-${i.slice(0,12)}`,accessibilityRole:"button",onPress:()=>s(i),style:({pressed:d})=>[o.row,!a&&o.rowDivider,d&&o.pressed],children:[t(r,{style:o.rowText,children:i}),t(m,{size:14,color:e.color.textTertiary})]})}const o=c.create({wrap:{flex:1,justifyContent:"center",paddingHorizontal:e.gutter,paddingVertical:24,gap:8},title:{color:e.color.text,fontSize:22,fontWeight:"700",letterSpacing:-.3},body:{color:e.color.textSecondary,fontSize:e.font.body,lineHeight:20},code:{color:e.color.text,fontFamily:b.OS==="web"?y:"Menlo"},buildLine:{marginTop:8,color:e.color.textTertiary,fontSize:e.font.micro,lineHeight:15},group:{marginTop:e.groupGap-8},card:{borderRadius:e.radius.card,borderWidth:c.hairlineWidth,borderColor:e.color.border,backgroundColor:e.color.surface,overflow:"hidden"},row:{flexDirection:"row",alignItems:"center",gap:10,paddingHorizontal:e.rowPadH,paddingVertical:13},rowDivider:{borderBottomWidth:c.hairlineWidth,borderBottomColor:e.color.separator},rowText:{flex:1,color:e.color.text,fontSize:e.font.body,lineHeight:19},pressed:{backgroundColor:e.color.fill}});
|
|
1
|
+
"use strict";import{monoFont as y}from"@buoy-gg/shared-ui";import{Platform as b}from"react-native";import{Pressable as f,StyleSheet as c,Text as r,View as l}from"react-native";import{ChevronRight as m,night as e,NightSectionLabel as w}from"@buoy-gg/shared-ui";import{pickSuggestions as x}from"../store/suggestions";import{jsx as t,jsxs as n}from"react/jsx-runtime";const h=typeof __DEV__!=="undefined"&&!__DEV__;export function EmptyState({configured:i,onPick:s,availableToolIds:a=[],readOnly:d=false}){const u=x({availableToolIds:a,readOnly:d,isRelease:h});if(!i){return n(l,{style:o.wrap,testID:"buoy-ask-buoy-unconfigured",children:[t(r,{style:o.title,children:"Not set up yet"}),n(r,{style:o.body,children:["Ask Buoy talks to your own company's AI endpoint. Nothing goes to Buoy. A developer needs to add the ",t(r,{style:o.code,children:"askBuoy"})," setting. It goes where the app sets up Buoy. It needs your team's endpoint and model."]})]})}return n(l,{style:o.wrap,testID:"buoy-ask-buoy-empty",children:[t(r,{style:o.title,children:"Ask about this app"}),t(r,{style:o.body,children:"Ask what the app is doing right now, or tell it to change something. Anything it changes shows up in a bar at the top, with what can be undone."}),t(r,{style:o.buildLine,testID:"buoy-ask-buoy-build-line",children:h?"Release build \u2014 reading, impersonation and navigation work; simulations (forcing server responses, driving the screen) need a dev build.":"Dev build \u2014 full simulation available."}),n(l,{style:o.group,children:[t(w,{children:"Try asking"}),t(l,{style:o.card,children:u.map((g,p)=>t(k,{text:g,onPick:s,last:p===u.length-1},g))})]})]})}function k({text:i,onPick:s,last:a}){return n(f,{testID:`buoy-ask-buoy-suggestion-${i.slice(0,12)}`,accessibilityRole:"button",onPress:()=>s(i),style:({pressed:d})=>[o.row,!a&&o.rowDivider,d&&o.pressed],children:[t(r,{style:o.rowText,children:i}),t(m,{size:14,color:e.color.textTertiary})]})}const o=c.create({wrap:{flex:1,justifyContent:"center",paddingHorizontal:e.gutter,paddingVertical:24,gap:8},title:{color:e.color.text,fontSize:22,fontWeight:"700",letterSpacing:-.3},body:{color:e.color.textSecondary,fontSize:e.font.body,lineHeight:20},code:{color:e.color.text,fontFamily:b.OS==="web"?y:"Menlo"},buildLine:{marginTop:8,color:e.color.textTertiary,fontSize:e.font.micro,lineHeight:15},group:{marginTop:e.groupGap-8},card:{borderRadius:e.radius.card,borderWidth:c.hairlineWidth,borderColor:e.color.border,backgroundColor:e.color.surface,overflow:"hidden"},row:{flexDirection:"row",alignItems:"center",gap:10,paddingHorizontal:e.rowPadH,paddingVertical:13},rowDivider:{borderBottomWidth:c.hairlineWidth,borderBottomColor:e.color.separator},rowText:{flex:1,color:e.color.text,fontSize:e.font.body,lineHeight:19},pressed:{backgroundColor:e.color.fill}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{describe as r,expect as o,it as s,vi as i}from"vitest";i.mock("react",async e=>({...await e(),useState:t=>[typeof t==="function"?t():t,()=>{}],useRef:t=>({current:t}),useEffect:()=>{},useCallback:t=>t}));i.mock("../blocks/context",async e=>({...await e(),useBlockHandlers:()=>({answer:()=>{},runAction:()=>{},answered:{}})}));import{Choice as c}from"../blocks/AskBlocks";import{Composer as l}from"./Composer";import{EmptyState as u}from"./EmptyState";function n(e){if(Array.isArray(e))return e.flatMap(n);if(!e||typeof e!=="object")return[];const t=e;return[t,...n(t.props?.children)]}function a(e){if(typeof e==="string"||typeof e==="number")return String(e);if(Array.isArray(e))return e.map(a).join("");return e&&typeof e==="object"?a(e.props?.children):""}r("the setup message",()=>{s("names the setting, not a React component",()=>{const e=a(u({configured:false,onPick:()=>{}}));o(e).toContain("askBuoy");o(e).not.toContain("FloatingDevTools")})});r("the message box",()=>{s("has a name a screen reader can say",()=>{const e=n(l({onSend:()=>{},onStop:()=>{},busy:false})).find(t=>t.props?.testID==="buoy-ask-buoy-input");o(e?.props?.accessibilityLabel).toBe("Message for Ask Buoy")})});r("a choice card",()=>{const e={id:"ask-1",kind:"choice",question:"If you want the value of `e2e-after-local`, ask and I'll read it.",options:[{id:"yes",label:"Yes, go ahead"},{id:"no",label:"No thanks"}]};s("shows the question the model wrote",()=>{o(a(c({block:e}))).toContain("e2e-after-local")});s("leaves it out when the answer above already says it",()=>{const t=a(c({block:{...e,questionInAnswer:true}}));o(t).not.toContain("e2e-after-local");o(t).toContain("Yes, go ahead")})});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use strict";import{idFactory as c}from"@buoy-gg/agent-core";import{realNow as i}from"@buoy-gg/shared-ui";import{writeSavedLedger as k}from"./transcriptPersistence";const l=c("m");const f=400;function x(t){if(!t||Object.keys(t).length===0)return void 0;try{const e=JSON.stringify(t);return e.length>f?`${e.slice(0,f)}\u2026`:e}catch{return void 0}}const w=c("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 m=null;let h=null;const d=new Set;let p=null;let b=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){if(t===p)return;b?.();p=t;b=t?t.subscribe(()=>void k(t.list())):null},getLedger(){return p},holdSession(t,e){g={session:t,signature:e}},getHeldSession(){return g},setReporter(t){m=t},getReporter(){return m},setUsageReader(t){h=t},getUsageReader(){return h},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:w(),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(y=>y.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})`:e.reason==="network"?"Connection dropped \u2014 trying again":`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:x(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==="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 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:x(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"done":return e.stopReason==="realm-died"?{...r,reloadedApp:true}:r;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 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}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{persistentStorage as
|
|
1
|
+
"use strict";import{persistentStorage as i,devToolsStorageKeys as p}from"@buoy-gg/shared-ui";import{redactForWire as u}from"../sync/wire";export const TRANSCRIPT_KEY=`${p.base}_ask_buoy_transcript`;const l=1;const f=60;const g=256e3;const y="[not kept across a restart \u2014 read it again if you need it]";function s(t){return t?u(t):t}function h(t){const e=t.streaming===true;return{...t,text:s(t.text),streaming:false,reasoning:t.reasoning?.map(r=>({...r,text:s(r.text)})),toolCalls:t.toolCalls.map(r=>{const n=r.params?s(r.params):void 0;const{result:c,...a}=r;return r.status==="running"?{...a,params:n,status:"failed",summary:"Interrupted"}:{...a,params:n}}),error:t.error??(e&&!t.text?"This answer was interrupted \u2014 the app restarted mid-turn.":void 0)}}function m(t){return t.map(e=>{if(e.role==="tool-results"){return{...e,results:e.results.map(r=>({...r,content:y}))}}if(e.role==="assistant")return{...e,text:s(e.text)};return{...e,text:s(e.text)}})}export function fitToCaps(t){let e={...t,messages:t.messages.slice(-f),history:t.history.slice(-f*2)};let r=JSON.stringify(e);while(r.length>g&&e.messages.length>1){e={...e,messages:e.messages.slice(1),history:e.history.slice(Math.min(2,e.history.length))};r=JSON.stringify(e)}return e}function A(t){if(!t)return null;const e=t.params;if(!e)return null;let r;try{r=JSON.stringify(e)}catch{return null}if(u(r)!==r)return null;return{id:t.id,toolId:t.toolId,action:t.action,label:s(t.label),description:t.description?s(t.description):void 0,reason:s(t.reason),params:e,targetDigest:t.targetDigest}}function x(t){if(typeof t!=="object"||t===null)return false;const e=t;return typeof e.id==="string"&&typeof e.toolId==="string"&&typeof e.action==="string"&&typeof e.label==="string"&&typeof e.reason==="string"&&typeof e.params==="object"&&e.params!==null}function d(t){if(!t?.length)return void 0;const e=t.filter(r=>{try{const n=JSON.stringify(r);return u(n)===n}catch{return false}});return e.length?e:void 0}export function buildPayload(t){return fitToCaps({v:l,savedAt:Date.now(),messages:t.messages.map(h),history:m(t.history),answers:t.answers,usage:t.usage,pending:A(t.pending),ledger:d(t.ledger),queued:t.queued?.length?t.queued.map(e=>({id:e.id,text:s(e.text)})):void 0})}export function parsePayload(t){if(!t)return null;try{const e=JSON.parse(t);if(e?.v!==l||!Array.isArray(e.messages))return null;if(e.messages.length===0)return null;return{v:l,savedAt:typeof e.savedAt==="number"?e.savedAt:0,messages:e.messages,history:Array.isArray(e.history)?e.history:[],answers:e.answers??{},usage:e.usage??{input:0,output:0,requests:0},pending:x(e.pending)?e.pending:null,ledger:Array.isArray(e.ledger)?e.ledger.filter(r=>typeof r==="object"&&r!==null&&typeof r.id==="string"):void 0,queued:Array.isArray(e.queued)?e.queued.filter(r=>typeof r==="object"&&r!==null&&typeof r.id==="string"&&typeof r.text==="string"):void 0}}catch{return null}}export const RESUME_AFTER_RELOAD="The app reloaded. Please finish what you were doing.";export const RELOAD_NOTE='The app reloaded during my last step. Say "continue" to finish.';const S=2*6e4;export function afterReload(t,e){const r=t.messages[t.messages.length-1];if(r?.role!=="assistant"||!r.reloadedApp)return null;const n=[...t.messages].reverse().find(a=>a.role==="user");const c=e-t.savedAt<S;return c&&n?.text!==RESUME_AFTER_RELOAD?"resume":"note"}export async function readTranscript(){try{return parsePayload(await i.getItem(TRANSCRIPT_KEY))}catch{return null}}let o=0;export async function writeTranscript(t){o+=1;try{await i.setItem(TRANSCRIPT_KEY,JSON.stringify(t))}catch{}}export async function writeSavedLedger(t){const e=o;const r=await readTranscript();if(!r||e!==o)return;await writeTranscript(fitToCaps({...r,ledger:d(t)}))}export async function clearTranscript(){o+=1;try{await i.removeItem(TRANSCRIPT_KEY)}catch{}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{describe as d,expect as t,it as
|
|
1
|
+
"use strict";import{describe as d,expect as t,it as n}from"vitest";import{afterReload as c,buildPayload as u,fitToCaps as h,parsePayload as l,RESUME_AFTER_RELOAD as p}from"./transcriptPersistence";import{askBuoyStore as g}from"./askBuoyStore";const r=(e={})=>({id:"m1",role:"assistant",text:"ok",toolCalls:[],blocks:[],...e});const i=(e,o=[])=>u({messages:e,history:o,answers:{},usage:{input:0,output:0,requests:0}});d("what reaches the disk",()=>{n("never writes a tool result",()=>{const e=[{role:"user",text:"what's in the bag?"},{role:"assistant",text:"",toolCalls:[{id:"c1",name:"buoy_zustand",input:{}}]},{role:"tool-results",results:[{toolCallId:"c1",content:'{"lines":[{"card":"4111111111111111"}]}'}]},{role:"assistant",text:"Five items."}];const o=i([r()],e);const s=JSON.stringify(o);t(s).not.toContain("4111111111111111");t(o.history.map(a=>a.role)).toEqual(["user","assistant","tool-results","assistant"])});n("never writes a step's result, even when it writes the step",()=>{const e=i([r({toolCalls:[{id:"c1",toolId:"zustand",action:"getStoreState",label:"Read the bag",status:"ok",summary:"Done",params:'{"storeName":"bag"}',result:'{"card":"4111111111111111"}'}]})]);t(JSON.stringify(e)).not.toContain("4111111111111111");t(e.messages[0].toolCalls[0].result).toBeUndefined();t(e.messages[0].toolCalls[0].action).toBe("getStoreState")});n("scrubs credential shapes out of what the agent said",()=>{const e=i([r({text:"The header was Bearer abcdefghijklmnop123456"})]);t(e.messages[0].text).not.toContain("abcdefghijklmnop123456")});n("caps a long session instead of growing a file forever",()=>{const e=Array.from({length:400},(s,a)=>r({id:`m${a}`,text:`answer ${a}`}));const o=i(e);t(o.messages.length).toBeLessThanOrEqual(60);t(o.messages[o.messages.length-1].text).toBe("answer 399")});n("drops from the oldest end until a heavy transcript fits",()=>{const e=Array.from({length:20},(s,a)=>r({id:`m${a}`,text:"x".repeat(4e4)}));const o=h({v:1,savedAt:0,messages:e,history:[],answers:{},usage:{input:0,output:0,requests:0}});t(JSON.stringify(o).length).toBeLessThanOrEqual(256e3);t(o.messages.length).toBeGreaterThan(0)})});d("what comes back",()=>{n("marks a turn the app died in as interrupted rather than blank",()=>{const e=i([r({text:"",streaming:true})]);t(e.messages[0].streaming).toBe(false);t(e.messages[0].error).toMatch(/interrupted/i)});n("never restores a tool call still claiming to run",()=>{const e=i([r({streaming:true,toolCalls:[{id:"c1",toolId:"storage",action:"set",label:"Write a key",status:"running"}]})]);t(e.messages[0].toolCalls[0].status).toBe("failed");t(e.messages[0].toolCalls[0].summary).toBe("Interrupted")});n("keeps an answer that already finished exactly as it was",()=>{const e=i([r({text:"Five items.",streaming:false})]);t(e.messages[0].error).toBeUndefined();t(e.messages[0].text).toBe("Five items.")});n("treats anything it cannot trust as no transcript at all",()=>{t(l(null)).toBeNull();t(l("not json")).toBeNull();t(l(JSON.stringify({v:0,messages:[r()]}))).toBeNull();t(l(JSON.stringify({v:1,messages:[]}))).toBeNull()});n("round-trips a real conversation",()=>{const e=i([r({role:"user",text:"hi"}),r({id:"m2",text:"hello"})]);const o=l(JSON.stringify(e));t(o?.messages.map(s=>s.text)).toEqual(["hi","hello"])})});d("an unanswered approval",()=>{const e={id:"c1",toolId:"zustand",action:"setState",label:"Update the Pok\xE9 Mart bag store",description:"Change a live zustand store.",reason:"This will change the app's state.",params:{storeName:"bag",data:{lines:[]}},resolve:()=>{}};const o=(s={})=>u({messages:[r()],history:[],answers:{},usage:{input:0,output:0,requests:0},pending:{...e,...s}});n("survives the round trip with the exact call it asked about",()=>{const s=l(JSON.stringify(o()));t(s?.pending).toMatchObject({toolId:"zustand",action:"setState",label:"Update the Pok\xE9 Mart bag store",params:{storeName:"bag",data:{lines:[]}}})});n("is saved again after it has already been restored once",()=>{t(o({restored:true}).pending).toBeTruthy()});n("is dropped rather than scrubbed when the call carries a credential",()=>{const s=o({params:{key:"auth",value:"Bearer abcdefghijklmnop123456"}});t(s.pending).toBeNull();t(JSON.stringify(s)).not.toContain("abcdefghijklmnop123456")});n("reads back as absent from a transcript written before it existed",()=>{const s=i([r()]);t(l(JSON.stringify(s))?.pending).toBeNull()});n("refuses a malformed one rather than restoring half a question",()=>{const s=JSON.stringify({...i([r()]),pending:{toolId:"zustand"}});t(l(s)?.pending).toBeNull()})});d("the effect ledger",()=>{const e={id:"fx-1-1",at:1,toolId:"storage",action:"async.setItem",effect:{kind:"storage-write",toolId:"storage",action:"async.setItem",label:"Save @qa/key",key:"@qa/key",had:true,previous:"old value"}};const o=s=>u({messages:[r()],history:[],answers:{},usage:{input:0,output:0,requests:0},ledger:s});n("survives the round trip with the prior value an undo needs",()=>{const s=l(JSON.stringify(o([e])));t(s?.ledger).toHaveLength(1);t(s?.ledger?.[0]).toMatchObject({toolId:"storage",action:"async.setItem"})});n("drops an entry holding a credential rather than rewriting it",()=>{const s={...e,effect:{...e.effect,previous:"Bearer abcdefghijklmnop123456"}};const a=o([s]);t(a.ledger).toBeUndefined();t(JSON.stringify(a)).not.toContain("abcdefghijklmnop123456")});n("reads back as absent from a transcript written before it existed",()=>{t(l(JSON.stringify(i([r()])))?.ledger).toBeUndefined()})});d("a turn that reloaded the app",()=>{const e=a=>r({id:`u-${a}`,role:"user",text:a});const o=r({id:"a1",text:"The app is reloading.",reloadedApp:true});const s=a=>l(JSON.stringify(i(a)));n("is marked when the engine stops for a reload",()=>{g.reset();const a=g.startAssistant();g.applyEvent(a,{type:"done",stopReason:"realm-died"});t(g.getState().messages[0].reloadedApp).toBe(true)});n("picks the task back up once, right after the reload",()=>{const a=s([e("make /v0 fail, reload, then undo"),o]);t(c(a,a.savedAt+1e3)).toBe("resume")});n("leaves a note instead of looping when the picked-up task reloads again",()=>{const a=s([e("make /v0 fail, reload, then undo"),o,e(p),o]);t(c(a,a.savedAt+1e3)).toBe("note")});n("leaves a note for a chat opened long after the reload",()=>{const a=s([e("reload the app"),o]);t(c(a,a.savedAt+10*6e4)).toBe("note")});n("does nothing for a turn that ended any other way",()=>{const a=s([e("hi"),r({text:"Hello."})]);t(c(a,a.savedAt)).toBeNull()})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{getUseIsPro as
|
|
1
|
+
"use strict";import{getUseIsPro as ye,hasAccountAccess as v,subscribeAccountAccess as Ae}from"@buoy-gg/shared-ui";import{useCallback as l,useEffect as p,useMemo as Y,useRef as h,useState as Z,useSyncExternalStore as be}from"react";import{Platform as ee}from"react-native";import{dispatchToolAction as te,listSnapshotToolIds as ke,listToolActions as we,readToolSnapshot as ve,waitForToolRegistry as oe}from"@buoy-gg/shared-ui";import{answerToText as se,CATALOG as L,createAskBuoySession as Se,readTargetDigest as Te,runGatedAction as re,SNAPSHOT_ACTION as ne}from"@buoy-gg/agent-core";import{askBuoyStore as t,queuePauseReason as Re,turnPhaseOf as Oe}from"./askBuoyStore";import{clearConversation as Ne,resetConversation as Ee}from"./resetConversation";import{afterReload as Be,buildPayload as Ie,readTranscript as _e,RELOAD_NOTE as qe,RESUME_AFTER_RELOAD as Ce,writeTranscript as xe}from"./transcriptPersistence";import{useAskBuoySettings as De}from"./useAskBuoySettings";import{HOSTED_CLAUDE_MODEL as Pe,pickHostedModel as $e}from"../hostedModel";import{useHostedModel as Le}from"./hostedModelSetting";const T=typeof __DEV__!=="undefined"&&!__DEV__;function Me(c){for(const i of t.getState().messages){if(!i.restored)continue;for(const y of i.blocks){const g=y.block;if(g?.id!==c)continue;const m=g.question??g.label??g.title;return typeof m==="string"&&m.trim()?m.trim():void 0}}return void 0}function ie(){try{const c=we();for(const i of ke()){if(!c[i])c[i]=[];if(!c[i].includes(ne)){c[i]=[ne,...c[i]]}}return c}catch{return{}}}const Ue=600;export function useAskBuoyState(){return be(t.subscribe,t.getState,t.getState)}export function useAskBuoy(c){const i=useAskBuoyState();const y=Le();const g=Y(()=>$e(c,y,i.claudeOut),[c,y,i.claudeOut]);const m=h(g);if(i.messages.length===0||!m.current)m.current=g;const o=m.current;const M=h(c);M.current=c;const U=!!c?.hostedModels?.claude;const F=i.messages.length===0;p(()=>{const e=M.current?.readUsage;if(!U||y!=="claude"||!F||!e)return;let s=true;e().then(r=>{if(s&&r.claude)t.setClaudeOut(!r.claude.available)}).catch(()=>{});return()=>{s=false}},[U,y,F]);const A=ye()();const S=h(A);S.current=A;const R=l((...e)=>{if(!v()||!S.current)return Promise.reject(new Error("Ask Buoy requires Pro access."));return te(...e)},[]);const u=h(null);const j=h(false);const[H,K]=Z(false);const b=h(null);const Q=h(o);Q.current=o;const O=!!o?.endpoint&&!!o?.model;const N=o?.persistTranscript!==false;const{bypassApprovals:E,readOnly:B}=De();const x=h(E);x.current=E;const I=h(B);I.current=B;const _=l(()=>I.current?{...o?.policy??{},readOnly:true}:o?.policy??{},[o]);const[D,ae]=Z(0);p(()=>{let e=false;void oe().then(()=>{if(!e)ae(s=>s+1)});return()=>{e=true}},[]);const k=Y(()=>ie(),[D]);const ce=Object.keys(k).sort().join(",");p(()=>{t.setConfigured(O);t.setMeta({model:o?.model,protocol:o?.protocol??(o?"anthropic":void 0),appName:o?.appName,platform:ee.OS,build:T?"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(T&&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(k);r.session.setApprovalBypass(x.current);r.session.setReadOnly(I.current);t.setLedger(r.session.ledger);return}const a=Se({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:n=>{const C=(n==="fallback"?o.hostedFallback?.model:o.model)===Pe;t.setMeta({model:n==="fallback"?o.hostedFallback?.model:o.model});if(n==="fallback"){if(!C)t.setClaudeOut(true);t.setNotice(C?"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?[...L,...o.tools]:void 0,dispatch:R,readSnapshot:ve,availableActions:k,policy:o.policy,context:o.context,isRelease:T,inApp:true,appName:o.appName,platform:ee.OS,requestApproval:n=>t.askApproval(n)});u.current=a;a.setApprovalBypass(x.current);a.setReadOnly(I.current);t.holdSession(a,s);t.setLedger(a.ledger);let d=false;void oe().then(n=>{if(n&&!d)void a.prime()});if(N&&!j.current){j.current=true;void _e().then(n=>{if(!n)return;const C=t.hydrate({messages:n.messages,answers:n.answers,usage:n.usage,queued:n.queued});if(!C)return;u.current?.restore(n.history);if(n.ledger?.length)u.current?.ledger.restore(n.ledger);if(n.pending)t.restorePending(n.pending);const X=Be(n,Date.now());if(X==="resume")K(true);else if(X==="note")t.addNote(qe)})}return()=>{d=true}},[O,o,k,ce,N]);p(()=>{u.current?.setApprovalBypass(E)},[E]);p(()=>{u.current?.setReadOnly(B)},[B]);const P=i.messages;const V=i.answers;const G=i.usage;const W=i.busy;const $=i.pending;const z=i.queued;p(()=>{if(!N)return;if(P.length===0)return;const e=()=>{void xe(Ie({messages:P,history:u.current?.messages??[],answers:V,usage:G,pending:$,ledger:t.getLedger()?.list(),queued:z}))};if(!W||$){e();return}const s=setTimeout(e,Ue);return()=>clearTimeout(s)},[N,W,P,V,G,$,z]);const q=l(e=>{if(!v()||!S.current)return;const s=u.current;if(!s)return;t.beginTurn();const r=t.startAssistant();const a=new AbortController;b.current=a;s.setAvailableActions(ie());const d=Q.current?.onEvent;void(async()=>{try{for await(const n of s.send(e,a.signal)){t.applyEvent(r,n);if(d){try{d(n)}catch{}}}}catch(n){if(!a.signal.aborted){t.applyEvent(r,{type:"error",message:n instanceof Error?n.message:"Something went wrong talking to your AI endpoint."})}}finally{if(a.signal.aborted)t.markStopped(r);t.finishAssistant(r);b.current=null}})()},[]);const f=l(e=>{if(!v()||!S.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);q(s)},[q]);p(()=>{if(!H||!A||!v()||!u.current||D===0)return;K(false);f(Ce)},[H,A,D,f]);const le=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;q(r.text)},[q]);const w=l(()=>{t.requestStop();if(t.getState().pending)t.answerApproval(false);b.current?.abort();b.current=null},[]);p(()=>{const e=Ae(()=>{if(!v())w()});return()=>{e();w()}},[w]);p(()=>{if(!A)w()},[A,w]);p(()=>{if(i.queued.length===0)return;if(Oe(i)!=="idle")return;if(Re(i)!==null)return;const e=t.shiftQueued();if(e)f(e.text)},[i,f]);const ue=l(e=>t.takeQueuedToDraft(e),[]);const de=l(e=>t.removeQueued(e),[]);const pe=l(()=>{b.current?.abort();b.current=null;Ne()},[]);const fe=l(()=>Ee({dispatch:te,undoFirst:true}),[]);const J=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?[...L,...o.tools]:void 0;if(e.targetDigest){const d=await Te({toolId:e.toolId,action:e.action,params:e.params??{},dispatch:R,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 re({toolId:e.toolId,action:e.action,params:e.params??{},dispatch:R,ledger:s,policy:_(),isRelease:T,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,_]);const he=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 J(s)},[J]);const ge=l(e=>{t.recordAnswer(e);const s=Me(e.blockId);f(s?`${s} \u2014 ${se(e)}`:se(e))},[f]);const me=l(async(e,s)=>{if(!v()||!S.current){t.setNotice("Ask Buoy requires Pro access.");return}if(e.send){f(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 re({toolId:e.dispatch.toolId,action:e.dispatch.action,params:e.dispatch.params??{},dispatch:R,ledger:a,policy:_(),isRelease:T,catalog:o?.tools?.length?[...L,...o.tools]:void 0});if(d.ok){t.setActionState(r,"done")}else{t.setActionState(r,"failed");t.setNotice(`${e.label} failed \u2014 ${d.error}`)}},[f,o,_]);return{state:i,send:f,stop:w,reset:pe,undoThenReset:fe,approve:he,retry:le,answer:ge,runAction:me,editQueued:ue,removeQueued:de,configured:O,toolCount:Object.keys(k).length,availableToolIds:Object.keys(k)}}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure rules behind the Select, MultiSelect, Form and Proposal blocks.
|
|
3
|
+
* Kept out of ExtraBlocks.tsx so they can be tested without rendering.
|
|
4
|
+
*/
|
|
5
|
+
import type { BlockAnswer, ChoiceOption } from "@buoy-gg/agent-core";
|
|
6
|
+
/** Options needed before a search field earns its space. */
|
|
7
|
+
export declare const SEARCH_FROM = 8;
|
|
8
|
+
/** Rows a searchable Select shows at once; typing narrows the rest. */
|
|
9
|
+
export declare const SEARCH_CAP = 8;
|
|
10
|
+
export interface SelectView {
|
|
11
|
+
showSearch: boolean;
|
|
12
|
+
/** The rows to draw. */
|
|
13
|
+
items: ChoiceOption[];
|
|
14
|
+
/** Matching rows left out by the cap — the "N more" count. */
|
|
15
|
+
hidden: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* What a Select draws for the current query.
|
|
19
|
+
*
|
|
20
|
+
* The cap only applies when there is a search field to narrow the list with:
|
|
21
|
+
* capping a list the reader cannot search would make the rest unreachable.
|
|
22
|
+
* `hidden` counts only options that MATCH the query, so "N more — keep
|
|
23
|
+
* typing" never promises rows that typing would not find.
|
|
24
|
+
*/
|
|
25
|
+
export declare function selectView(options: ChoiceOption[], query: string, searchable?: boolean): SelectView;
|
|
26
|
+
/**
|
|
27
|
+
* The multi-select answer, ids and labels both in LIST order (the order the
|
|
28
|
+
* reader sees), whatever order the boxes were ticked in. Ids that are not
|
|
29
|
+
* options are dropped so the two arrays always pair up.
|
|
30
|
+
*/
|
|
31
|
+
export declare function multiAnswer(options: ChoiceOption[], picked: string[]): {
|
|
32
|
+
optionIds: string[];
|
|
33
|
+
labels: string[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The values a Form or Proposal shows. Once answered, the values that were
|
|
37
|
+
* SENT win over local state — local state resets when the block remounts, and
|
|
38
|
+
* an "Applied with these values" row must show the values that were applied.
|
|
39
|
+
*/
|
|
40
|
+
export declare function shownFormValues(answer: BlockAnswer | undefined, local: Record<string, unknown>): Record<string, unknown>;
|
|
41
|
+
//# sourceMappingURL=choiceLogic.d.ts.map
|
|
@@ -131,6 +131,12 @@ export interface ChatMessage {
|
|
|
131
131
|
* to do — with a Swift exception underneath it.
|
|
132
132
|
*/
|
|
133
133
|
stopped?: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* The turn ended because one of its steps reloaded the app, which took this
|
|
136
|
+
* sheet down with it. Read after the reload to pick the task back up — see
|
|
137
|
+
* `afterReload` in transcriptPersistence.
|
|
138
|
+
*/
|
|
139
|
+
reloadedApp?: boolean;
|
|
134
140
|
/**
|
|
135
141
|
* This bubble came back from disk.
|
|
136
142
|
*
|
|
@@ -73,6 +73,20 @@ export declare function buildPayload(input: {
|
|
|
73
73
|
}): StoredTranscript;
|
|
74
74
|
/** Anything unparseable, or from an older shape, is treated as "no transcript". */
|
|
75
75
|
export declare function parsePayload(raw: string | null): StoredTranscript | null;
|
|
76
|
+
/** Sent for the user when the app reloaded in the middle of a task. */
|
|
77
|
+
export declare const RESUME_AFTER_RELOAD = "The app reloaded. Please finish what you were doing.";
|
|
78
|
+
/** Shown when the task is not picked back up on its own. */
|
|
79
|
+
export declare const RELOAD_NOTE = "The app reloaded during my last step. Say \"continue\" to finish.";
|
|
80
|
+
/**
|
|
81
|
+
* What to do with a chat whose last turn reloaded the app (`reloadedApp`).
|
|
82
|
+
*
|
|
83
|
+
* "resume" sends RESUME_AFTER_RELOAD once, so the task the user asked for
|
|
84
|
+
* finishes, including undoing what it changed. It is the one thing that is
|
|
85
|
+
* sent with no tap after a restart, and only because the reload was a step of
|
|
86
|
+
* that task. Never twice in a row (a task that reloads on every step would
|
|
87
|
+
* loop) and never for a chat opened long after: those get RELOAD_NOTE.
|
|
88
|
+
*/
|
|
89
|
+
export declare function afterReload(saved: StoredTranscript, now: number): "resume" | "note" | null;
|
|
76
90
|
export declare function readTranscript(): Promise<StoredTranscript | null>;
|
|
77
91
|
export declare function writeTranscript(payload: StoredTranscript): Promise<void>;
|
|
78
92
|
/**
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What the sync adapter is allowed to put on the broker wire.
|
|
3
3
|
*
|
|
4
|
-
* The broker
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* The broker binds every interface and forwards to every connected dashboard.
|
|
5
|
+
* A dashboard must present the broker's `dashboardToken`, and a browser page
|
|
6
|
+
* from a non-local origin needs the owner's OK. A native app needs neither: it
|
|
7
|
+
* can join as a device from anywhere on the network. The broker drops Buoy
|
|
8
|
+
* key names from device metadata (`redactAccountKeys`). Even so, a chat
|
|
9
|
+
* transcript is not a safe thing to send unfiltered: the agent reads storage
|
|
10
|
+
* values, network response bodies and user records, and any of it can end up
|
|
11
|
+
* quoted in an answer.
|
|
10
12
|
*
|
|
11
13
|
* So text crossing the wire is scrubbed for the shapes that are unambiguously
|
|
12
14
|
* credentials. This is a reduction in blast radius, not a guarantee — the real
|