Let bots manage their hooks, with the user confirming what widens them
Bots get list_hooks, pause_hook and delete_hook, which act directly since they only take away. A new hook, or a new name, brief or filter, or switching a paused one back on, goes through propose_hook: it stores a hook_proposals row and puts a [hook-proposal:ID] card in the reply, which the user it was proposed to confirms or rejects. The card is read from what Confirm saves, with the bot's reason labelled as its own; confirming checks everything again and saves through the Hooks modal's helpers. A new hook's URL is shown on the card once, to the user only, never in the outcome the bot is told. What calls a hook, its computers and its signing are never proposed. Only chat replies propose, and a hook call's own run gets no hook tools, so a caller's payload can't reach them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type { HookProposalView } from "@/src/types";
|
||||
import describeHookProposalDetails from "@/src/utils/describe-hook-proposal-details";
|
||||
import grabUserBotHook from "../bot-hooks/grab-user-bot-hook";
|
||||
import grabHookProposal from "./grab-hook-proposal";
|
||||
|
||||
type Params = {
|
||||
proposal_id: number | string;
|
||||
user_id: number | string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook proposal as its chip and confirm card show it to the user it was
|
||||
* proposed to: its details worked out now from what Confirm would save
|
||||
* (and, for a change, the hook as it is), the bot's reason beside them.
|
||||
*/
|
||||
export default async function grabHookProposalView({ proposal_id, user_id }: Params): Promise<HookProposalView> {
|
||||
const proposal = await grabHookProposal({ proposal_id, user_id });
|
||||
const { data } = proposal;
|
||||
const current = proposal.hook_id
|
||||
? await grabUserBotHook({ user_id, hook_id: proposal.hook_id }).catch(() => null)
|
||||
: null;
|
||||
const computer_ids = [data.computer_id, data.watch_computer_id].filter(Boolean) as number[];
|
||||
|
||||
const [bot, workspace, computers] = await Promise.all([
|
||||
BunSQLite.sql<{ name: string }>({ sql: `SELECT name FROM bots WHERE id = ?`, values: [proposal.bot_id] }),
|
||||
proposal.workspace_id
|
||||
? BunSQLite.sql<{ name: string }>({ sql: `SELECT name FROM workspaces WHERE id = ?`, values: [proposal.workspace_id] })
|
||||
: null,
|
||||
computer_ids.length
|
||||
? BunSQLite.sql<{ id: number; name: string }>({
|
||||
sql: `SELECT id, name FROM computers WHERE id IN (${computer_ids.map(() => "?").join(", ")})`,
|
||||
values: computer_ids,
|
||||
})
|
||||
: null,
|
||||
]);
|
||||
|
||||
return {
|
||||
id: proposal.id,
|
||||
action: proposal.action,
|
||||
hook_id: proposal.hook_id,
|
||||
title: proposal.title,
|
||||
bot_id: proposal.bot_id,
|
||||
bot_name: bot.payload?.[0]?.name || "The bot",
|
||||
status: proposal.status,
|
||||
expired: proposal.status == "pending" && proposal.expires_at < Date.now(),
|
||||
expires_at: proposal.expires_at,
|
||||
outcome: proposal.outcome,
|
||||
result_id: proposal.result_id,
|
||||
makes_url: proposal.action == "create" && data.source != "browser_notifications",
|
||||
details: describeHookProposalDetails({
|
||||
action: proposal.action,
|
||||
data,
|
||||
current,
|
||||
workspace_name: workspace?.payload?.[0]?.name || null,
|
||||
computers: new Map((computers?.payload || []).map((c) => [Number(c.id), c.name || ""])),
|
||||
}),
|
||||
reason: proposal.reason,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user