ghostykit 0.4.0 → 0.5.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 +7 -4
- package/dist/chunks/{chunk-DXFWACUL.js → chunk-TMEHQZWL.js} +5 -1
- package/dist/chunks/{chunk-HPTM2BCM.js → chunk-VK54J63C.js} +193 -19
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +7 -1
- package/dist/core/jsonPatch.d.ts +11 -0
- package/dist/core/reducer.d.ts +12 -2
- package/dist/core/types.d.ts +39 -2
- package/dist/react/Piloto.d.ts +4 -0
- package/dist/react/Status.d.ts +3 -1
- package/dist/react/index.js +97 -19
- package/dist/react/strings.d.ts +4 -0
- package/dist/react/usePilot.d.ts +21 -2
- package/dist/styles.css +9 -0
- package/dist/transport/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,10 +109,13 @@ ref.current?.send("Why did this fail?"); // ask on the user'
|
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
Other options: `strings`, `toolIcons`, `formatUserText`, `markdown`, `features`
|
|
112
|
-
(`attachments`, `retry`, `copy`, `usage`, `replyOptions`, `duration`), `references` /
|
|
113
|
-
`onReferencesChange`, `voiceNoteText`, `placeholder`, `avatar`, `initialTurns`,
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
(`attachments`, `retry`, `copy`, `usage`, `replyOptions`, `duration`, `steer`), `references` /
|
|
113
|
+
`onReferencesChange`, `voiceNoteText`, `placeholder`, `avatar`, `initialTurns`, `loadOlder` (earlier
|
|
114
|
+
pages of a long thread), the slot `sidePanel`, and the callbacks `onTurnStart`, `onTitle`, `onState`,
|
|
115
|
+
`onEvent` (every raw AG-UI event), `onSend`. The agent's shared state (`STATE_SNAPSHOT` +
|
|
116
|
+
`STATE_DELTA`) is `pilot.state.agentState`, and each turn's `CUSTOM` events are `turn.custom` (what
|
|
117
|
+
`renderDeliveries` draws). To read the state outside the chat, call `usePilot(transport)` yourself
|
|
118
|
+
and pass the result as `pilot`. Every piece is
|
|
116
119
|
exported (`AgentBubble`, `AgentSteps`, `Composer`, `ConfirmCard`, `Markdown`, …) if you want to build
|
|
117
120
|
your own layout.
|
|
118
121
|
|
|
@@ -65,10 +65,14 @@ function createAgUiTransport(opts) {
|
|
|
65
65
|
throw new PilotHttpError(json?.message ?? json?.error ?? `Error ${res.status}`, res.status, json?.error, retryAfter);
|
|
66
66
|
}
|
|
67
67
|
if (!res.body) return;
|
|
68
|
+
const serverDate = Date.parse(res.headers.get("date") ?? "");
|
|
69
|
+
const skew = Number.isNaN(serverDate) ? null : serverDate - Date.now();
|
|
68
70
|
await readSse(res.body, ({ data }) => {
|
|
69
71
|
if (!data) return;
|
|
70
72
|
try {
|
|
71
|
-
|
|
73
|
+
const event = JSON.parse(data);
|
|
74
|
+
if (skew !== null && typeof event.timestamp !== "number") event.timestamp = Date.now() + skew;
|
|
75
|
+
onEvent(event);
|
|
72
76
|
} catch {
|
|
73
77
|
}
|
|
74
78
|
});
|
|
@@ -1,7 +1,120 @@
|
|
|
1
|
+
// src/core/jsonPatch.ts
|
|
2
|
+
function parsePath(path) {
|
|
3
|
+
if (path === "") return [];
|
|
4
|
+
if (!path.startsWith("/")) throw new Error(`bad path ${path}`);
|
|
5
|
+
return path.slice(1).split("/").map((k) => k.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
6
|
+
}
|
|
7
|
+
function get(doc, keys) {
|
|
8
|
+
let cur = doc;
|
|
9
|
+
for (const k of keys) {
|
|
10
|
+
if (Array.isArray(cur)) cur = cur[Number(k)];
|
|
11
|
+
else if (cur && typeof cur === "object") cur = cur[k];
|
|
12
|
+
else return void 0;
|
|
13
|
+
}
|
|
14
|
+
return cur;
|
|
15
|
+
}
|
|
16
|
+
function edit(doc, keys, fn) {
|
|
17
|
+
if (!keys.length) throw new Error("root");
|
|
18
|
+
const [k, ...rest] = keys;
|
|
19
|
+
if (!rest.length) return fn(doc, k);
|
|
20
|
+
if (Array.isArray(doc)) {
|
|
21
|
+
const i = Number(k);
|
|
22
|
+
if (!(i in doc)) throw new Error(`missing ${k}`);
|
|
23
|
+
const out = [...doc];
|
|
24
|
+
out[i] = edit(doc[i], rest, fn);
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
if (doc && typeof doc === "object" && k in doc) {
|
|
28
|
+
return { ...doc, [k]: edit(doc[k], rest, fn) };
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`missing ${k}`);
|
|
31
|
+
}
|
|
32
|
+
function add(doc, keys, value) {
|
|
33
|
+
if (!keys.length) return value;
|
|
34
|
+
return edit(doc, keys, (parent, k) => {
|
|
35
|
+
if (Array.isArray(parent)) {
|
|
36
|
+
const i = k === "-" ? parent.length : Number(k);
|
|
37
|
+
if (!Number.isInteger(i) || i < 0 || i > parent.length) throw new Error(`index ${k}`);
|
|
38
|
+
const out = [...parent];
|
|
39
|
+
out.splice(i, 0, value);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
if (parent && typeof parent === "object") return { ...parent, [k]: value };
|
|
43
|
+
throw new Error("not a container");
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function remove(doc, keys) {
|
|
47
|
+
return edit(doc, keys, (parent, k) => {
|
|
48
|
+
if (Array.isArray(parent)) {
|
|
49
|
+
const i = Number(k);
|
|
50
|
+
if (!(i in parent)) throw new Error(`index ${k}`);
|
|
51
|
+
return parent.filter((_, j) => j !== i);
|
|
52
|
+
}
|
|
53
|
+
if (parent && typeof parent === "object" && k in parent) {
|
|
54
|
+
const { [k]: _, ...rest } = parent;
|
|
55
|
+
return rest;
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`missing ${k}`);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function replace(doc, keys, value) {
|
|
61
|
+
if (!keys.length) return value;
|
|
62
|
+
return edit(doc, keys, (parent, k) => {
|
|
63
|
+
if (Array.isArray(parent)) {
|
|
64
|
+
const i = Number(k);
|
|
65
|
+
if (!(i in parent)) throw new Error(`index ${k}`);
|
|
66
|
+
const out = [...parent];
|
|
67
|
+
out[i] = value;
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
if (parent && typeof parent === "object" && k in parent) return { ...parent, [k]: value };
|
|
71
|
+
throw new Error(`missing ${k}`);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
var equal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
75
|
+
function applyJsonPatch(doc, ops) {
|
|
76
|
+
let cur = doc;
|
|
77
|
+
try {
|
|
78
|
+
for (const o of ops) {
|
|
79
|
+
const keys = parsePath(o.path);
|
|
80
|
+
if (o.op === "add") cur = add(cur, keys, o.value);
|
|
81
|
+
else if (o.op === "remove") cur = remove(cur, keys);
|
|
82
|
+
else if (o.op === "replace") cur = replace(cur, keys, o.value);
|
|
83
|
+
else if (o.op === "move" || o.op === "copy") {
|
|
84
|
+
const from = parsePath(o.from ?? "");
|
|
85
|
+
const v = get(cur, from);
|
|
86
|
+
if (v === void 0) throw new Error(`missing ${o.from}`);
|
|
87
|
+
if (o.op === "move") cur = remove(cur, from);
|
|
88
|
+
cur = add(cur, keys, v);
|
|
89
|
+
} else if (o.op === "test") {
|
|
90
|
+
if (!equal(get(cur, keys), o.value)) throw new Error(`test ${o.path}`);
|
|
91
|
+
} else throw new Error(`op ${o.op}`);
|
|
92
|
+
}
|
|
93
|
+
return { doc: cur, ok: true };
|
|
94
|
+
} catch {
|
|
95
|
+
return { doc, ok: false };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
1
99
|
// src/core/reducer.ts
|
|
2
100
|
function createPilotState(turns = []) {
|
|
3
|
-
return {
|
|
101
|
+
return {
|
|
102
|
+
turns,
|
|
103
|
+
busy: false,
|
|
104
|
+
error: null,
|
|
105
|
+
notice: null,
|
|
106
|
+
usage: null,
|
|
107
|
+
permissions: [],
|
|
108
|
+
turnStartedAt: 0,
|
|
109
|
+
runId: null,
|
|
110
|
+
agentState: {},
|
|
111
|
+
step: null,
|
|
112
|
+
history: null,
|
|
113
|
+
olderCount: 0,
|
|
114
|
+
effects: []
|
|
115
|
+
};
|
|
4
116
|
}
|
|
117
|
+
var PROTOCOL_CUSTOM = /* @__PURE__ */ new Set(["ghosty.title", "ghosty.injected", "ghosty.history"]);
|
|
5
118
|
function patchAssistant(s, patch) {
|
|
6
119
|
const turns = [...s.turns];
|
|
7
120
|
const last = turns[turns.length - 1];
|
|
@@ -36,7 +149,7 @@ function upsertTool(s, id, patch) {
|
|
|
36
149
|
}
|
|
37
150
|
var APPROVE = { optionId: "approve", name: "Aprobar", kind: "allow_once" };
|
|
38
151
|
var REJECT = { optionId: "reject", name: "Rechazar", kind: "reject_once" };
|
|
39
|
-
function toPermission(i, turns) {
|
|
152
|
+
function toPermission(i, turns, clockOffset = 0) {
|
|
40
153
|
const meta = i.metadata ?? {};
|
|
41
154
|
const toolName2 = (typeof meta.tool === "string" ? meta.tool : void 0) ?? turns.flatMap((t) => t.tools ?? []).find((x) => x.id === i.toolCallId)?.title;
|
|
42
155
|
return {
|
|
@@ -46,9 +159,14 @@ function toPermission(i, turns) {
|
|
|
46
159
|
...i.toolCallId ? { toolCallId: i.toolCallId } : {},
|
|
47
160
|
reason: i.reason,
|
|
48
161
|
options: [REJECT, APPROVE],
|
|
49
|
-
...i.expiresAt ? { expiresAt: i.expiresAt } : {}
|
|
162
|
+
...i.expiresAt ? { expiresAt: i.expiresAt, deadline: expiryMs(i.expiresAt) + clockOffset } : {}
|
|
50
163
|
};
|
|
51
164
|
}
|
|
165
|
+
function closeOpenTools(turns, status) {
|
|
166
|
+
return turns.map(
|
|
167
|
+
(t) => t.tools?.some((x) => x.status === "in_progress") ? { ...t, tools: t.tools.map((x) => x.status === "in_progress" ? { ...x, status } : x) } : t
|
|
168
|
+
);
|
|
169
|
+
}
|
|
52
170
|
function textOf(content) {
|
|
53
171
|
if (typeof content === "string") return content;
|
|
54
172
|
if (Array.isArray(content)) return content.map((p) => p && typeof p === "object" && p.type === "text" ? p.text ?? "" : "").join("");
|
|
@@ -67,9 +185,9 @@ function attachmentsOf(content) {
|
|
|
67
185
|
function messagesToTurns(messages) {
|
|
68
186
|
const turns = [];
|
|
69
187
|
let cur = null;
|
|
70
|
-
const assistant = () => {
|
|
188
|
+
const assistant = (id) => {
|
|
71
189
|
if (!cur) {
|
|
72
|
-
cur = { role: "assistant", text: "" };
|
|
190
|
+
cur = { ...id ? { id } : {}, role: "assistant", text: "" };
|
|
73
191
|
turns.push(cur);
|
|
74
192
|
}
|
|
75
193
|
return cur;
|
|
@@ -77,9 +195,12 @@ function messagesToTurns(messages) {
|
|
|
77
195
|
for (const m of messages) {
|
|
78
196
|
if (m.role === "user") {
|
|
79
197
|
cur = null;
|
|
80
|
-
turns.push({ role: "user", text: textOf(m.content), ...attachmentsOf(m.content) ? { attachments: attachmentsOf(m.content) } : {} });
|
|
198
|
+
turns.push({ ...m.id ? { id: m.id } : {}, role: "user", text: textOf(m.content), ...attachmentsOf(m.content) ? { attachments: attachmentsOf(m.content) } : {} });
|
|
81
199
|
} else if (m.role === "assistant") {
|
|
82
200
|
const t = assistant();
|
|
201
|
+
if (!t.id && m.id) t.id = m.id;
|
|
202
|
+
const custom = m.custom;
|
|
203
|
+
if (Array.isArray(custom) && custom.length) t.custom = [...t.custom ?? [], ...custom.filter((c) => c && typeof c.name === "string")];
|
|
83
204
|
const text = m.content ?? "";
|
|
84
205
|
if (text) t.text = t.text ? `${t.text}
|
|
85
206
|
|
|
@@ -89,11 +210,11 @@ ${text}` : text;
|
|
|
89
210
|
if (t.text.trim() && t.cuts?.[t.cuts.length - 1] !== t.text.length) t.cuts = [...t.cuts ?? [], t.text.length];
|
|
90
211
|
}
|
|
91
212
|
} else if (m.role === "tool") {
|
|
92
|
-
const t = assistant();
|
|
213
|
+
const t = assistant(m.id);
|
|
93
214
|
const failed = Boolean(m.error);
|
|
94
215
|
t.tools = (t.tools ?? []).map((x) => x.id === m.toolCallId ? { ...x, status: failed ? "failed" : "completed" } : x);
|
|
95
216
|
} else if (m.role === "reasoning") {
|
|
96
|
-
const t = assistant();
|
|
217
|
+
const t = assistant(m.id);
|
|
97
218
|
t.thought = (t.thought ?? "") + textOf(m.content);
|
|
98
219
|
}
|
|
99
220
|
}
|
|
@@ -101,27 +222,47 @@ ${text}` : text;
|
|
|
101
222
|
}
|
|
102
223
|
function applySnapshot(s, messages) {
|
|
103
224
|
const fresh = messagesToTurns(messages);
|
|
104
|
-
|
|
225
|
+
let prefix = [];
|
|
226
|
+
if (s.olderCount && fresh[0]?.id) {
|
|
227
|
+
const at = s.turns.findIndex((t) => t.id === fresh[0].id);
|
|
228
|
+
if (at >= 0) prefix = s.turns.slice(0, at);
|
|
229
|
+
}
|
|
230
|
+
const keepOlder = prefix.length > 0;
|
|
231
|
+
const old = keepOlder ? s.turns.slice(prefix.length) : s.turns.slice(s.olderCount);
|
|
232
|
+
const lastIndex = fresh.length - 1;
|
|
105
233
|
const turns = fresh.map((t, i) => {
|
|
106
234
|
const o = old[i];
|
|
107
|
-
|
|
235
|
+
const live = s.busy && i === lastIndex && t.role === "assistant";
|
|
236
|
+
const tools = t.tools?.map((x) => {
|
|
237
|
+
const prev = o?.role === t.role ? o.tools?.find((y) => y.id === x.id) : void 0;
|
|
238
|
+
const status = x.status === "pending" && (prev?.status ?? (live ? "in_progress" : void 0));
|
|
239
|
+
return { ...prev, ...x, ...status ? { status } : {} };
|
|
240
|
+
});
|
|
241
|
+
const base = tools ? { ...t, tools } : t;
|
|
242
|
+
if (!o || o.role !== t.role) return base;
|
|
108
243
|
return {
|
|
109
|
-
...
|
|
244
|
+
...base,
|
|
110
245
|
...o.seconds !== void 0 ? { seconds: o.seconds } : {},
|
|
111
246
|
...o.usage ? { usage: o.usage } : {},
|
|
112
247
|
...o.decisions ? { decisions: o.decisions } : {},
|
|
113
248
|
...o.thought && !t.thought ? { thought: o.thought } : {},
|
|
114
249
|
...o.attachments && !t.attachments ? { attachments: o.attachments } : {},
|
|
115
|
-
...o.
|
|
250
|
+
...o.custom && !t.custom ? { custom: o.custom } : {}
|
|
116
251
|
};
|
|
117
252
|
});
|
|
118
|
-
return {
|
|
253
|
+
return {
|
|
254
|
+
...s,
|
|
255
|
+
turns: [...prefix, ...turns],
|
|
256
|
+
olderCount: prefix.length,
|
|
257
|
+
history: keepOlder ? s.history : s.historyNext ?? null,
|
|
258
|
+
historyNext: void 0
|
|
259
|
+
};
|
|
119
260
|
}
|
|
120
261
|
function reduceAgUiEvent(prev, e, now = Date.now()) {
|
|
121
262
|
const s = prev.effects.length ? { ...prev, effects: [] } : prev;
|
|
122
263
|
switch (e.type) {
|
|
123
264
|
case "RUN_STARTED":
|
|
124
|
-
return { ...s, busy: true, error: null, runId: e.runId ?? s.runId };
|
|
265
|
+
return { ...s, busy: true, error: null, step: null, runId: e.runId ?? s.runId };
|
|
125
266
|
case "TEXT_MESSAGE_START":
|
|
126
267
|
if (e.role && e.role !== "assistant") return s;
|
|
127
268
|
return patchAssistant(s, (t) => ({ ...t, text: t.text && !t.text.endsWith("\n") ? `${t.text}
|
|
@@ -164,18 +305,40 @@ function reduceAgUiEvent(prev, e, now = Date.now()) {
|
|
|
164
305
|
return finish(s, e, now);
|
|
165
306
|
case "RUN_ERROR":
|
|
166
307
|
return finish({ ...s, error: typeof e.message === "string" ? e.message : "El agente fall\xF3." }, e, now, true);
|
|
308
|
+
case "STEP_STARTED":
|
|
309
|
+
return typeof e.stepName === "string" ? { ...s, step: e.stepName } : s;
|
|
310
|
+
case "STEP_FINISHED":
|
|
311
|
+
return s.step === e.stepName ? { ...s, step: null } : s;
|
|
312
|
+
case "STATE_SNAPSHOT": {
|
|
313
|
+
const agentState = e.snapshot && typeof e.snapshot === "object" ? e.snapshot : {};
|
|
314
|
+
return { ...s, agentState, effects: [{ type: "state", state: agentState }] };
|
|
315
|
+
}
|
|
316
|
+
case "STATE_DELTA": {
|
|
317
|
+
if (!Array.isArray(e.delta)) return s;
|
|
318
|
+
const { doc, ok } = applyJsonPatch(s.agentState, e.delta);
|
|
319
|
+
return ok ? { ...s, agentState: doc, effects: [{ type: "state", state: doc }] } : s;
|
|
320
|
+
}
|
|
167
321
|
case "CUSTOM":
|
|
168
322
|
if (e.name === "ghosty.title") return { ...s, effects: [{ type: "title", title: e.value?.title }] };
|
|
169
|
-
return s;
|
|
323
|
+
if (e.name === "ghosty.history") return { ...s, historyNext: toHistory(e.value) };
|
|
324
|
+
if (typeof e.name !== "string" || PROTOCOL_CUSTOM.has(e.name)) return s;
|
|
325
|
+
return patchAssistant(s, (t) => ({ ...t, custom: [...t.custom ?? [], { name: e.name, value: e.value, ...typeof e.timestamp === "number" ? { timestamp: e.timestamp } : {} }] }));
|
|
170
326
|
default:
|
|
171
327
|
return s;
|
|
172
328
|
}
|
|
173
329
|
}
|
|
330
|
+
function toHistory(v) {
|
|
331
|
+
if (!v || typeof v !== "object") return null;
|
|
332
|
+
const h = v;
|
|
333
|
+
return { hasMoreBefore: h.hasMoreBefore === true, ...typeof h.before === "string" ? { before: h.before } : {} };
|
|
334
|
+
}
|
|
174
335
|
function finish(s0, e, now, failed = false) {
|
|
175
|
-
let s = { ...s0, busy: false };
|
|
336
|
+
let s = { ...s0, busy: false, step: null };
|
|
176
337
|
const outcome = e.outcome;
|
|
177
338
|
const interrupted = outcome?.type === "interrupt" && Array.isArray(outcome.interrupts) && outcome.interrupts.length > 0;
|
|
178
|
-
|
|
339
|
+
const clockOffset = typeof e.timestamp === "number" ? now - e.timestamp : 0;
|
|
340
|
+
s.permissions = interrupted ? outcome.interrupts.map((i) => toPermission(i, s.turns, clockOffset)) : [];
|
|
341
|
+
if (outcome?.type === "cancelled" || failed) s = { ...s, turns: closeOpenTools(s.turns, failed ? "failed" : "cancelled") };
|
|
179
342
|
if (interrupted) {
|
|
180
343
|
const waiting = new Set(s.permissions.map((p) => p.toolCallId).filter(Boolean));
|
|
181
344
|
if (waiting.size) {
|
|
@@ -207,11 +370,19 @@ function beginSend(prev, text, attachments = [], now = Date.now()) {
|
|
|
207
370
|
const turns = text || attachments.length ? [...prev.turns, { role: "user", text, ...attachments.length ? { attachments } : {} }] : prev.turns;
|
|
208
371
|
return { ...prev, turns, busy: true, error: null, notice: null, turnStartedAt: now, effects: [{ type: "turn-start" }] };
|
|
209
372
|
}
|
|
373
|
+
function beginSteer(prev, text) {
|
|
374
|
+
return { ...prev, turns: [...prev.turns, { role: "user", text }], notice: null, effects: [] };
|
|
375
|
+
}
|
|
376
|
+
function prependHistory(prev, messages, page) {
|
|
377
|
+
const have = new Set(prev.turns.map((t) => t.id).filter(Boolean));
|
|
378
|
+
const older = messagesToTurns(messages).filter((t) => !t.id || !have.has(t.id));
|
|
379
|
+
return { ...prev, turns: [...older, ...prev.turns], olderCount: prev.olderCount + older.length, history: page, effects: [] };
|
|
380
|
+
}
|
|
210
381
|
function sendFailed(prev, message) {
|
|
211
382
|
return { ...prev, busy: false, turnStartedAt: 0, error: message, effects: [] };
|
|
212
383
|
}
|
|
213
384
|
function stopTurn(prev) {
|
|
214
|
-
return { ...prev, busy: false, turnStartedAt: 0, effects: [] };
|
|
385
|
+
return { ...prev, busy: false, step: null, turnStartedAt: 0, turns: closeOpenTools(prev.turns, "cancelled"), effects: [] };
|
|
215
386
|
}
|
|
216
387
|
function dropLastExchange(prev) {
|
|
217
388
|
const turns = [...prev.turns];
|
|
@@ -311,7 +482,7 @@ function cleanReplyOption(t) {
|
|
|
311
482
|
}
|
|
312
483
|
|
|
313
484
|
// src/core/steps.ts
|
|
314
|
-
var isRunning = (t) => t.status !== "completed" && t.status !== "failed";
|
|
485
|
+
var isRunning = (t) => t.status !== "completed" && t.status !== "failed" && t.status !== "cancelled";
|
|
315
486
|
function toolNames(t) {
|
|
316
487
|
const title = t.title ?? "";
|
|
317
488
|
if (!title) return [];
|
|
@@ -444,12 +615,15 @@ var TOOL_VERBS_EN = {
|
|
|
444
615
|
};
|
|
445
616
|
|
|
446
617
|
export {
|
|
618
|
+
applyJsonPatch,
|
|
447
619
|
createPilotState,
|
|
448
620
|
argsDetail,
|
|
449
621
|
toPermission,
|
|
450
622
|
messagesToTurns,
|
|
451
623
|
reduceAgUiEvent,
|
|
452
624
|
beginSend,
|
|
625
|
+
beginSteer,
|
|
626
|
+
prependHistory,
|
|
453
627
|
sendFailed,
|
|
454
628
|
stopTurn,
|
|
455
629
|
dropLastExchange,
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
|
-
export { argsDetail, beginSend, createPilotState, dropLastExchange, expiryMs, lastUserTurn, messagesToTurns, reduceAgUiEvent, resolvePermission, sendFailed, stopTurn, toPermission, } from "./reducer";
|
|
2
|
+
export { argsDetail, beginSend, beginSteer, createPilotState, dropLastExchange, expiryMs, lastUserTurn, messagesToTurns, prependHistory, reduceAgUiEvent, resolvePermission, sendFailed, stopTurn, toPermission, } from "./reducer";
|
|
3
|
+
export { applyJsonPatch, type PatchOp } from "./jsonPatch";
|
|
3
4
|
export { isNarration, narrationToMarkdown, splitNarration } from "./narration";
|
|
4
5
|
export { cleanReplyOption, extractReplyOptions, type ReplyOptions } from "./options";
|
|
5
6
|
export { TOOL_VERBS_EN, TOOL_VERBS_ES, groupSteps, isRunning, stepLabel, summarizeTools, toolName, toolNames, type ToolVerb, type ToolVerbs, } from "./steps";
|
package/dist/core/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TOOL_VERBS_EN,
|
|
3
3
|
TOOL_VERBS_ES,
|
|
4
|
+
applyJsonPatch,
|
|
4
5
|
argsDetail,
|
|
5
6
|
beginSend,
|
|
7
|
+
beginSteer,
|
|
6
8
|
cleanReplyOption,
|
|
7
9
|
createPilotState,
|
|
8
10
|
dropLastExchange,
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
lastUserTurn,
|
|
15
17
|
messagesToTurns,
|
|
16
18
|
narrationToMarkdown,
|
|
19
|
+
prependHistory,
|
|
17
20
|
reduceAgUiEvent,
|
|
18
21
|
resolvePermission,
|
|
19
22
|
sendFailed,
|
|
@@ -24,7 +27,7 @@ import {
|
|
|
24
27
|
toPermission,
|
|
25
28
|
toolName,
|
|
26
29
|
toolNames
|
|
27
|
-
} from "../chunks/chunk-
|
|
30
|
+
} from "../chunks/chunk-VK54J63C.js";
|
|
28
31
|
import {
|
|
29
32
|
applyReferences,
|
|
30
33
|
cleanUserText,
|
|
@@ -34,9 +37,11 @@ import {
|
|
|
34
37
|
export {
|
|
35
38
|
TOOL_VERBS_EN,
|
|
36
39
|
TOOL_VERBS_ES,
|
|
40
|
+
applyJsonPatch,
|
|
37
41
|
applyReferences,
|
|
38
42
|
argsDetail,
|
|
39
43
|
beginSend,
|
|
44
|
+
beginSteer,
|
|
40
45
|
cleanReplyOption,
|
|
41
46
|
cleanUserText,
|
|
42
47
|
createPilotState,
|
|
@@ -50,6 +55,7 @@ export {
|
|
|
50
55
|
lastUserTurn,
|
|
51
56
|
messagesToTurns,
|
|
52
57
|
narrationToMarkdown,
|
|
58
|
+
prependHistory,
|
|
53
59
|
reduceAgUiEvent,
|
|
54
60
|
resolvePermission,
|
|
55
61
|
sendFailed,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface PatchOp {
|
|
2
|
+
op: "add" | "remove" | "replace" | "move" | "copy" | "test";
|
|
3
|
+
path: string;
|
|
4
|
+
value?: unknown;
|
|
5
|
+
from?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Aplica `ops` en orden. Si una falla, regresa el documento original y `ok: false`. */
|
|
8
|
+
export declare function applyJsonPatch<T>(doc: T, ops: PatchOp[]): {
|
|
9
|
+
doc: T;
|
|
10
|
+
ok: boolean;
|
|
11
|
+
};
|
package/dist/core/reducer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Interrupt, Message } from "@ag-ui/core";
|
|
2
|
-
import type { Attachment, Decision, PermissionRequest, PilotState, Turn } from "./types";
|
|
2
|
+
import type { Attachment, Decision, HistoryPage, PermissionRequest, PilotState, Turn } from "./types";
|
|
3
3
|
export declare function createPilotState(turns?: Turn[]): PilotState;
|
|
4
4
|
type Ev = {
|
|
5
5
|
type: string;
|
|
@@ -8,7 +8,7 @@ type Ev = {
|
|
|
8
8
|
/** Los argumentos de una tool en corto, para el renglón del paso: «job: purge-notifications». */
|
|
9
9
|
export declare function argsDetail(json?: string): string | undefined;
|
|
10
10
|
/** Un `Interrupt` de AG-UI como tarjeta: título, herramienta (por su toolCallId) y vencimiento. */
|
|
11
|
-
export declare function toPermission(i: Interrupt, turns: Turn[]): PermissionRequest;
|
|
11
|
+
export declare function toPermission(i: Interrupt, turns: Turn[], clockOffset?: number): PermissionRequest;
|
|
12
12
|
/**
|
|
13
13
|
* El hilo guardado (mensajes AG-UI) como turnos: cada mensaje del usuario abre uno; lo que sigue del
|
|
14
14
|
* asistente y de las herramientas se junta en un turno, con sus pasos y su narración.
|
|
@@ -18,6 +18,16 @@ export declare function messagesToTurns(messages: Message[]): Turn[];
|
|
|
18
18
|
export declare function reduceAgUiEvent(prev: PilotState, e: Ev, now?: number): PilotState;
|
|
19
19
|
/** El usuario manda un mensaje (o contesta una tarjeta: `text` vacío, sin burbuja). */
|
|
20
20
|
export declare function beginSend(prev: PilotState, text: string, attachments?: Attachment[], now?: number): PilotState;
|
|
21
|
+
/**
|
|
22
|
+
* Steer: un mensaje del usuario mientras el run sigue. Su burbuja entra en el hilo y lo que el agente
|
|
23
|
+
* diga después va en una respuesta nueva debajo; el run (y su reloj) siguen siendo los mismos.
|
|
24
|
+
*/
|
|
25
|
+
export declare function beginSteer(prev: PilotState, text: string): PilotState;
|
|
26
|
+
/**
|
|
27
|
+
* Mensajes más viejos (`loadOlder`) delante del hilo. Las páginas empiezan en un mensaje del usuario;
|
|
28
|
+
* lo que ya se tenía (mismo id) no se repite.
|
|
29
|
+
*/
|
|
30
|
+
export declare function prependHistory(prev: PilotState, messages: Message[], page: HistoryPage | null): PilotState;
|
|
21
31
|
/** El servidor no aceptó el encargo (HTTP): se DICE, para que no se quede pensando. */
|
|
22
32
|
export declare function sendFailed(prev: PilotState, message: string): PilotState;
|
|
23
33
|
/** Detener: el cliente cortó la conexión (el servidor corta el run). */
|
package/dist/core/types.d.ts
CHANGED
|
@@ -15,7 +15,8 @@ export interface ToolEntry {
|
|
|
15
15
|
/** El nombre de la herramienta (`toolCallName`): es la llave de los verbos de la app. */
|
|
16
16
|
title?: string;
|
|
17
17
|
kind?: string;
|
|
18
|
-
/** `in_progress` mientras corre; `completed` | `failed` con su resultado; `pending` = espera
|
|
18
|
+
/** `in_progress` mientras corre; `completed` | `failed` con su resultado; `pending` = espera
|
|
19
|
+
* aprobación; `cancelled` = el run se detuvo antes de que terminara. */
|
|
19
20
|
status?: string;
|
|
20
21
|
path?: string;
|
|
21
22
|
/** Los argumentos, en corto («job: purge-notifications»). */
|
|
@@ -37,7 +38,21 @@ export interface Decision {
|
|
|
37
38
|
status: "approved" | "rejected" | "expired" | "cancelled";
|
|
38
39
|
detail?: string;
|
|
39
40
|
}
|
|
41
|
+
/** Un evento `CUSTOM` del turno (entregas de archivos, avisos propios de la app). */
|
|
42
|
+
export interface CustomEntry {
|
|
43
|
+
name: string;
|
|
44
|
+
value: unknown;
|
|
45
|
+
timestamp?: number;
|
|
46
|
+
}
|
|
47
|
+
/** Hay mensajes más viejos que los del snapshot (`CUSTOM ghosty.history`). */
|
|
48
|
+
export interface HistoryPage {
|
|
49
|
+
hasMoreBefore: boolean;
|
|
50
|
+
/** Cursor opaco del servidor: se le regresa a `loadOlder`. */
|
|
51
|
+
before?: string;
|
|
52
|
+
}
|
|
40
53
|
export interface Turn {
|
|
54
|
+
/** El id del primer mensaje AG-UI del turno (sólo los que vienen de un snapshot). */
|
|
55
|
+
id?: string;
|
|
41
56
|
role: "user" | "assistant";
|
|
42
57
|
text: string;
|
|
43
58
|
/** Cuánto tardó, en segundos (sólo los turnos vistos en vivo). */
|
|
@@ -50,6 +65,9 @@ export interface Turn {
|
|
|
50
65
|
usage?: Usage;
|
|
51
66
|
/** Lo que el usuario decidió en las tarjetas de este turno (sólo en vivo). */
|
|
52
67
|
decisions?: Decision[];
|
|
68
|
+
/** Los `CUSTOM` del turno (p. ej. `ghosty.delivery`), para `renderDeliveries`. Del snapshot salen
|
|
69
|
+
* del campo `custom` de cada mensaje del asistente. */
|
|
70
|
+
custom?: CustomEntry[];
|
|
53
71
|
}
|
|
54
72
|
/** Una opción de la tarjeta de aprobación. */
|
|
55
73
|
export interface PermissionOption {
|
|
@@ -67,8 +85,14 @@ export interface PermissionRequest {
|
|
|
67
85
|
toolCallId?: string;
|
|
68
86
|
reason?: string;
|
|
69
87
|
options: PermissionOption[];
|
|
70
|
-
/** Hasta cuándo se puede contestar (ISO). */
|
|
88
|
+
/** Hasta cuándo se puede contestar (ISO, reloj del SERVIDOR). */
|
|
71
89
|
expiresAt?: string | number;
|
|
90
|
+
/**
|
|
91
|
+
* El mismo vencimiento en el reloj de ESTE navegador (epoch ms): `expiresAt` menos la hora del
|
|
92
|
+
* servidor al mandar el evento, más la hora local al recibirlo. Así un reloj local desfasado no
|
|
93
|
+
* adelanta ni atrasa la cuenta regresiva.
|
|
94
|
+
*/
|
|
95
|
+
deadline?: number;
|
|
72
96
|
}
|
|
73
97
|
/** Lo que una transición pide hacer FUERA del estado (avisos a la app). */
|
|
74
98
|
export type PilotEffect = {
|
|
@@ -83,6 +107,9 @@ export type PilotEffect = {
|
|
|
83
107
|
} | {
|
|
84
108
|
type: "title";
|
|
85
109
|
title?: string;
|
|
110
|
+
} | {
|
|
111
|
+
type: "state";
|
|
112
|
+
state: Record<string, unknown>;
|
|
86
113
|
};
|
|
87
114
|
export interface PilotState {
|
|
88
115
|
turns: Turn[];
|
|
@@ -96,6 +123,16 @@ export interface PilotState {
|
|
|
96
123
|
/** Cuándo empezó el turno que ESTE cliente encargó (0 = ninguno). */
|
|
97
124
|
turnStartedAt: number;
|
|
98
125
|
runId: string | null;
|
|
126
|
+
/** El estado compartido del agente (`STATE_SNAPSHOT`, con `STATE_DELTA` aplicado encima). */
|
|
127
|
+
agentState: Record<string, unknown>;
|
|
128
|
+
/** El paso con nombre en curso (`STEP_STARTED`), p. ej. `waking`. */
|
|
129
|
+
step: string | null;
|
|
130
|
+
/** Si hay mensajes más viejos por cargar (`loadOlder`). */
|
|
131
|
+
history: HistoryPage | null;
|
|
132
|
+
/** Cuántos de `turns` llegaron con `loadOlder` (van antes del snapshot). */
|
|
133
|
+
olderCount: number;
|
|
134
|
+
/** El `ghosty.history` que acompaña al snapshot que viene. */
|
|
135
|
+
historyNext?: HistoryPage | null;
|
|
99
136
|
/** Efectos de la ÚLTIMA transición. El hook los ejecuta y los descarta. */
|
|
100
137
|
effects: PilotEffect[];
|
|
101
138
|
}
|
package/dist/react/Piloto.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface PilotoFeatures {
|
|
|
19
19
|
replyOptions?: boolean;
|
|
20
20
|
/** «· 12 s» junto a la frase de espera. Default: sí. */
|
|
21
21
|
duration?: boolean;
|
|
22
|
+
/** Mandar con el run vivo (steer). Default: lo que diga el agente en `state.caps.steer`. */
|
|
23
|
+
steer?: boolean;
|
|
22
24
|
}
|
|
23
25
|
export interface PilotoSlots {
|
|
24
26
|
/** Lo que la app pinte bajo cada respuesta (archivos, tarjetas propias). */
|
|
@@ -31,6 +33,8 @@ export interface PilotoSlots {
|
|
|
31
33
|
headerActions?: ReactNode;
|
|
32
34
|
/** Pegado arriba del composer (un aviso, un banner). */
|
|
33
35
|
aboveComposer?: ReactNode;
|
|
36
|
+
/** Un panel junto al chat (subagentes, vista previa), con el estado del agente. */
|
|
37
|
+
sidePanel?: (agentState: Record<string, unknown>) => ReactNode;
|
|
34
38
|
}
|
|
35
39
|
export interface PilotoProps extends UsePilotOptions {
|
|
36
40
|
transport: PilotTransport | null;
|
package/dist/react/Status.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Los tres puntitos de «escribiendo» (color y animación en CSS: `gc-typing`). */
|
|
2
2
|
export declare function TypingDots(): import("react").JSX.Element;
|
|
3
|
-
export declare function WaitLabel({ since, showDuration, }: {
|
|
3
|
+
export declare function WaitLabel({ since, showDuration, step, }: {
|
|
4
|
+
/** El paso con nombre en curso (`STEP_STARTED`): si `strings.steps` lo nombra, esa es la frase. */
|
|
5
|
+
step?: string | null;
|
|
4
6
|
/** Desde que se MANDÓ el mensaje (no desde que se montó la etiqueta). */
|
|
5
7
|
since?: number | null;
|
|
6
8
|
showDuration?: boolean;
|
package/dist/react/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TOOL_VERBS_EN,
|
|
3
3
|
TOOL_VERBS_ES,
|
|
4
|
+
applyJsonPatch,
|
|
4
5
|
argsDetail,
|
|
5
6
|
beginSend,
|
|
7
|
+
beginSteer,
|
|
6
8
|
cleanReplyOption,
|
|
7
9
|
createPilotState,
|
|
8
10
|
dropLastExchange,
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
lastUserTurn,
|
|
15
17
|
messagesToTurns,
|
|
16
18
|
narrationToMarkdown,
|
|
19
|
+
prependHistory,
|
|
17
20
|
reduceAgUiEvent,
|
|
18
21
|
resolvePermission,
|
|
19
22
|
sendFailed,
|
|
@@ -24,12 +27,12 @@ import {
|
|
|
24
27
|
toPermission,
|
|
25
28
|
toolName,
|
|
26
29
|
toolNames
|
|
27
|
-
} from "../chunks/chunk-
|
|
30
|
+
} from "../chunks/chunk-VK54J63C.js";
|
|
28
31
|
import {
|
|
29
32
|
PilotHttpError,
|
|
30
33
|
createAgUiTransport,
|
|
31
34
|
readSse
|
|
32
|
-
} from "../chunks/chunk-
|
|
35
|
+
} from "../chunks/chunk-TMEHQZWL.js";
|
|
33
36
|
import {
|
|
34
37
|
applyReferences,
|
|
35
38
|
cleanUserText,
|
|
@@ -884,6 +887,9 @@ var STRINGS_ES = {
|
|
|
884
887
|
linkOpen: "Abrir",
|
|
885
888
|
close: "Cerrar",
|
|
886
889
|
goToLatest: "Ir al \xFAltimo mensaje",
|
|
890
|
+
steps: { waking: "Despertando al agente\u2026", session: "Preparando la sesi\xF3n\u2026" },
|
|
891
|
+
loadOlder: "Cargar mensajes anteriores",
|
|
892
|
+
loadingOlder: "Cargando\u2026",
|
|
887
893
|
confirmTitle: "Necesito tu permiso para",
|
|
888
894
|
approve: "Aprobar",
|
|
889
895
|
reject: "Rechazar",
|
|
@@ -949,6 +955,9 @@ var STRINGS_EN = {
|
|
|
949
955
|
linkOpen: "Open",
|
|
950
956
|
close: "Close",
|
|
951
957
|
goToLatest: "Go to latest message",
|
|
958
|
+
steps: { waking: "Waking the agent\u2026", session: "Starting the session\u2026" },
|
|
959
|
+
loadOlder: "Load earlier messages",
|
|
960
|
+
loadingOlder: "Loading\u2026",
|
|
952
961
|
confirmTitle: "I need your permission to",
|
|
953
962
|
approve: "Approve",
|
|
954
963
|
reject: "Reject",
|
|
@@ -1009,7 +1018,7 @@ function ConfirmCard({
|
|
|
1009
1018
|
const { strings: t } = usePiloto();
|
|
1010
1019
|
const [pending, setPending] = useState(null);
|
|
1011
1020
|
const [stamp, setStamp] = useState(null);
|
|
1012
|
-
const time = useRemaining(request.expiresAt);
|
|
1021
|
+
const time = useRemaining(request.deadline ?? request.expiresAt);
|
|
1013
1022
|
const expired = time !== null && time.left <= 0;
|
|
1014
1023
|
const urgent = time !== null && !expired && time.left < 6e4;
|
|
1015
1024
|
useEffect(() => {
|
|
@@ -1448,7 +1457,10 @@ function StepLine({ tool, times, live }) {
|
|
|
1448
1457
|
const spins = isRunning(tool) && live;
|
|
1449
1458
|
const label = stepLabel(tool, toolVerbs, t.step);
|
|
1450
1459
|
return /* @__PURE__ */ jsxs3("li", { className: "gs-paso-in flex min-w-0 items-center gap-2 text-[13px] leading-6 text-text-secondary", children: [
|
|
1451
|
-
/* @__PURE__ */ jsx5("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", "aria-label": spins ? t.running : tool.status === "failed" ? t.failed : t.done, children: spins ? /* @__PURE__ */ jsx5(LoaderCircle, { className: "h-4 w-4 animate-spin text-text-info" }) : tool.status === "
|
|
1460
|
+
/* @__PURE__ */ jsx5("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", "aria-label": spins ? t.running : tool.status === "failed" ? t.failed : t.done, children: spins ? /* @__PURE__ */ jsx5(LoaderCircle, { className: "h-4 w-4 animate-spin text-text-info" }) : tool.status === "cancelled" ? (
|
|
1461
|
+
// El run se detuvo antes de que terminara.
|
|
1462
|
+
/* @__PURE__ */ jsx5(CircleSlash, { className: "h-4 w-4 text-text-tertiary" })
|
|
1463
|
+
) : tool.status === "pending" ? (
|
|
1452
1464
|
// Espera aprobación (el run se detuvo en un interrupt): ni gira ni está lista.
|
|
1453
1465
|
/* @__PURE__ */ jsx5(Clock, { className: "h-4 w-4 text-text-tertiary" })
|
|
1454
1466
|
) : tool.status === "failed" ? /* @__PURE__ */ jsx5(CircleX, { className: "h-4 w-4 text-text-danger" }) : /* @__PURE__ */ jsx5(CircleCheck, { className: "h-4 w-4 text-text-success" }) }),
|
|
@@ -1470,6 +1482,7 @@ var STATUS = {
|
|
|
1470
1482
|
pending: { Icon: Clock, className: "text-text-tertiary", key: "step" },
|
|
1471
1483
|
in_progress: { Icon: LoaderCircle, className: "animate-spin text-text-info", key: "running" },
|
|
1472
1484
|
completed: { Icon: CircleCheck, className: "text-text-success", key: "done" },
|
|
1485
|
+
cancelled: { Icon: CircleSlash, className: "text-text-tertiary", key: "step" },
|
|
1473
1486
|
failed: { Icon: CircleX, className: "text-text-danger", key: "failed" }
|
|
1474
1487
|
};
|
|
1475
1488
|
function ToolRow({ tool }) {
|
|
@@ -2247,7 +2260,8 @@ function TypingDots() {
|
|
|
2247
2260
|
}
|
|
2248
2261
|
function WaitLabel({
|
|
2249
2262
|
since,
|
|
2250
|
-
showDuration = true
|
|
2263
|
+
showDuration = true,
|
|
2264
|
+
step
|
|
2251
2265
|
}) {
|
|
2252
2266
|
const { strings: t } = usePiloto();
|
|
2253
2267
|
const [mounted] = useState7(() => Date.now());
|
|
@@ -2258,7 +2272,8 @@ function WaitLabel({
|
|
|
2258
2272
|
}, []);
|
|
2259
2273
|
const seconds = Math.max(0, Math.floor((now - (since || mounted)) / 1e3));
|
|
2260
2274
|
const phrases = t.thinkingPhrases;
|
|
2261
|
-
const
|
|
2275
|
+
const stepText = step ? t.steps?.[step] : void 0;
|
|
2276
|
+
const what = stepText ? stepText : phrases?.length ? seconds >= STILL_WORKING_S ? t.stillWorking : phrases[Math.floor(seconds / 4) % phrases.length] : t.howItGoes(seconds);
|
|
2262
2277
|
return /* @__PURE__ */ jsx9("div", { className: "flex flex-col gap-0.5 text-sm text-text-secondary", children: /* @__PURE__ */ jsxs7("p", { className: "flex items-center", children: [
|
|
2263
2278
|
/* @__PURE__ */ jsx9(AnimatePresence2, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx9(
|
|
2264
2279
|
motion2.span,
|
|
@@ -2325,6 +2340,7 @@ function usePilot(transport, opts = {}) {
|
|
|
2325
2340
|
if (e.type === "turn-start") o.onTurnStart?.();
|
|
2326
2341
|
else if (e.type === "turn-done") o.onTurnDone?.({ text: e.text, runId: e.runId, seconds: e.seconds, error: e.error, interrupted: e.interrupted });
|
|
2327
2342
|
else if (e.type === "title") o.onTitle?.(e.title);
|
|
2343
|
+
else if (e.type === "state") o.onState?.(e.state);
|
|
2328
2344
|
}
|
|
2329
2345
|
}, []);
|
|
2330
2346
|
const runNow = useCallback2(
|
|
@@ -2344,7 +2360,8 @@ function usePilot(transport, opts = {}) {
|
|
|
2344
2360
|
ctrl.current?.abort();
|
|
2345
2361
|
const c = new AbortController();
|
|
2346
2362
|
ctrl.current = c;
|
|
2347
|
-
|
|
2363
|
+
const steering = typeof input.forwardedProps?.steer === "string";
|
|
2364
|
+
if (!steering && (input.messages.length || input.resume?.length)) liveRun.current = input.runId;
|
|
2348
2365
|
optsRef.current.onSend?.(input);
|
|
2349
2366
|
let ended = false;
|
|
2350
2367
|
try {
|
|
@@ -2368,6 +2385,10 @@ function usePilot(transport, opts = {}) {
|
|
|
2368
2385
|
commit({ ...ref.current, notice: err instanceof Error ? err.message : null, effects: [] });
|
|
2369
2386
|
return runNow({ messages: [] }, true);
|
|
2370
2387
|
}
|
|
2388
|
+
if (steering && !reopening) {
|
|
2389
|
+
commit({ ...ref.current, notice: err instanceof Error && err.message ? err.message : null, effects: [] });
|
|
2390
|
+
return runNow({ messages: [] }, true);
|
|
2391
|
+
}
|
|
2371
2392
|
commit(sendFailed(ref.current, err instanceof Error && err.message ? err.message : "No se pudo hablar con el agente."));
|
|
2372
2393
|
return;
|
|
2373
2394
|
}
|
|
@@ -2386,9 +2407,17 @@ function usePilot(transport, opts = {}) {
|
|
|
2386
2407
|
...ref.current.permissions.filter((p) => !answers.current.has(p.id)).map((p) => ({ interruptId: p.id, status: "cancelled" })),
|
|
2387
2408
|
...[...answers.current.entries()].map(([interruptId, a]) => ({ interruptId, ...a }))
|
|
2388
2409
|
];
|
|
2410
|
+
const steerOn = () => optsRef.current.steer ?? ref.current.agentState.caps?.steer === true;
|
|
2389
2411
|
const send = useCallback2(
|
|
2390
2412
|
async (text, attachments = [], _extra = {}) => {
|
|
2391
|
-
if (!transportRef.current
|
|
2413
|
+
if (!transportRef.current) return;
|
|
2414
|
+
if (ref.current.busy) {
|
|
2415
|
+
const live = liveRun.current ?? ref.current.runId;
|
|
2416
|
+
if (!steerOn() || !live || !text.trim() || attachments.length) return;
|
|
2417
|
+
commit(beginSteer(ref.current, text));
|
|
2418
|
+
await runNow({ messages: [userMessage(text, [])], forwardedProps: { steer: live } });
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2392
2421
|
const resume = pendingResume();
|
|
2393
2422
|
answers.current.clear();
|
|
2394
2423
|
let s = ref.current;
|
|
@@ -2450,7 +2479,27 @@ function usePilot(transport, opts = {}) {
|
|
|
2450
2479
|
},
|
|
2451
2480
|
[commit, sendAnswersIfComplete]
|
|
2452
2481
|
);
|
|
2453
|
-
|
|
2482
|
+
const [loadingOlder, setLoadingOlder] = useState8(false);
|
|
2483
|
+
const loadingRef = useRef4(false);
|
|
2484
|
+
const loadOlder = useCallback2(async () => {
|
|
2485
|
+
const fn = optsRef.current.loadOlder;
|
|
2486
|
+
const h = ref.current.history;
|
|
2487
|
+
if (!fn || !h?.hasMoreBefore || loadingRef.current) return;
|
|
2488
|
+
loadingRef.current = true;
|
|
2489
|
+
setLoadingOlder(true);
|
|
2490
|
+
try {
|
|
2491
|
+
const page = await fn(h.before);
|
|
2492
|
+
if (ref.current.history !== h) return;
|
|
2493
|
+
commit(prependHistory(ref.current, page.messages ?? [], { hasMoreBefore: page.hasMoreBefore === true, ...page.before ? { before: page.before } : {} }));
|
|
2494
|
+
} catch (err) {
|
|
2495
|
+
commit({ ...ref.current, notice: err instanceof Error && err.message ? err.message : null, effects: [] });
|
|
2496
|
+
} finally {
|
|
2497
|
+
loadingRef.current = false;
|
|
2498
|
+
setLoadingOlder(false);
|
|
2499
|
+
}
|
|
2500
|
+
}, [commit]);
|
|
2501
|
+
const canSteer = Boolean(transport) && (opts.steer ?? state.agentState.caps?.steer === true);
|
|
2502
|
+
return { state, threadId, send, stop, retry, respondPermission, recordDecision, expirePermission, canSteer, loadOlder, loadingOlder };
|
|
2454
2503
|
}
|
|
2455
2504
|
|
|
2456
2505
|
// src/react/Piloto.tsx
|
|
@@ -2459,10 +2508,10 @@ var useIsoLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutE
|
|
|
2459
2508
|
var ANCHOR_GAP = 24;
|
|
2460
2509
|
var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
2461
2510
|
const { transport, pilot: external, locale = "es", theme = "light", features = {}, slots = {} } = props;
|
|
2462
|
-
const own = usePilot(external ? null : transport, props);
|
|
2511
|
+
const own = usePilot(external ? null : transport, features.steer === void 0 ? props : { ...props, steer: features.steer });
|
|
2463
2512
|
const pilot = external ?? own;
|
|
2464
2513
|
const { state } = pilot;
|
|
2465
|
-
const { turns, busy, error, notice, permissions } = state;
|
|
2514
|
+
const { turns, busy, error, notice, permissions, olderCount } = state;
|
|
2466
2515
|
const strings = useMemo3(() => ({ ...locale === "en" ? STRINGS_EN : STRINGS_ES, ...props.strings }), [locale, props.strings]);
|
|
2467
2516
|
const config = useMemo3(
|
|
2468
2517
|
() => ({
|
|
@@ -2546,6 +2595,18 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2546
2595
|
const touchY = useRef5(null);
|
|
2547
2596
|
const [atBottom, setAtBottom] = useState9(true);
|
|
2548
2597
|
const message = (n) => scroller.current?.querySelector(`[data-turn="${n}"]`) ?? null;
|
|
2598
|
+
const olderSeen = useRef5(olderCount);
|
|
2599
|
+
const heightBefore = useRef5(0);
|
|
2600
|
+
useIsoLayoutEffect(() => {
|
|
2601
|
+
const el = scroller.current;
|
|
2602
|
+
if (el && olderCount > olderSeen.current && heightBefore.current) el.scrollTop += el.scrollHeight - heightBefore.current;
|
|
2603
|
+
olderSeen.current = olderCount;
|
|
2604
|
+
heightBefore.current = 0;
|
|
2605
|
+
}, [olderCount]);
|
|
2606
|
+
const loadOlder = () => {
|
|
2607
|
+
heightBefore.current = scroller.current?.scrollHeight ?? 0;
|
|
2608
|
+
void pilot.loadOlder();
|
|
2609
|
+
};
|
|
2549
2610
|
const placed = useRef5(false);
|
|
2550
2611
|
useEffect6(() => {
|
|
2551
2612
|
if (placed.current || turns.length === 0) return;
|
|
@@ -2567,10 +2628,10 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2567
2628
|
useEffect6(() => {
|
|
2568
2629
|
if (mine === mineSeen.current) return;
|
|
2569
2630
|
mineSeen.current = mine;
|
|
2570
|
-
if (mine) setAnchor(mineIndex);
|
|
2631
|
+
if (mine) setAnchor(mineIndex - olderCount);
|
|
2571
2632
|
following.current = true;
|
|
2572
2633
|
}, [mine, mineIndex]);
|
|
2573
|
-
const gapAbove = (a) => anchor ===
|
|
2634
|
+
const gapAbove = (a) => anchor === -olderCount ? a.offsetTop : ANCHOR_GAP;
|
|
2574
2635
|
const measureSpacer = useCallback3(() => {
|
|
2575
2636
|
const sc = scroller.current;
|
|
2576
2637
|
const sp = spacer.current;
|
|
@@ -2632,7 +2693,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2632
2693
|
onSubmit: send,
|
|
2633
2694
|
onStop: pilot.stop,
|
|
2634
2695
|
busy,
|
|
2635
|
-
canSteer:
|
|
2696
|
+
canSteer: pilot.canSteer,
|
|
2636
2697
|
placeholder,
|
|
2637
2698
|
attachments: showFiles,
|
|
2638
2699
|
upload: props.upload,
|
|
@@ -2646,8 +2707,12 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2646
2707
|
draft.n
|
|
2647
2708
|
)
|
|
2648
2709
|
] });
|
|
2710
|
+
const withPanel = (chat) => slots.sidePanel ? /* @__PURE__ */ jsxs8("div", { className: "flex min-h-0 flex-1 flex-row", children: [
|
|
2711
|
+
/* @__PURE__ */ jsx10("div", { className: "relative flex min-h-0 min-w-0 flex-1 flex-col", children: chat }),
|
|
2712
|
+
slots.sidePanel(state.agentState)
|
|
2713
|
+
] }) : chat;
|
|
2649
2714
|
const permissionCards = cards.map((p) => /* @__PURE__ */ jsx10(ConfirmCard, { request: p, onRespond: respond, onSettled: settle, onExpire: pilot.expirePermission }, p.id));
|
|
2650
|
-
return /* @__PURE__ */ jsx10(PilotoContext.Provider, { value: config, children: /* @__PURE__ */ jsx10("div", { ...rootProps(theme, props.skin), "data-busy": busy || void 0, className: `piloto ${props.className ?? ""}`, style: props.style, children: empty ? (
|
|
2715
|
+
return /* @__PURE__ */ jsx10(PilotoContext.Provider, { value: config, children: /* @__PURE__ */ jsx10("div", { ...rootProps(theme, props.skin), "data-busy": busy || void 0, className: `piloto ${props.className ?? ""}`, style: props.style, children: withPanel(empty ? (
|
|
2651
2716
|
// Un hilo vacío enseña lo mismo que la portada: la cara, el saludo, la caja y por dónde
|
|
2652
2717
|
// empezar. Si no cabe (panel bajo), se desplaza: `safe center` nunca empuja nada fuera.
|
|
2653
2718
|
/* @__PURE__ */ jsxs8("div", { className: "gs-scroll flex min-h-0 flex-1 flex-col items-center justify-center overflow-y-auto px-4 pb-[10vh] [justify-content:safe_center]", children: [
|
|
@@ -2688,10 +2753,20 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2688
2753
|
className: "gs-scroll relative min-h-0 flex-1 overflow-y-auto overflow-x-hidden",
|
|
2689
2754
|
children: [
|
|
2690
2755
|
/* @__PURE__ */ jsxs8("div", { ref: column, className: "mx-auto flex w-full max-w-[784px] flex-col gap-6 px-4 py-8 sm:px-6", children: [
|
|
2756
|
+
state.history?.hasMoreBefore && (external || props.loadOlder) && /* @__PURE__ */ jsx10(
|
|
2757
|
+
"button",
|
|
2758
|
+
{
|
|
2759
|
+
type: "button",
|
|
2760
|
+
onClick: loadOlder,
|
|
2761
|
+
disabled: pilot.loadingOlder,
|
|
2762
|
+
className: "self-center rounded-full border border-border-primary px-3 py-1 text-sm text-text-secondary transition hover:text-text-primary disabled:opacity-60",
|
|
2763
|
+
children: pilot.loadingOlder ? t.loadingOlder : t.loadOlder
|
|
2764
|
+
}
|
|
2765
|
+
),
|
|
2691
2766
|
turns.map((turn, i) => {
|
|
2692
2767
|
const isLast = i === turns.length - 1;
|
|
2693
2768
|
const live = busy && isLast;
|
|
2694
|
-
return /* @__PURE__ */ jsx10("div", { "data-turn": i, children: turn.role === "user" ? /* @__PURE__ */ jsx10(UserBubble, { turn }) : /* @__PURE__ */ jsx10(
|
|
2769
|
+
return /* @__PURE__ */ jsx10("div", { "data-turn": i - olderCount, children: turn.role === "user" ? /* @__PURE__ */ jsx10(UserBubble, { turn }) : /* @__PURE__ */ jsx10(
|
|
2695
2770
|
AgentBubble,
|
|
2696
2771
|
{
|
|
2697
2772
|
turn,
|
|
@@ -2703,12 +2778,12 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2703
2778
|
showDuration: features.duration !== false,
|
|
2704
2779
|
renderDeliveries: slots.renderDeliveries
|
|
2705
2780
|
}
|
|
2706
|
-
) }, i);
|
|
2781
|
+
) }, i - olderCount);
|
|
2707
2782
|
}),
|
|
2708
2783
|
permissionCards,
|
|
2709
2784
|
busy && !toolRunning && !cards.length && /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1.5", children: [
|
|
2710
2785
|
/* @__PURE__ */ jsx10(TypingDots, {}),
|
|
2711
|
-
/* @__PURE__ */ jsx10(WaitLabel, { since: state.turnStartedAt, showDuration: features.duration !== false })
|
|
2786
|
+
/* @__PURE__ */ jsx10(WaitLabel, { since: state.turnStartedAt, step: state.step, showDuration: features.duration !== false })
|
|
2712
2787
|
] }),
|
|
2713
2788
|
notice && /* @__PURE__ */ jsx10("p", { className: "text-sm text-text-tertiary", children: notice }),
|
|
2714
2789
|
error && /* @__PURE__ */ jsx10("p", { className: "text-sm text-text-danger", children: error })
|
|
@@ -2731,7 +2806,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2731
2806
|
slots.aboveComposer,
|
|
2732
2807
|
composer
|
|
2733
2808
|
] })
|
|
2734
|
-
] }) }) });
|
|
2809
|
+
] })) }) });
|
|
2735
2810
|
});
|
|
2736
2811
|
export {
|
|
2737
2812
|
AgentBubble,
|
|
@@ -2765,9 +2840,11 @@ export {
|
|
|
2765
2840
|
UsageStats,
|
|
2766
2841
|
UserBubble,
|
|
2767
2842
|
WaitLabel,
|
|
2843
|
+
applyJsonPatch,
|
|
2768
2844
|
applyReferences,
|
|
2769
2845
|
argsDetail,
|
|
2770
2846
|
beginSend,
|
|
2847
|
+
beginSteer,
|
|
2771
2848
|
cleanReplyOption,
|
|
2772
2849
|
cleanUserText,
|
|
2773
2850
|
createAgUiTransport,
|
|
@@ -2784,6 +2861,7 @@ export {
|
|
|
2784
2861
|
messagesToTurns,
|
|
2785
2862
|
mimeOf,
|
|
2786
2863
|
narrationToMarkdown,
|
|
2864
|
+
prependHistory,
|
|
2787
2865
|
readSse,
|
|
2788
2866
|
reduceAgUiEvent,
|
|
2789
2867
|
renderInline,
|
package/dist/react/strings.d.ts
CHANGED
|
@@ -54,6 +54,10 @@ export interface PilotStrings {
|
|
|
54
54
|
linkOpen: string;
|
|
55
55
|
close: string;
|
|
56
56
|
goToLatest: string;
|
|
57
|
+
/** La frase de espera de cada paso con nombre (`STEP_STARTED.stepName`). Los que no estén aquí no se pintan. */
|
|
58
|
+
steps: Record<string, string>;
|
|
59
|
+
loadOlder: string;
|
|
60
|
+
loadingOlder: string;
|
|
57
61
|
confirmTitle: string;
|
|
58
62
|
approve: string;
|
|
59
63
|
reject: string;
|
package/dist/react/usePilot.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { BaseEvent, RunAgentInput } from "@ag-ui/core";
|
|
2
|
-
import type { Attachment, Decision, PermissionRequest, PilotState, Reference, Turn } from "../core/types";
|
|
1
|
+
import type { BaseEvent, Message, RunAgentInput } from "@ag-ui/core";
|
|
2
|
+
import type { Attachment, Decision, HistoryPage, PermissionRequest, PilotState, Reference, Turn } from "../core/types";
|
|
3
3
|
import { type PilotTransport } from "../transport";
|
|
4
4
|
export interface TurnDoneInfo {
|
|
5
5
|
/** El texto final de la respuesta. */
|
|
@@ -31,6 +31,20 @@ export interface UsePilotOptions {
|
|
|
31
31
|
onEvent?: (event: BaseEvent) => void;
|
|
32
32
|
/** Cada run que sale (analítica). */
|
|
33
33
|
onSend?: (input: RunAgentInput) => void;
|
|
34
|
+
/** El estado del agente cambió (`STATE_SNAPSHOT` / `STATE_DELTA` ya aplicado). */
|
|
35
|
+
onState?: (state: Record<string, unknown>) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Steer: mandar con el run vivo (docs/protocol.md §6). Default: lo que diga el agente en
|
|
38
|
+
* `state.caps.steer`. Sin steer, mandar mientras corre no hace nada.
|
|
39
|
+
*/
|
|
40
|
+
steer?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Trae los mensajes anteriores a `before` (el cursor de `CUSTOM ghosty.history`), p. ej. del REST de
|
|
43
|
+
* lectura de la app. Las páginas empiezan en un mensaje del usuario.
|
|
44
|
+
*/
|
|
45
|
+
loadOlder?: (before: string | undefined) => Promise<{
|
|
46
|
+
messages: Message[];
|
|
47
|
+
} & HistoryPage>;
|
|
34
48
|
}
|
|
35
49
|
export interface SendOptions {
|
|
36
50
|
references?: Reference[];
|
|
@@ -49,6 +63,11 @@ export interface Pilot {
|
|
|
49
63
|
recordDecision: (id: string, status: Decision["status"], asked?: PermissionRequest) => void;
|
|
50
64
|
/** Una tarjeta venció sin contestarse: se anota y su interrupt viaja como `cancelled` en el siguiente run. */
|
|
51
65
|
expirePermission: (id: string) => void;
|
|
66
|
+
/** Se puede mandar con el run vivo (steer). */
|
|
67
|
+
canSteer: boolean;
|
|
68
|
+
/** Antepone la página anterior del hilo (si hay `loadOlder` y `state.history.hasMoreBefore`). */
|
|
69
|
+
loadOlder: () => Promise<void>;
|
|
70
|
+
loadingOlder: boolean;
|
|
52
71
|
}
|
|
53
72
|
/** El contexto de la app como `context` de AG-UI: `[{description, value}]`, `value` siempre texto. */
|
|
54
73
|
export declare function toAgUiContext(ctx: unknown): RunAgentInput["context"];
|
package/dist/styles.css
CHANGED
|
@@ -511,6 +511,9 @@
|
|
|
511
511
|
.piloto .flex-col {
|
|
512
512
|
flex-direction: column;
|
|
513
513
|
}
|
|
514
|
+
.piloto .flex-row {
|
|
515
|
+
flex-direction: row;
|
|
516
|
+
}
|
|
514
517
|
.piloto .flex-wrap {
|
|
515
518
|
flex-wrap: wrap;
|
|
516
519
|
}
|
|
@@ -588,6 +591,9 @@
|
|
|
588
591
|
.piloto :where(.divide-border > :not(:last-child)) {
|
|
589
592
|
border-color: var(--color-border);
|
|
590
593
|
}
|
|
594
|
+
.piloto .self-center {
|
|
595
|
+
align-self: center;
|
|
596
|
+
}
|
|
591
597
|
.piloto .truncate {
|
|
592
598
|
overflow: hidden;
|
|
593
599
|
text-overflow: ellipsis;
|
|
@@ -1404,6 +1410,9 @@
|
|
|
1404
1410
|
.piloto .disabled\:opacity-50:disabled {
|
|
1405
1411
|
opacity: 50%;
|
|
1406
1412
|
}
|
|
1413
|
+
.piloto .disabled\:opacity-60:disabled {
|
|
1414
|
+
opacity: 60%;
|
|
1415
|
+
}
|
|
1407
1416
|
@supports (backdrop-filter: var(--tw)) {
|
|
1408
1417
|
.piloto .supports-\[backdrop-filter\]\:bg-background\/70 {
|
|
1409
1418
|
background-color: var(--color-background);
|
package/dist/transport/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghostykit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Ghosty Autopilot \u2014 the autopilot chat for your React app, on AG-UI: works with LangGraph, Mastra, CrewAI or any AG-UI agent. Streaming text, agent steps, human approval cards (interrupt/resume), attachments and voice.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/core/index.js",
|