Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 33 additions & 20 deletions .github/THREAT_MODEL.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions .github/workflows/deploy-bunny-js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
run: npm run build

- name: Deploy searchapi twin to Bunny Edge Scripting
uses: BunnyWay/actions/deploy-script@main
uses: BunnyWay/actions/deploy-script@931a74b9c45ea9f9f01435fbbf1649830e67d3b3 # 2026-10-12 main; a mutable ref here is code-exec with the deploy keys — bump deliberately
with:
# BUNNYJS_SCRIPT_ID and BUNNYJS_DEPLOY_KEY are repo secrets; both come from the
# bunny dashboard: Script -> Deployments -> Settings. The deploy key authorizes
Expand All @@ -78,7 +78,7 @@ jobs:
file: "bunny_js/dist/searchapi.js"

- name: Deploy llm twin to Bunny Edge Scripting
uses: BunnyWay/actions/deploy-script@main
uses: BunnyWay/actions/deploy-script@931a74b9c45ea9f9f01435fbbf1649830e67d3b3 # 2026-10-12 main; a mutable ref here is code-exec with the deploy keys — bump deliberately
with:
# Same per-script secret pair pattern as searchapi, its own script id and its
# own deploy key — narrower blast radius than an account-wide API key.
Expand All @@ -87,7 +87,7 @@ jobs:
file: "bunny_js/dist/llm.js"

- name: Deploy mcp twin to Bunny Edge Scripting
uses: BunnyWay/actions/deploy-script@main
uses: BunnyWay/actions/deploy-script@931a74b9c45ea9f9f01435fbbf1649830e67d3b3 # 2026-10-12 main; a mutable ref here is code-exec with the deploy keys — bump deliberately
with:
# The mcp bundle embeds the llm and search tools (see mcp_tools.ts), so this one
# script serves both web_search and chat_completion.
Expand Down
16 changes: 16 additions & 0 deletions website/components/AssistantChat.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,22 @@ export const researchNotes = css({
"& li": { marginBottom: "xs" },
});

/**
* The in-stream rating row: five muted stars (the FeedbackWidget) plus — only when a web tool
* served this chat — the target toggle, sitting directly under the last completed answer like
* that answer's own cost line. Wraps on narrow screens rather than overflowing; muted chrome,
* because the answer is the content and the rating is an offer, not a call to action.
*/
export const rateBlock = css({
display: "flex",
flexWrap: "wrap",
alignItems: "center",
gap: "sm",
marginTop: "xs",
fontSize: "xs",
color: "textMuted",
});

export const loadingBubble = css({
// The 80% cap sits on `loadingColumn`, which holds this bubble and the research status row.
maxWidth: "100%",
Expand Down
32 changes: 30 additions & 2 deletions website/components/AssistantChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ import { button } from "../styled-system/recipes";
import { PageHeader } from "./PageHeader";
import { FretchenLogo } from "./FretchenLogo";
import { GetFundsModal } from "./GetFundsModal";
import { FeedbackWidget, FEEDBACK_TARGETS } from "./FeedbackWidget";
import { PaymentError, readChargedTotal } from "../utils/x402PaidFetch";

/**
Expand Down Expand Up @@ -492,6 +493,11 @@ export function AssistantChat() {
spent: bigint;
notes: ResearchNote[];
} | null>(null);
// Whether any turn of this chat has paid the search agent (ERC-8004 agent 97600) — both
// `search_web` and `fetch_url` buy from it (tools/search.ts, tools/webFetch.ts). Latched for
// the session, not per turn: the search agent is only rated once it actually served this
// chat, bundled into the same star pick as the assistent rather than offered as a choice.
const [webToolsUsed, setWebToolsUsed] = useState(false);
// Read by the loop before each hop. A ref, not state: the loop is already running inside an
// event handler and must see the press without waiting for a re-render. `stopPressed` is only the
// button's own feedback.
Expand Down Expand Up @@ -1267,8 +1273,14 @@ export function AssistantChat() {
shouldStop: () => stopRequestedRef.current,
onPhase: (phase) => {
setLoopPhase(phase);
if (phase.kind === "tool" && phase.name === searchWebTool.function.name) searches++;
if (phase.kind === "tool" && phase.name === fetchUrlTool.function.name) pages++;
if (phase.kind === "tool" && phase.name === searchWebTool.function.name) {
searches++;
setWebToolsUsed(true);
}
if (phase.kind === "tool" && phase.name === fetchUrlTool.function.name) {
pages++;
setWebToolsUsed(true);
}
if (phase.kind === "tool" && phase.name === noteFindingsTool.function.name) {
notes.push(...parseNotes(phase.args));
}
Expand Down Expand Up @@ -1607,6 +1619,22 @@ export function AssistantChat() {
))
)}

{/* In-stream rating, directly under the last completed answer — the same position
as that answer's own cost line, because the stars rate that answer. At rest it is
nothing but five muted glyphs; the disclosure appears only at the moment of
commitment (FeedbackWidget). One star pick rates every agent that served this
chat: the assistent always, the search agent once its tools ran — the user never
chooses a target, because they experienced one answer, not two services. */}
{!isLoading && messages.length > 0 && messages[messages.length - 1].role === "assistant" && (
<div className={chat.rateBlock}>
<FeedbackWidget
targets={
webToolsUsed ? [FEEDBACK_TARGETS.assistent, FEEDBACK_TARGETS.search] : [FEEDBACK_TARGETS.assistent]
}
/>
</div>
)}

{currencyFallback && (
// One honest line when the send switched token: the deviation is announced, the
// stored preference stays untouched (the hook never persists a fallback), and
Expand Down
Loading
Loading