jat-feedback 3.8.0 → 4.0.0
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/README.md +245 -7
- package/dist/jat-feedback.js +40 -36
- package/dist/jat-feedback.mjs +3235 -3017
- package/package.json +8 -3
- package/supabase/migrations/3.9.0_parent_id_set_null_on_delete.sql +19 -0
package/README.md
CHANGED
|
@@ -35,6 +35,8 @@ The package includes the widget bundle, Supabase migration, and edge function
|
|
|
35
35
|
| `agent-proxy` | No | `''` | URL path for the LLM proxy endpoint (e.g., `'/api/feedback/agent'`). Enables the Agent tab. |
|
|
36
36
|
| `agent-model` | No | `'claude-sonnet-4-6'` | LLM model identifier passed to the proxy endpoint |
|
|
37
37
|
| `agent-context` | No | `''` | Static app context injected into Agent system prompt (describe your app, key pages, nav) |
|
|
38
|
+
| `agent-role` | No | `''` | Role identifier for the current user (e.g. `'dispatcher'`, `'owner'`, `'tech'`). Injected as `[Role: X]` in the system prompt so the LLM can tailor tone, available tools, and safety defaults per role. |
|
|
39
|
+
| `approval-required-tools` | No | `''` | Comma-separated list of tool names that require user approval before executing (e.g. `'reassign_job,delete_user'`). Replaces the default set of four DOM-modifying built-ins. Omit to keep default gating. See [Approval-Required Tools](#approval-required-tools). |
|
|
38
40
|
|
|
39
41
|
## Agent Tab: LLM Proxy Endpoint
|
|
40
42
|
|
|
@@ -130,18 +132,34 @@ The Agent tab appears automatically when `agent-proxy` is set. Without it, only
|
|
|
130
132
|
|
|
131
133
|
### Page-Level Tool Registration
|
|
132
134
|
|
|
133
|
-
Register custom tools that the agent can call during its execution loop. Tools
|
|
135
|
+
Register custom tools that the agent can call during its execution loop. Tools come in two flavors — pick the one that fits the data and trust boundary:
|
|
136
|
+
|
|
137
|
+
- **Client tools** (default) run in the browser with full access to your app's state, auth context, and DOM. Use these for read-only page state (current user, current route, page data) and anything the user is already authenticated to do client-side.
|
|
138
|
+
- **Server tools** forward the call to your proxy at `{agent-proxy}/tool/execute` and execute server-side with your own authenticated context (e.g. SvelteKit `locals.currentOrg`). Use these for cross-tenant lookups, privileged writes, or anything that touches secrets — the widget never sees them.
|
|
139
|
+
|
|
140
|
+
The LLM doesn't care which kind a tool is — it sees the same name + description + parameters either way. The widget renders results identically.
|
|
134
141
|
|
|
135
142
|
#### `registerTools()` API Reference
|
|
136
143
|
|
|
137
144
|
```typescript
|
|
138
|
-
|
|
145
|
+
type ToolDefinition = ClientToolDefinition | ServerToolDefinition;
|
|
146
|
+
|
|
147
|
+
interface ClientToolDefinition {
|
|
139
148
|
name: string;
|
|
140
149
|
description: string;
|
|
141
|
-
parameters: Record<string, unknown>;
|
|
150
|
+
parameters: Record<string, unknown>; // JSON Schema object
|
|
151
|
+
executor?: 'client'; // optional, default
|
|
142
152
|
handler: (args: Record<string, unknown>) => Promise<unknown>;
|
|
143
153
|
}
|
|
144
154
|
|
|
155
|
+
interface ServerToolDefinition {
|
|
156
|
+
name: string;
|
|
157
|
+
description: string;
|
|
158
|
+
parameters: Record<string, unknown>; // JSON Schema object
|
|
159
|
+
executor: 'server'; // required
|
|
160
|
+
// No handler — execution happens at {agent-proxy}/tool/execute
|
|
161
|
+
}
|
|
162
|
+
|
|
145
163
|
const widget = document.querySelector('jat-feedback');
|
|
146
164
|
widget.registerTools(tools: ToolDefinition[]): void
|
|
147
165
|
```
|
|
@@ -151,13 +169,15 @@ widget.registerTools(tools: ToolDefinition[]): void
|
|
|
151
169
|
| `name` | `string` | Unique tool name (snake_case). The LLM uses this to call the tool. |
|
|
152
170
|
| `description` | `string` | Tells the LLM what the tool does and when to use it. |
|
|
153
171
|
| `parameters` | `object` | JSON Schema describing the tool's arguments. Use `{ type: 'object', properties: {} }` for no-arg tools. |
|
|
154
|
-
| `
|
|
172
|
+
| `executor` | `'client' \| 'server'` | Where the tool runs. Omit (or `'client'`) for browser-side handlers. Set to `'server'` to forward execution to the proxy. |
|
|
173
|
+
| `handler` | `async (args) => any` | **Client tools only.** Runs in the browser when the LLM calls the tool. Receives parsed args, returns any JSON-serializable value. |
|
|
155
174
|
|
|
156
175
|
**Behavior:**
|
|
157
176
|
- Multiple `registerTools()` calls **accumulate** — tools are added, not replaced
|
|
158
177
|
- Register tools before the user opens the Agent tab (typically in `onMount`)
|
|
159
|
-
- If a handler throws, the error message is returned to the LLM so it can recover gracefully
|
|
160
|
-
-
|
|
178
|
+
- If a client handler throws, the error message is returned to the LLM so it can recover gracefully
|
|
179
|
+
- Client handlers have full access to the page's DOM, stores, and JS context
|
|
180
|
+
- Server tools require an `agent-proxy` attribute — without it, the widget refuses to call them
|
|
161
181
|
|
|
162
182
|
#### Example: Global Tools in SvelteKit
|
|
163
183
|
|
|
@@ -252,13 +272,188 @@ Add tools that only make sense on a specific page:
|
|
|
252
272
|
</script>
|
|
253
273
|
```
|
|
254
274
|
|
|
275
|
+
#### Example: Server Tool
|
|
276
|
+
|
|
277
|
+
Server tools execute on your server with whatever org/auth context your proxy has. Declare them by name + description + parameter schema only — there's no client handler:
|
|
278
|
+
|
|
279
|
+
```svelte
|
|
280
|
+
<script lang="ts">
|
|
281
|
+
import { onMount } from "svelte"
|
|
282
|
+
|
|
283
|
+
onMount(() => {
|
|
284
|
+
const widget = document.querySelector("jat-feedback")
|
|
285
|
+
if (!widget?.registerTools) return
|
|
286
|
+
|
|
287
|
+
widget.registerTools([
|
|
288
|
+
{
|
|
289
|
+
name: "lookup_customer",
|
|
290
|
+
description: "Find a customer by email. Returns id, name, plan, and most-recent order.",
|
|
291
|
+
parameters: {
|
|
292
|
+
type: "object",
|
|
293
|
+
properties: { email: { type: "string", format: "email" } },
|
|
294
|
+
required: ["email"],
|
|
295
|
+
},
|
|
296
|
+
executor: "server",
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "reassign_job",
|
|
300
|
+
description: "Reassign a job to a different dispatcher. Destructive — confirm before calling.",
|
|
301
|
+
parameters: {
|
|
302
|
+
type: "object",
|
|
303
|
+
properties: {
|
|
304
|
+
job_id: { type: "string" },
|
|
305
|
+
new_assignee_id: { type: "string" },
|
|
306
|
+
},
|
|
307
|
+
required: ["job_id", "new_assignee_id"],
|
|
308
|
+
},
|
|
309
|
+
executor: "server",
|
|
310
|
+
},
|
|
311
|
+
])
|
|
312
|
+
})
|
|
313
|
+
</script>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
##### Proxy Contract: `POST {agent-proxy}/tool/execute`
|
|
317
|
+
|
|
318
|
+
The widget POSTs the tool call as JSON; the proxy executes the tool and responds in one of two shapes.
|
|
319
|
+
|
|
320
|
+
**Request:**
|
|
321
|
+
|
|
322
|
+
```http
|
|
323
|
+
POST /api/feedback/agent/tool/execute
|
|
324
|
+
Content-Type: application/json
|
|
325
|
+
|
|
326
|
+
{
|
|
327
|
+
"tool": "lookup_customer",
|
|
328
|
+
"args": { "email": "ada@example.com" },
|
|
329
|
+
"model": "claude-sonnet-4-6"
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Response — JSON (single result):**
|
|
334
|
+
|
|
335
|
+
```http
|
|
336
|
+
HTTP/1.1 200 OK
|
|
337
|
+
Content-Type: application/json
|
|
338
|
+
|
|
339
|
+
{ "result": { "id": "cust_123", "name": "Ada Lovelace", "plan": "pro" } }
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Or, on a refused/errored call:
|
|
343
|
+
|
|
344
|
+
```json
|
|
345
|
+
{ "error": "Customer not found in your organization" }
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Response — SSE (streaming):**
|
|
349
|
+
|
|
350
|
+
```http
|
|
351
|
+
HTTP/1.1 200 OK
|
|
352
|
+
Content-Type: text/event-stream
|
|
353
|
+
|
|
354
|
+
data: {"type":"chunk","text":"Looking up customer…\n"}
|
|
355
|
+
|
|
356
|
+
data: {"type":"chunk","text":"Found 1 match.\n"}
|
|
357
|
+
|
|
358
|
+
data: {"type":"result","value":{"id":"cust_123","name":"Ada Lovelace","plan":"pro"}}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
`chunk` events render live as a typing indicator inside the result message. The terminating `result` event delivers the structured value the LLM sees on its next step. An `error` event aborts the call and surfaces a user-facing error.
|
|
362
|
+
|
|
363
|
+
##### SvelteKit Example: Wiring `/tool/execute`
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
// src/routes/api/feedback/agent/tool/execute/+server.ts
|
|
367
|
+
import { json, error } from '@sveltejs/kit';
|
|
368
|
+
import type { RequestHandler } from './$types';
|
|
369
|
+
|
|
370
|
+
const TOOLS: Record<string, (args: any, locals: App.Locals) => Promise<unknown>> = {
|
|
371
|
+
async lookup_customer(args, locals) {
|
|
372
|
+
if (!locals.currentOrg) throw error(403, 'no org context');
|
|
373
|
+
const { data, error: dbErr } = await locals.supabase
|
|
374
|
+
.from('customers')
|
|
375
|
+
.select('id, name, plan')
|
|
376
|
+
.eq('org_id', locals.currentOrg.id)
|
|
377
|
+
.eq('email', args.email)
|
|
378
|
+
.maybeSingle();
|
|
379
|
+
if (dbErr) throw error(500, dbErr.message);
|
|
380
|
+
if (!data) return { error: 'Customer not found in your organization' };
|
|
381
|
+
return { result: data };
|
|
382
|
+
},
|
|
383
|
+
// …
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
export const POST: RequestHandler = async ({ request, locals }) => {
|
|
387
|
+
const { tool, args } = await request.json();
|
|
388
|
+
const handler = TOOLS[tool];
|
|
389
|
+
if (!handler) return json({ error: `Unknown tool: ${tool}` });
|
|
390
|
+
try {
|
|
391
|
+
const result = await handler(args, locals);
|
|
392
|
+
return json(result);
|
|
393
|
+
} catch (err) {
|
|
394
|
+
return json({ error: err instanceof Error ? err.message : 'Tool failed' });
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
For SSE, set `Content-Type: text/event-stream`, return a `ReadableStream`, and emit `data: {…}\n\n` lines per event. Always finish with a `result` event so the LLM sees a structured value.
|
|
400
|
+
|
|
255
401
|
#### How Tools Work
|
|
256
402
|
|
|
257
403
|
Tools are integrated into the page-agent's action loop:
|
|
258
404
|
- The LLM sees registered tools alongside built-in browser actions (click, type, scroll, etc.)
|
|
259
405
|
- Each agent step picks one action — either a browser action or a registered tool
|
|
260
406
|
- Tool results feed back to the LLM on the next step automatically
|
|
261
|
-
- If a handler throws, the error message is returned to the LLM so it can recover gracefully
|
|
407
|
+
- If a client handler throws, the error message is returned to the LLM so it can recover gracefully
|
|
408
|
+
- Server tools forward to `{agent-proxy}/tool/execute`; the LLM sees only the final structured value (or `error` message)
|
|
409
|
+
|
|
410
|
+
### Approval-Required Tools
|
|
411
|
+
|
|
412
|
+
By default, four DOM-modifying built-ins (`click_element_by_index`, `input_text`, `select_dropdown_option`, `execute_javascript`) require the user to click "Allow" before the agent executes them. v4 makes this set configurable.
|
|
413
|
+
|
|
414
|
+
**Extend the default set (add your server tools):**
|
|
415
|
+
|
|
416
|
+
```svelte
|
|
417
|
+
<script>
|
|
418
|
+
import { onMount } from "svelte"
|
|
419
|
+
|
|
420
|
+
onMount(() => {
|
|
421
|
+
const widget = document.querySelector("jat-feedback")
|
|
422
|
+
if (!widget?.setApprovalRequiredTools) return
|
|
423
|
+
// Keep the default four DOM tools AND gate your custom tool
|
|
424
|
+
widget.setApprovalRequiredTools([
|
|
425
|
+
"click_element_by_index", "input_text", "select_dropdown_option",
|
|
426
|
+
"execute_javascript", "reassign_job"
|
|
427
|
+
])
|
|
428
|
+
})
|
|
429
|
+
</script>
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Replace the default set (only gate your tools):**
|
|
433
|
+
|
|
434
|
+
```html
|
|
435
|
+
<jat-feedback approval-required-tools="reassign_job,delete_user" ...></jat-feedback>
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Per-tool flag (no global list needed):**
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
widget.registerTools([{
|
|
442
|
+
name: "delete_user",
|
|
443
|
+
description: "Permanently delete a user account",
|
|
444
|
+
parameters: { type: "object", properties: { user_id: { type: "string" } }, required: ["user_id"] },
|
|
445
|
+
executor: "server",
|
|
446
|
+
requireApproval: true, // gates this tool regardless of the global list
|
|
447
|
+
}])
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Disable approval prompts entirely:**
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
widget.setApprovalRequiredTools([]) // empty list + no per-tool flags = no prompts
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
The exported `DEFAULT_APPROVAL_REQUIRED_TOOLS` constant lists the four built-in DOM tools — import it if you want to spread them into a custom list programmatically.
|
|
262
457
|
|
|
263
458
|
### Error Handling
|
|
264
459
|
|
|
@@ -862,6 +1057,49 @@ The `project_tasks` table columns used by the pipeline:
|
|
|
862
1057
|
| `parent_id` | UUID | 3.0.0 | Self-referential parent for hierarchical tasks |
|
|
863
1058
|
| `updated_at` | TIMESTAMPTZ | 3.0.0 | Auto-updated timestamp |
|
|
864
1059
|
|
|
1060
|
+
## Upgrading from v3 to v4
|
|
1061
|
+
|
|
1062
|
+
v4 promotes `ToolDefinition` from a flat interface to a discriminated union. Existing client tools continue to work at runtime unchanged — this is a TypeScript-types-only breaking change.
|
|
1063
|
+
|
|
1064
|
+
### TypeScript: update `ToolDefinition` annotations
|
|
1065
|
+
|
|
1066
|
+
```typescript
|
|
1067
|
+
// v3 (old)
|
|
1068
|
+
import type { ToolDefinition } from "jat-feedback"
|
|
1069
|
+
const tools: ToolDefinition[] = [...]
|
|
1070
|
+
|
|
1071
|
+
// v4 (new) — same import, type is now ClientToolDefinition | ServerToolDefinition
|
|
1072
|
+
import type { ToolDefinition, ClientToolDefinition, ServerToolDefinition } from "jat-feedback"
|
|
1073
|
+
|
|
1074
|
+
// If you have generic arrays, use the union type:
|
|
1075
|
+
const tools: ToolDefinition[] = [...] // still works (union exported as ToolDefinition)
|
|
1076
|
+
|
|
1077
|
+
// Or be specific:
|
|
1078
|
+
const clientTools: ClientToolDefinition[] = [...]
|
|
1079
|
+
const serverTools: ServerToolDefinition[] = [...]
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
**Existing tool objects don't need changes.** A plain `{ name, description, parameters, handler }` object satisfies `ClientToolDefinition` — `executor` defaults to `'client'` when omitted.
|
|
1083
|
+
|
|
1084
|
+
### New: add server tools (optional)
|
|
1085
|
+
|
|
1086
|
+
If you want tools that execute server-side with your auth context, see the [Server Tool](#example-server-tool) docs. No changes needed if you only use client tools.
|
|
1087
|
+
|
|
1088
|
+
### New: configure approval gating (optional)
|
|
1089
|
+
|
|
1090
|
+
The default approval behavior (four DOM built-ins) is unchanged. If you register server tools that should also require confirmation, use `requireApproval: true` on the tool definition or `widget.setApprovalRequiredTools([...])`. See [Approval-Required Tools](#approval-required-tools).
|
|
1091
|
+
|
|
1092
|
+
### Bump the version
|
|
1093
|
+
|
|
1094
|
+
```bash
|
|
1095
|
+
# In each consuming project
|
|
1096
|
+
npm install jat-feedback@^4.0.0
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
No schema migrations required for v4 — all changes are widget JS only.
|
|
1100
|
+
|
|
1101
|
+
---
|
|
1102
|
+
|
|
865
1103
|
## Upgrading
|
|
866
1104
|
|
|
867
1105
|
npm never auto-updates — you need to explicitly upgrade and then handle each type of change manually.
|