feat: hooks

This commit is contained in:
Gab
2026-03-27 14:00:04 +11:00
parent 8e05565e84
commit c39a97bb7d
12 changed files with 208 additions and 10 deletions

View File

@@ -313,7 +313,7 @@ export namespace Agent {
available_to_agents?: string[]
}
async function loadTFPrompts(): Promise<TFPrompt[]> {
export async function loadTFPrompts(): Promise<TFPrompt[]> {
const toolsPath = path.join(os.homedir(), ".tfcode", "tools.json")
try {
const content = await Bun.file(toolsPath).text()

View File

@@ -54,7 +54,7 @@ function HookStatus(props: { hook: Hook }) {
)
}
export function DialogTfHooks() {
export function DialogTfHooks(props: { onSelect?: (hook: Hook) => void }) {
const toast = useToast()
const { theme } = useTheme()
const [, setRef] = createSignal<DialogSelectRef<unknown>>()
@@ -86,7 +86,9 @@ export function DialogTfHooks() {
const region = creds.region || TF_DEFAULT_REGION
const baseUrl = REGION_API_URLS[region] || REGION_API_URLS[TF_DEFAULT_REGION]
const response = await fetch(`${baseUrl}/hook/list`, {
const url = new URL(`${baseUrl}/hook/list`)
url.searchParams.set("workspaceid", creds.workspace_id)
const response = await fetch(url.toString(), {
method: "GET",
headers: {
"x-api-key": creds.api_key,
@@ -98,8 +100,8 @@ export function DialogTfHooks() {
throw new Error(`HTTP ${response.status}: ${errorText}`)
}
const data = (await response.json()) as { success?: boolean; hooks?: Hook[] }
return data.hooks || []
const data = (await response.json()) as Hook[]
return Array.isArray(data) ? data : []
} catch (error) {
toast.show({
variant: "error",
@@ -172,7 +174,7 @@ export function DialogTfHooks() {
keybind={keybinds()}
onSelect={(option) => {
if (option.value.id === "loading" || option.value.id === "empty") return
// Don't close on select - just show the hook details
props.onSelect?.(option.value)
}}
/>
)

View File

@@ -374,7 +374,22 @@ export function Prompt(props: PromptProps) {
name: "hooks",
},
onSelect: () => {
dialog.replace(() => <DialogTfHooks />)
dialog.replace(() => (
<DialogTfHooks
onSelect={(hook) => {
const parts = []
if (hook.code_execution_instructions) parts.push(hook.code_execution_instructions)
if (hook.predefined_code_snippet) parts.push("\n\n```python\n" + hook.predefined_code_snippet + "\n```")
const text = parts.join("")
if (text) {
input.setText(text)
setStore("prompt", { input: text, parts: [] })
input.gotoBufferEnd()
}
dialog.clear()
}}
/>
))
},
},
]

View File

@@ -454,6 +454,38 @@ export namespace Server {
return c.json(skills)
},
)
.get(
"/prompts",
describeRoute({
summary: "List ToothFairyAI prompts",
description: "Get prompts assigned to a specific agent or all prompts.",
operationId: "app.prompts",
responses: {
200: {
description: "List of prompts",
content: {
"application/json": {
schema: resolver(
z.array(
z.object({
id: z.string(),
label: z.string(),
interpolation_string: z.string(),
available_to_agents: z.array(z.string()).optional(),
description: z.string().optional(),
}),
),
),
},
},
},
},
}),
async (c) => {
const prompts = await Agent.loadTFPrompts()
return c.json(prompts)
},
)
.get(
"/lsp",
describeRoute({