@pinagent/react-native 0.1.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 +201 -0
- package/dist/babel.cjs +130 -0
- package/dist/babel.cjs.map +1 -0
- package/dist/babel.d.cts +16 -0
- package/dist/babel.d.cts.map +1 -0
- package/dist/babel.d.ts +16 -0
- package/dist/babel.d.ts.map +1 -0
- package/dist/babel.js +130 -0
- package/dist/babel.js.map +1 -0
- package/dist/server.cjs +4684 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +109 -0
- package/dist/server.d.cts.map +1 -0
- package/dist/server.d.ts +110 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +4681 -0
- package/dist/server.js.map +1 -0
- package/package.json +83 -0
- package/src/native/Pinagent.tsx +703 -0
- package/src/native/StreamSheet.tsx +426 -0
- package/src/native/index.ts +9 -0
- package/src/native/inspector.ts +407 -0
- package/src/native/multi-pick.ts +74 -0
- package/src/native/restore.ts +91 -0
- package/src/native/screenshot.ts +34 -0
- package/src/native/submit-outcome.ts +70 -0
- package/src/native/transcript.ts +143 -0
- package/src/native/transport.ts +162 -0
- package/src/native/types.ts +95 -0
- package/src/native/ws-client.ts +173 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Live agent transcript sheet for React Native.
|
|
4
|
+
*
|
|
5
|
+
* After a comment is submitted and an agent is spawned, the widget opens this
|
|
6
|
+
* bottom sheet and streams the run over WebSocket (see `ws-client.ts`). It's
|
|
7
|
+
* the RN analog of the web widget's agent tray: a scrolling transcript, an
|
|
8
|
+
* answer form when the agent calls `ask_user`, a follow-up box, and Stop /
|
|
9
|
+
* Dismiss controls.
|
|
10
|
+
*
|
|
11
|
+
* State is intentionally simple and reducer-driven: agent events accumulate in
|
|
12
|
+
* one array folded by `renderTranscript`; user follow-ups are tracked locally
|
|
13
|
+
* (the bus only streams agent events, not the developer's messages). A
|
|
14
|
+
* reconnect replays the agent transcript, so we clear `events` on `onReset`
|
|
15
|
+
* but keep local follow-ups.
|
|
16
|
+
*
|
|
17
|
+
* The sheet can be **minimized** to a compact status pill so the developer can
|
|
18
|
+
* keep interacting with the app — e.g. to pick another element and spawn a
|
|
19
|
+
* second agent. Minimizing doesn't tear the run down: the component stays
|
|
20
|
+
* mounted (only its rendering changes), so the WebSocket keeps streaming in the
|
|
21
|
+
* background and the live transcript is intact the moment it's re-expanded.
|
|
22
|
+
* `<Pinagent/>` mounts one of these per concurrent run.
|
|
23
|
+
*/
|
|
24
|
+
import type { ReactElement } from 'react';
|
|
25
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
26
|
+
import {
|
|
27
|
+
Animated,
|
|
28
|
+
Modal,
|
|
29
|
+
Pressable,
|
|
30
|
+
ScrollView,
|
|
31
|
+
StyleSheet,
|
|
32
|
+
Text,
|
|
33
|
+
TextInput,
|
|
34
|
+
View,
|
|
35
|
+
} from 'react-native';
|
|
36
|
+
import { type AgentEvent, pendingAsk, renderTranscript } from './transcript';
|
|
37
|
+
import { StreamClient } from './ws-client';
|
|
38
|
+
|
|
39
|
+
/** Height of a minimized pill — drives the bottom-left stacking offset. */
|
|
40
|
+
const PILL_HEIGHT = 40;
|
|
41
|
+
|
|
42
|
+
export interface StreamSheetProps {
|
|
43
|
+
feedbackId: string;
|
|
44
|
+
/** Source label shown in the header (e.g. `file:line` or component name). */
|
|
45
|
+
target: string;
|
|
46
|
+
/** Render as a compact pill (WS stays live) instead of the full sheet. */
|
|
47
|
+
minimized: boolean;
|
|
48
|
+
/** Stack position among minimized pills (0 = bottom-most), for layout. */
|
|
49
|
+
stackIndex: number;
|
|
50
|
+
/** Collapse the full sheet to its pill. */
|
|
51
|
+
onMinimize: () => void;
|
|
52
|
+
/** Expand the pill back to the full sheet. */
|
|
53
|
+
onExpand: () => void;
|
|
54
|
+
/** Dismiss for good — tears down the WS and removes this run's view. */
|
|
55
|
+
onClose: () => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function StreamSheet({
|
|
59
|
+
feedbackId,
|
|
60
|
+
target,
|
|
61
|
+
minimized,
|
|
62
|
+
stackIndex,
|
|
63
|
+
onMinimize,
|
|
64
|
+
onExpand,
|
|
65
|
+
onClose,
|
|
66
|
+
}: StreamSheetProps): ReactElement {
|
|
67
|
+
const [events, setEvents] = useState<AgentEvent[]>([]);
|
|
68
|
+
const [followUps, setFollowUps] = useState<string[]>([]);
|
|
69
|
+
const [answered, setAnswered] = useState<Record<string, string>>({});
|
|
70
|
+
const [done, setDone] = useState(false);
|
|
71
|
+
const [transportError, setTransportError] = useState<string | null>(null);
|
|
72
|
+
const [draft, setDraft] = useState('');
|
|
73
|
+
const [askDraft, setAskDraft] = useState('');
|
|
74
|
+
|
|
75
|
+
const clientRef = useRef<StreamClient | null>(null);
|
|
76
|
+
const scrollRef = useRef<ScrollView>(null);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const client = new StreamClient(feedbackId, {
|
|
80
|
+
onReset: () => setEvents([]),
|
|
81
|
+
onEvent: (event) => {
|
|
82
|
+
setEvents((prev) => [...prev, event]);
|
|
83
|
+
if (event.type === 'result') setDone(true);
|
|
84
|
+
},
|
|
85
|
+
onDone: () => setDone(true),
|
|
86
|
+
onError: (message) => setTransportError(message),
|
|
87
|
+
});
|
|
88
|
+
clientRef.current = client;
|
|
89
|
+
client.start();
|
|
90
|
+
return () => client.stop();
|
|
91
|
+
}, [feedbackId]);
|
|
92
|
+
|
|
93
|
+
const rows = useMemo(() => renderTranscript(events), [events]);
|
|
94
|
+
const ask = useMemo(() => pendingAsk(events), [events]);
|
|
95
|
+
const askOpen = ask && !answered[ask.askId];
|
|
96
|
+
const running = !done && !transportError;
|
|
97
|
+
// The agent is blocked on an `ask_user` we haven't answered. While minimized
|
|
98
|
+
// that would otherwise be invisible — the run just stalls — so the pill flags
|
|
99
|
+
// it and pulses to pull you back. (When expanded, the answer form shows.)
|
|
100
|
+
const needsInput = minimized && !!askOpen;
|
|
101
|
+
|
|
102
|
+
// Drive the pulse on the minimized pill while it's waiting for input. The
|
|
103
|
+
// ref/effect are hooks, so they live above the early return; the loop only
|
|
104
|
+
// runs in the `needsInput` state and resets otherwise.
|
|
105
|
+
const pulse = useRef(new Animated.Value(1)).current;
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (!needsInput) {
|
|
108
|
+
pulse.setValue(1);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const loop = Animated.loop(
|
|
112
|
+
Animated.sequence([
|
|
113
|
+
Animated.timing(pulse, { toValue: 0.35, duration: 600, useNativeDriver: true }),
|
|
114
|
+
Animated.timing(pulse, { toValue: 1, duration: 600, useNativeDriver: true }),
|
|
115
|
+
]),
|
|
116
|
+
);
|
|
117
|
+
loop.start();
|
|
118
|
+
return () => loop.stop();
|
|
119
|
+
}, [needsInput, pulse]);
|
|
120
|
+
|
|
121
|
+
// Minimized: a compact, tappable status pill. The WS hooks above keep
|
|
122
|
+
// running because the component stays mounted — only the rendering changes —
|
|
123
|
+
// so the run streams on in the background and re-expands with full state.
|
|
124
|
+
if (minimized) {
|
|
125
|
+
return (
|
|
126
|
+
<Pressable
|
|
127
|
+
onPress={onExpand}
|
|
128
|
+
accessibilityRole="button"
|
|
129
|
+
style={[
|
|
130
|
+
styles.pill,
|
|
131
|
+
needsInput && styles.pillAsk,
|
|
132
|
+
{ bottom: 40 + stackIndex * (PILL_HEIGHT + 8) },
|
|
133
|
+
]}
|
|
134
|
+
>
|
|
135
|
+
<Animated.View
|
|
136
|
+
style={[
|
|
137
|
+
styles.pillDot,
|
|
138
|
+
needsInput
|
|
139
|
+
? [styles.pillDotAsk, { opacity: pulse }]
|
|
140
|
+
: transportError
|
|
141
|
+
? styles.pillDotError
|
|
142
|
+
: running
|
|
143
|
+
? styles.pillDotRunning
|
|
144
|
+
: styles.pillDotDone,
|
|
145
|
+
]}
|
|
146
|
+
/>
|
|
147
|
+
<Text style={styles.pillText} numberOfLines={1}>
|
|
148
|
+
{needsInput ? `Needs input · ${target}` : target}
|
|
149
|
+
</Text>
|
|
150
|
+
<Pressable onPress={onClose} hitSlop={10} accessibilityRole="button">
|
|
151
|
+
<Text style={styles.pillClose}>✕</Text>
|
|
152
|
+
</Pressable>
|
|
153
|
+
</Pressable>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function submitAnswer(answer: string): void {
|
|
158
|
+
if (!ask || !answer.trim()) return;
|
|
159
|
+
clientRef.current?.sendAskResponse(ask.askId, answer.trim());
|
|
160
|
+
setAnswered((prev) => ({ ...prev, [ask.askId]: answer.trim() }));
|
|
161
|
+
setAskDraft('');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function sendFollowUp(): void {
|
|
165
|
+
const text = draft.trim();
|
|
166
|
+
if (!text) return;
|
|
167
|
+
clientRef.current?.sendUserMessage(text);
|
|
168
|
+
setFollowUps((prev) => [...prev, text]);
|
|
169
|
+
setDraft('');
|
|
170
|
+
setDone(false); // a follow-up resumes the run
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<Modal visible transparent animationType="slide" onRequestClose={onClose}>
|
|
175
|
+
<View style={styles.backdrop}>
|
|
176
|
+
<View style={styles.sheet}>
|
|
177
|
+
<View style={styles.header}>
|
|
178
|
+
<Text style={styles.headerTitle} numberOfLines={1}>
|
|
179
|
+
{running ? 'Agent working' : 'Agent finished'} · {target}
|
|
180
|
+
</Text>
|
|
181
|
+
<View style={styles.headerBtns}>
|
|
182
|
+
{/* Minimize: collapse to a pill so the app is interactive again
|
|
183
|
+
(e.g. to pick another element and spawn a second agent). The
|
|
184
|
+
run keeps streaming in the background. */}
|
|
185
|
+
<Pressable onPress={onMinimize} hitSlop={8} accessibilityRole="button">
|
|
186
|
+
<Text style={styles.headerBtn}>—</Text>
|
|
187
|
+
</Pressable>
|
|
188
|
+
<Pressable onPress={onClose} hitSlop={8} accessibilityRole="button">
|
|
189
|
+
<Text style={styles.headerBtn}>✕</Text>
|
|
190
|
+
</Pressable>
|
|
191
|
+
</View>
|
|
192
|
+
</View>
|
|
193
|
+
|
|
194
|
+
<ScrollView
|
|
195
|
+
ref={scrollRef}
|
|
196
|
+
style={styles.log}
|
|
197
|
+
contentContainerStyle={styles.logContent}
|
|
198
|
+
onContentSizeChange={() => scrollRef.current?.scrollToEnd({ animated: true })}
|
|
199
|
+
>
|
|
200
|
+
{rows.length === 0 && !transportError ? (
|
|
201
|
+
<Text style={styles.muted}>Connecting…</Text>
|
|
202
|
+
) : null}
|
|
203
|
+
|
|
204
|
+
{rows.map((row) => {
|
|
205
|
+
if (row.kind === 'tool') {
|
|
206
|
+
const mark = row.ok === undefined ? '…' : row.ok ? '✓' : '✗';
|
|
207
|
+
return (
|
|
208
|
+
<View key={row.id} style={styles.toolRow}>
|
|
209
|
+
<Text style={styles.toolName}>
|
|
210
|
+
{mark} {row.text}
|
|
211
|
+
</Text>
|
|
212
|
+
{row.detail ? (
|
|
213
|
+
<Text style={styles.toolDetail} numberOfLines={1}>
|
|
214
|
+
{row.detail}
|
|
215
|
+
</Text>
|
|
216
|
+
) : null}
|
|
217
|
+
</View>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (row.kind === 'error') {
|
|
221
|
+
return (
|
|
222
|
+
<Text key={row.id} style={styles.errorRow}>
|
|
223
|
+
{row.text}
|
|
224
|
+
</Text>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (row.kind === 'result' || row.kind === 'status') {
|
|
228
|
+
return (
|
|
229
|
+
<Text key={row.id} style={styles.resultRow}>
|
|
230
|
+
{row.text}
|
|
231
|
+
</Text>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (row.kind === 'ask') {
|
|
235
|
+
return (
|
|
236
|
+
<Text key={row.id} style={styles.askRow}>
|
|
237
|
+
{row.text}
|
|
238
|
+
</Text>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
return (
|
|
242
|
+
<Text key={row.id} style={styles.textRow}>
|
|
243
|
+
{row.text}
|
|
244
|
+
</Text>
|
|
245
|
+
);
|
|
246
|
+
})}
|
|
247
|
+
|
|
248
|
+
{followUps.map((m, i) => (
|
|
249
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: append-only message log — entries are only pushed, never reordered or removed
|
|
250
|
+
<Text key={`you-${i}-${m}`} style={styles.youRow}>
|
|
251
|
+
You: {m}
|
|
252
|
+
</Text>
|
|
253
|
+
))}
|
|
254
|
+
|
|
255
|
+
{transportError ? <Text style={styles.errorRow}>{transportError}</Text> : null}
|
|
256
|
+
</ScrollView>
|
|
257
|
+
|
|
258
|
+
{/* Answer form takes over the input area while the agent is blocked. */}
|
|
259
|
+
{askOpen ? (
|
|
260
|
+
<View style={styles.inputBar}>
|
|
261
|
+
{ask.options.length > 0 ? (
|
|
262
|
+
<View style={styles.options}>
|
|
263
|
+
{ask.options.map((opt) => (
|
|
264
|
+
<Pressable key={opt} onPress={() => submitAnswer(opt)} style={styles.optionBtn}>
|
|
265
|
+
<Text style={styles.optionText}>{opt}</Text>
|
|
266
|
+
</Pressable>
|
|
267
|
+
))}
|
|
268
|
+
</View>
|
|
269
|
+
) : null}
|
|
270
|
+
<View style={styles.row}>
|
|
271
|
+
<TextInput
|
|
272
|
+
value={askDraft}
|
|
273
|
+
onChangeText={setAskDraft}
|
|
274
|
+
placeholder="Answer the agent…"
|
|
275
|
+
placeholderTextColor="#9aa0a6"
|
|
276
|
+
style={styles.input}
|
|
277
|
+
/>
|
|
278
|
+
<Pressable
|
|
279
|
+
onPress={() => submitAnswer(askDraft)}
|
|
280
|
+
disabled={!askDraft.trim()}
|
|
281
|
+
style={[styles.sendBtn, !askDraft.trim() && styles.disabled]}
|
|
282
|
+
>
|
|
283
|
+
<Text style={styles.sendText}>Send</Text>
|
|
284
|
+
</Pressable>
|
|
285
|
+
</View>
|
|
286
|
+
</View>
|
|
287
|
+
) : (
|
|
288
|
+
<View style={styles.inputBar}>
|
|
289
|
+
<View style={styles.row}>
|
|
290
|
+
<TextInput
|
|
291
|
+
value={draft}
|
|
292
|
+
onChangeText={setDraft}
|
|
293
|
+
placeholder={running ? 'Queue a follow-up…' : 'Send a follow-up…'}
|
|
294
|
+
placeholderTextColor="#9aa0a6"
|
|
295
|
+
style={styles.input}
|
|
296
|
+
/>
|
|
297
|
+
<Pressable
|
|
298
|
+
onPress={sendFollowUp}
|
|
299
|
+
disabled={!draft.trim()}
|
|
300
|
+
style={[styles.sendBtn, !draft.trim() && styles.disabled]}
|
|
301
|
+
>
|
|
302
|
+
<Text style={styles.sendText}>Send</Text>
|
|
303
|
+
</Pressable>
|
|
304
|
+
</View>
|
|
305
|
+
<Pressable
|
|
306
|
+
onPress={() => (running ? clientRef.current?.interrupt() : onClose())}
|
|
307
|
+
style={styles.bottomBtn}
|
|
308
|
+
>
|
|
309
|
+
<Text style={styles.bottomBtnText}>{running ? 'Stop' : 'Dismiss'}</Text>
|
|
310
|
+
</Pressable>
|
|
311
|
+
</View>
|
|
312
|
+
)}
|
|
313
|
+
</View>
|
|
314
|
+
</View>
|
|
315
|
+
</Modal>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const styles = StyleSheet.create({
|
|
320
|
+
backdrop: { flex: 1, justifyContent: 'flex-end', backgroundColor: 'rgba(0,0,0,0.35)' },
|
|
321
|
+
sheet: {
|
|
322
|
+
backgroundColor: '#fff',
|
|
323
|
+
borderTopLeftRadius: 16,
|
|
324
|
+
borderTopRightRadius: 16,
|
|
325
|
+
maxHeight: '80%',
|
|
326
|
+
paddingBottom: 16,
|
|
327
|
+
},
|
|
328
|
+
header: {
|
|
329
|
+
flexDirection: 'row',
|
|
330
|
+
alignItems: 'center',
|
|
331
|
+
justifyContent: 'space-between',
|
|
332
|
+
paddingHorizontal: 16,
|
|
333
|
+
paddingVertical: 12,
|
|
334
|
+
borderBottomWidth: 1,
|
|
335
|
+
borderBottomColor: '#f0f0f0',
|
|
336
|
+
},
|
|
337
|
+
headerTitle: { flex: 1, fontSize: 14, fontWeight: '600', color: '#111827' },
|
|
338
|
+
headerBtns: { flexDirection: 'row', alignItems: 'center', gap: 16 },
|
|
339
|
+
headerBtn: { fontSize: 16, color: '#6b7280', paddingLeft: 4 },
|
|
340
|
+
log: { paddingHorizontal: 16 },
|
|
341
|
+
logContent: { paddingVertical: 12, gap: 8 },
|
|
342
|
+
muted: { color: '#9aa0a6', fontSize: 13 },
|
|
343
|
+
textRow: { fontSize: 14, color: '#111827', lineHeight: 20 },
|
|
344
|
+
toolRow: {
|
|
345
|
+
flexDirection: 'row',
|
|
346
|
+
alignItems: 'center',
|
|
347
|
+
gap: 8,
|
|
348
|
+
backgroundColor: '#f9fafb',
|
|
349
|
+
borderRadius: 8,
|
|
350
|
+
paddingHorizontal: 10,
|
|
351
|
+
paddingVertical: 6,
|
|
352
|
+
},
|
|
353
|
+
toolName: { fontSize: 13, fontWeight: '600', color: '#374151' },
|
|
354
|
+
toolDetail: { flex: 1, fontSize: 12, color: '#6b7280' },
|
|
355
|
+
errorRow: { fontSize: 13, color: '#b91c1c' },
|
|
356
|
+
resultRow: { fontSize: 13, fontWeight: '600', color: '#047857' },
|
|
357
|
+
askRow: { fontSize: 14, color: '#7c3aed', fontWeight: '600' },
|
|
358
|
+
youRow: { fontSize: 14, color: '#2563eb', alignSelf: 'flex-end' },
|
|
359
|
+
inputBar: {
|
|
360
|
+
paddingHorizontal: 16,
|
|
361
|
+
paddingTop: 8,
|
|
362
|
+
gap: 8,
|
|
363
|
+
borderTopWidth: 1,
|
|
364
|
+
borderTopColor: '#f0f0f0',
|
|
365
|
+
},
|
|
366
|
+
row: { flexDirection: 'row', alignItems: 'flex-end', gap: 8 },
|
|
367
|
+
options: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
|
|
368
|
+
optionBtn: {
|
|
369
|
+
borderWidth: 1,
|
|
370
|
+
borderColor: '#c4b5fd',
|
|
371
|
+
borderRadius: 999,
|
|
372
|
+
paddingHorizontal: 12,
|
|
373
|
+
paddingVertical: 6,
|
|
374
|
+
},
|
|
375
|
+
optionText: { color: '#6d28d9', fontSize: 13, fontWeight: '600' },
|
|
376
|
+
input: {
|
|
377
|
+
flex: 1,
|
|
378
|
+
minHeight: 40,
|
|
379
|
+
borderWidth: 1,
|
|
380
|
+
borderColor: '#e5e7eb',
|
|
381
|
+
borderRadius: 8,
|
|
382
|
+
paddingHorizontal: 10,
|
|
383
|
+
paddingVertical: 8,
|
|
384
|
+
fontSize: 15,
|
|
385
|
+
color: '#111827',
|
|
386
|
+
},
|
|
387
|
+
sendBtn: {
|
|
388
|
+
backgroundColor: '#3b82f6',
|
|
389
|
+
borderRadius: 8,
|
|
390
|
+
paddingHorizontal: 16,
|
|
391
|
+
paddingVertical: 10,
|
|
392
|
+
},
|
|
393
|
+
sendText: { color: '#fff', fontWeight: '600' },
|
|
394
|
+
disabled: { opacity: 0.4 },
|
|
395
|
+
bottomBtn: { alignSelf: 'center', paddingVertical: 8 },
|
|
396
|
+
bottomBtnText: { color: '#6b7280', fontWeight: '600' },
|
|
397
|
+
// Minimized status pill, bottom-left so it never collides with the FAB
|
|
398
|
+
// (bottom-right). `bottom` is set inline from the stack index.
|
|
399
|
+
pill: {
|
|
400
|
+
position: 'absolute',
|
|
401
|
+
left: 20,
|
|
402
|
+
maxWidth: '70%',
|
|
403
|
+
height: PILL_HEIGHT,
|
|
404
|
+
flexDirection: 'row',
|
|
405
|
+
alignItems: 'center',
|
|
406
|
+
gap: 8,
|
|
407
|
+
paddingHorizontal: 12,
|
|
408
|
+
borderRadius: PILL_HEIGHT / 2,
|
|
409
|
+
backgroundColor: 'rgba(17,24,39,0.95)',
|
|
410
|
+
shadowColor: '#000',
|
|
411
|
+
shadowOpacity: 0.25,
|
|
412
|
+
shadowRadius: 6,
|
|
413
|
+
shadowOffset: { width: 0, height: 2 },
|
|
414
|
+
elevation: 5,
|
|
415
|
+
},
|
|
416
|
+
// Waiting on `ask_user`: a purple-tinted pill (matching the expanded ask row)
|
|
417
|
+
// so a blocked run reads differently from a busy one at a glance.
|
|
418
|
+
pillAsk: { backgroundColor: 'rgba(76,29,149,0.97)' },
|
|
419
|
+
pillDot: { width: 8, height: 8, borderRadius: 4 },
|
|
420
|
+
pillDotRunning: { backgroundColor: '#3b82f6' },
|
|
421
|
+
pillDotDone: { backgroundColor: '#10b981' },
|
|
422
|
+
pillDotError: { backgroundColor: '#ef4444' },
|
|
423
|
+
pillDotAsk: { backgroundColor: '#c4b5fd' },
|
|
424
|
+
pillText: { flexShrink: 1, color: '#fff', fontSize: 13, fontWeight: '600' },
|
|
425
|
+
pillClose: { color: '#9aa0a6', fontSize: 13, paddingLeft: 2 },
|
|
426
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
export type { PinagentProps } from './Pinagent';
|
|
4
|
+
export { Pinagent } from './Pinagent';
|
|
5
|
+
export type { AgentEvent, ServerMessage, TranscriptRow } from './transcript';
|
|
6
|
+
export { pendingAsk, renderTranscript } from './transcript';
|
|
7
|
+
export { devServerBaseUrl, submitFeedback } from './transport';
|
|
8
|
+
export type { FeedbackInput, PickResult } from './types';
|
|
9
|
+
export { devServerWsUrl, StreamClient } from './ws-client';
|