Repository navigation
Expand file tree
/
Copy pathhooks.ts
More file actions
89 lines (81 loc) · 2.94 KB
/
Copy pathhooks.ts
File metadata and controls
89 lines (81 loc) · 2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
* Agent-awareness React hooks
*
* These hooks connect React components to the agent observation layer
* (UICapture → POST /api/ui-snapshot). See STANDARDS.md §5 for usage rules.
*/
import { useEffect, useRef } from 'react'
import { uiCapture } from '../services/UICapture'
/**
* Register a component's state for agent visibility.
*
* The state is included in the componentState section of UI snapshots
* the agent reads via GET /api/ui-snapshot. Keep payloads small (<10KB)
* and meaningful — summarize, don't dump raw data.
*
* @example
* useAgentAware('TodoList', {
* itemCount: items.length,
* completedCount: items.filter(i => i.done).length,
* })
*/
export function useAgentAware(name: string, state: Record<string, unknown>): void {
// Serialize so the effect only re-fires on real state changes,
// not on every render with a fresh object literal.
const serialized = JSON.stringify(state)
useEffect(() => {
uiCapture.updateComponent(name, JSON.parse(serialized) as Record<string, unknown>)
}, [name, serialized])
useEffect(() => {
return () => uiCapture.unregisterComponent(name)
}, [name])
}
export interface AgentCommand {
type: string
payload?: unknown
}
/**
* Handle commands pushed to the app from the CraftBot host frame.
*
* Commands arrive as postMessage events of shape
* `{ type: 'craftbot-agent-command', command: { type, payload } }`.
* If no host is present (app opened standalone), no commands ever
* arrive and the app degrades gracefully.
*/
export function useAgentCommand(handler: (command: AgentCommand) => void): void {
const handlerRef = useRef(handler)
handlerRef.current = handler
useEffect(() => {
const onMessage = (event: MessageEvent) => {
const data = event.data as { type?: string; command?: AgentCommand } | null
if (data && typeof data === 'object' && data.type === 'craftbot-agent-command' && data.command) {
handlerRef.current(data.command)
}
}
window.addEventListener('message', onMessage)
return () => window.removeEventListener('message', onMessage)
}, [])
}
/**
* Fire one of this app's declared triggers at the CraftBot agent — the
* reverse of useAgentCommand. The trigger must be declared in
* config/triggers.json; undeclared names are rejected by the host.
* `params` are data for the trigger's declared instruction, validated
* against its param spec.
*
* Returns false when no CraftBot host is present (app opened standalone) —
* the app should degrade gracefully, this is fire-and-forget either way.
*
* @example
* <button onClick={() => fireCraftBotTrigger('restock_needed', { item_id: item.id })}>
* Ask CraftBot to restock
* </button>
*/
export function fireCraftBotTrigger(trigger: string, params?: Record<string, unknown>): boolean {
if (window.parent === window) return false
window.parent.postMessage(
{ type: 'craftbot-agent-trigger', trigger, params: params || {} },
'*',
)
return true
}