ghostykit 0.5.2 → 0.6.1
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 +11 -4
- package/dist/chunks/{chunk-3XDAYARB.js → chunk-T5PZC5NA.js} +106 -13
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +9 -1
- package/dist/core/reducer.d.ts +15 -1
- package/dist/core/types.d.ts +42 -0
- package/dist/react/Piloto.d.ts +4 -4
- package/dist/react/Subagents.d.ts +10 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +164 -28
- package/dist/react/strings.d.ts +8 -0
- package/dist/react/usePilot.d.ts +15 -1
- package/dist/styles.css +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,10 +111,17 @@ ref.current?.send("Why did this fail?"); // ask on the user'
|
|
|
111
111
|
Other options: `strings`, `toolIcons`, `formatUserText`, `markdown`, `features`
|
|
112
112
|
(`attachments`, `retry`, `copy`, `usage`, `replyOptions`, `duration`, `steer`), `references` /
|
|
113
113
|
`onReferencesChange`, `voiceNoteText`, `placeholder`, `avatar`, `initialTurns`, `loadOlder` (earlier
|
|
114
|
-
pages of a long thread),
|
|
115
|
-
`
|
|
116
|
-
`
|
|
117
|
-
`
|
|
114
|
+
pages of a long thread), `forwardedProps` (extra keys for every run, e.g. `{permissions: "ask"}`), the
|
|
115
|
+
slots `sidePanel` and `aboveComposer` (both may take the `pilot`), and the callbacks `onTurnStart`,
|
|
116
|
+
`onTitle`, `onState`, `onEvent` (every raw AG-UI event), `onSend`. The agent's shared state
|
|
117
|
+
(`STATE_SNAPSHOT` + `STATE_DELTA`) is `pilot.state.agentState`, and each turn's `CUSTOM` events are
|
|
118
|
+
`turn.custom` (what `renderDeliveries` draws).
|
|
119
|
+
|
|
120
|
+
Subagents (`state.subagents.tasks`) are `pilot.subagents`; `pilot.stopSubagent(id)` stops one, and
|
|
121
|
+
`<Subagents pilot={pilot} />` is a ready-made bar ("2 agents working", one row each, *Stop*):
|
|
122
|
+
`slots={{ aboveComposer: (pilot) => <Subagents pilot={pilot} /> }}`. A message the server can't take
|
|
123
|
+
while a run is live (`409 run_active`) waits in `pilot.state.queued` and is sent when that run ends.
|
|
124
|
+
`pilot.refresh()` asks for the thread again (when your own event bus says something changed). To read the state outside the chat, call `usePilot(transport)` yourself
|
|
118
125
|
and pass the result as `pilot`. Every piece is
|
|
119
126
|
exported (`AgentBubble`, `AgentSteps`, `Composer`, `ConfirmCard`, `Markdown`, …) if you want to build
|
|
120
127
|
your own layout.
|
|
@@ -111,6 +111,8 @@ function createPilotState(turns = []) {
|
|
|
111
111
|
step: null,
|
|
112
112
|
history: null,
|
|
113
113
|
olderCount: 0,
|
|
114
|
+
queued: [],
|
|
115
|
+
openText: null,
|
|
114
116
|
effects: []
|
|
115
117
|
};
|
|
116
118
|
}
|
|
@@ -182,6 +184,26 @@ function attachmentsOf(content) {
|
|
|
182
184
|
}
|
|
183
185
|
return out.length ? out : void 0;
|
|
184
186
|
}
|
|
187
|
+
function toolLabel(t, v) {
|
|
188
|
+
const x = v ?? {};
|
|
189
|
+
if (typeof x.toolCallId !== "string") return;
|
|
190
|
+
const str = (y) => typeof y === "string" && y ? y : void 0;
|
|
191
|
+
const patch = {};
|
|
192
|
+
if (str(x.title)) patch.label = str(x.title);
|
|
193
|
+
if (str(x.kind)) patch.kind = str(x.kind);
|
|
194
|
+
if (str(x.path)) patch.path = str(x.path);
|
|
195
|
+
if (str(x.detail)) patch.detail = str(x.detail);
|
|
196
|
+
t.tools = (t.tools ?? []).map((y) => y.id === x.toolCallId ? { ...y, ...patch } : y);
|
|
197
|
+
}
|
|
198
|
+
function applyStoredCustom(t, m) {
|
|
199
|
+
const custom = m.metadata?.custom;
|
|
200
|
+
if (!Array.isArray(custom)) return;
|
|
201
|
+
for (const c of custom) {
|
|
202
|
+
if (!c || typeof c.name !== "string") continue;
|
|
203
|
+
if (c.name === "ghosty.tool") toolLabel(t, c.value);
|
|
204
|
+
else if (!NOT_IN_TURN.has(c.name)) t.custom = [...t.custom ?? [], c];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
185
207
|
function messagesToTurns(messages) {
|
|
186
208
|
const turns = [];
|
|
187
209
|
let cur = null;
|
|
@@ -199,8 +221,6 @@ function messagesToTurns(messages) {
|
|
|
199
221
|
} else if (m.role === "assistant") {
|
|
200
222
|
const t = assistant();
|
|
201
223
|
if (!t.id && m.id) t.id = m.id;
|
|
202
|
-
const custom = m.metadata?.custom;
|
|
203
|
-
if (Array.isArray(custom) && custom.length) t.custom = [...t.custom ?? [], ...custom.filter((c) => c && typeof c.name === "string")];
|
|
204
224
|
const text = m.content ?? "";
|
|
205
225
|
if (text) t.text = t.text ? `${t.text}
|
|
206
226
|
|
|
@@ -209,10 +229,12 @@ ${text}` : text;
|
|
|
209
229
|
t.tools = [...t.tools ?? [], { id: c.id, title: c.function.name, status: "pending", args: c.function.arguments, detail: argsDetail(c.function.arguments) }];
|
|
210
230
|
if (t.text.trim() && t.cuts?.[t.cuts.length - 1] !== t.text.length) t.cuts = [...t.cuts ?? [], t.text.length];
|
|
211
231
|
}
|
|
232
|
+
applyStoredCustom(t, m);
|
|
212
233
|
} else if (m.role === "tool") {
|
|
213
234
|
const t = assistant(m.id);
|
|
214
235
|
const failed = Boolean(m.error);
|
|
215
236
|
t.tools = (t.tools ?? []).map((x) => x.id === m.toolCallId ? { ...x, status: failed ? "failed" : "completed" } : x);
|
|
237
|
+
applyStoredCustom(t, m);
|
|
216
238
|
} else if (m.role === "reasoning") {
|
|
217
239
|
const t = assistant(m.id);
|
|
218
240
|
t.thought = (t.thought ?? "") + textOf(m.content);
|
|
@@ -255,7 +277,9 @@ function applySnapshot(s, messages) {
|
|
|
255
277
|
turns: [...prefix, ...turns],
|
|
256
278
|
olderCount: prefix.length,
|
|
257
279
|
history: keepOlder ? s.history : s.historyNext ?? null,
|
|
258
|
-
historyNext: void 0
|
|
280
|
+
historyNext: void 0,
|
|
281
|
+
// Lo abierto no viene en el snapshot: si sigue, el servidor lo re-emite enseguida.
|
|
282
|
+
openText: null
|
|
259
283
|
};
|
|
260
284
|
}
|
|
261
285
|
function reduceAgUiEvent(prev, e, now = Date.now()) {
|
|
@@ -263,20 +287,30 @@ function reduceAgUiEvent(prev, e, now = Date.now()) {
|
|
|
263
287
|
switch (e.type) {
|
|
264
288
|
case "RUN_STARTED":
|
|
265
289
|
return { ...s, busy: true, error: null, step: null, runId: e.runId ?? s.runId };
|
|
266
|
-
case "TEXT_MESSAGE_START":
|
|
290
|
+
case "TEXT_MESSAGE_START": {
|
|
267
291
|
if (e.role && e.role !== "assistant") return s;
|
|
268
|
-
|
|
292
|
+
const id = typeof e.messageId === "string" ? e.messageId : null;
|
|
293
|
+
const last = s.turns[s.turns.length - 1];
|
|
294
|
+
if (id && s.openText?.id === id && last?.role === "assistant" && last.text.length >= s.openText.start) {
|
|
295
|
+
const start = s.openText.start;
|
|
296
|
+
return patchAssistant(s, (t) => ({ ...t, text: t.text.slice(0, start) }));
|
|
297
|
+
}
|
|
298
|
+
const next = patchAssistant(s, (t) => ({ ...t, text: t.text && !t.text.endsWith("\n") ? `${t.text}
|
|
269
299
|
|
|
270
300
|
` : t.text }));
|
|
301
|
+
return { ...next, openText: id ? { id, start: next.turns[next.turns.length - 1].text.length } : null };
|
|
302
|
+
}
|
|
271
303
|
case "TEXT_MESSAGE_CONTENT":
|
|
272
304
|
case "TEXT_MESSAGE_CHUNK":
|
|
273
305
|
if (e.role && e.role !== "assistant") return s;
|
|
274
306
|
if (typeof e.delta !== "string" || !e.delta) return s;
|
|
275
307
|
return patchAssistant(s, (t) => ({ ...t, text: t.text + e.delta }));
|
|
276
308
|
case "TEXT_MESSAGE_END":
|
|
277
|
-
return s;
|
|
278
|
-
case "TOOL_CALL_START":
|
|
279
|
-
|
|
309
|
+
return s.openText && s.openText.id === e.messageId ? { ...s, openText: null } : s;
|
|
310
|
+
case "TOOL_CALL_START": {
|
|
311
|
+
const known = s.turns[s.turns.length - 1]?.tools?.some((x) => x.id === e.toolCallId);
|
|
312
|
+
return upsertTool(s, e.toolCallId, { title: e.toolCallName, status: "in_progress", ...known ? { args: "" } : {} });
|
|
313
|
+
}
|
|
280
314
|
case "TOOL_CALL_CHUNK": {
|
|
281
315
|
const known = s.turns[s.turns.length - 1]?.tools?.find((x) => x.id === e.toolCallId);
|
|
282
316
|
let next = known ? s : upsertTool(s, e.toolCallId, { title: e.toolCallName, status: "in_progress" });
|
|
@@ -352,10 +386,11 @@ ${content}` : content };
|
|
|
352
386
|
function toHistory(v) {
|
|
353
387
|
if (!v || typeof v !== "object") return null;
|
|
354
388
|
const h = v;
|
|
355
|
-
|
|
389
|
+
const before = typeof h.before === "string" || typeof h.before === "number" ? String(h.before) : void 0;
|
|
390
|
+
return { hasMoreBefore: h.hasMoreBefore === true, ...before !== void 0 ? { before } : {} };
|
|
356
391
|
}
|
|
357
392
|
function finish(s0, e, now, failed = false) {
|
|
358
|
-
let s = { ...s0, busy: false, step: null };
|
|
393
|
+
let s = { ...s0, busy: false, step: null, openText: null };
|
|
359
394
|
const outcome = e.outcome;
|
|
360
395
|
const interrupted = outcome?.type === "interrupt" && Array.isArray(outcome.interrupts) && outcome.interrupts.length > 0;
|
|
361
396
|
const clockOffset = typeof e.timestamp === "number" ? now - e.timestamp : 0;
|
|
@@ -395,18 +430,72 @@ function beginSend(prev, text, attachments = [], now = Date.now()) {
|
|
|
395
430
|
return { ...prev, turns, busy: true, error: null, notice: null, turnStartedAt: now, effects: [{ type: "turn-start" }] };
|
|
396
431
|
}
|
|
397
432
|
function beginSteer(prev, text) {
|
|
398
|
-
return { ...prev, turns: [...prev.turns, { role: "user", text }], notice: null, effects: [] };
|
|
433
|
+
return { ...prev, turns: [...prev.turns, { role: "user", text }], notice: null, openText: null, effects: [] };
|
|
434
|
+
}
|
|
435
|
+
function queueMessage(prev, text, attachments = [], tries = 1) {
|
|
436
|
+
const turns = [...prev.turns];
|
|
437
|
+
for (let i = turns.length - 1; i >= 0; i--) {
|
|
438
|
+
if (turns[i].role === "user" && turns[i].text === text) {
|
|
439
|
+
turns.splice(i, 1);
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return { ...prev, turns, queued: [...prev.queued, { text, ...attachments.length ? { attachments } : {}, tries }], effects: [] };
|
|
444
|
+
}
|
|
445
|
+
function takeQueued(prev) {
|
|
446
|
+
return { state: { ...prev, queued: [], effects: [] }, queued: prev.queued };
|
|
447
|
+
}
|
|
448
|
+
function subagentsOf(agentState) {
|
|
449
|
+
const tasks = agentState.subagents?.tasks;
|
|
450
|
+
if (!Array.isArray(tasks)) return [];
|
|
451
|
+
return tasks.filter((t) => Boolean(t) && typeof t === "object" && typeof t.id === "string");
|
|
452
|
+
}
|
|
453
|
+
function markSubagent(prev, taskId, status) {
|
|
454
|
+
const tasks = subagentsOf(prev.agentState);
|
|
455
|
+
if (!tasks.some((t) => t.id === taskId)) return prev;
|
|
456
|
+
const subagents = { ...prev.agentState.subagents, tasks: tasks.map((t) => t.id === taskId ? { ...t, status } : t) };
|
|
457
|
+
const agentState = { ...prev.agentState, subagents };
|
|
458
|
+
return { ...prev, agentState, effects: [{ type: "state", state: agentState }] };
|
|
399
459
|
}
|
|
400
460
|
function prependHistory(prev, messages, page) {
|
|
401
461
|
const have = new Set(prev.turns.map((t) => t.id).filter(Boolean));
|
|
402
462
|
const older = messagesToTurns(messages).filter((t) => !t.id || !have.has(t.id));
|
|
403
|
-
|
|
463
|
+
let turns = prev.turns;
|
|
464
|
+
const tail = older[older.length - 1];
|
|
465
|
+
const head = turns[0];
|
|
466
|
+
if (tail?.role === "assistant" && head?.role === "assistant") {
|
|
467
|
+
older[older.length - 1] = joinTurns(tail, head);
|
|
468
|
+
turns = turns.slice(1);
|
|
469
|
+
}
|
|
470
|
+
const olderCount = Math.max(0, prev.olderCount + older.length - (turns === prev.turns ? 0 : 1));
|
|
471
|
+
return { ...prev, turns: [...older, ...turns], olderCount, history: page ? { ...page, ...page.before !== void 0 ? { before: String(page.before) } : {} } : page, effects: [] };
|
|
472
|
+
}
|
|
473
|
+
function cutsOf(a, b, shift) {
|
|
474
|
+
const bridge = a.text.trim() && b.tools?.length && !b.cuts?.length ? [a.text.length] : [];
|
|
475
|
+
const cuts = [...a.cuts ?? [], ...bridge, ...(b.cuts ?? []).map((c) => c + shift)];
|
|
476
|
+
return cuts.length ? { cuts } : {};
|
|
477
|
+
}
|
|
478
|
+
function joinTurns(a, b) {
|
|
479
|
+
const sep = a.text && b.text ? "\n\n" : "";
|
|
480
|
+
const shift = a.text.length + sep.length;
|
|
481
|
+
return {
|
|
482
|
+
...b,
|
|
483
|
+
...a.id ? { id: a.id } : {},
|
|
484
|
+
text: a.text + sep + b.text,
|
|
485
|
+
...a.tools || b.tools ? { tools: [...a.tools ?? [], ...b.tools ?? []] } : {},
|
|
486
|
+
...cutsOf(a, b, shift),
|
|
487
|
+
...a.custom || b.custom ? { custom: [...a.custom ?? [], ...b.custom ?? []] } : {},
|
|
488
|
+
...a.thought || b.thought ? { thought: (a.thought ?? "") + (b.thought ?? "") } : {}
|
|
489
|
+
};
|
|
404
490
|
}
|
|
405
491
|
function sendFailed(prev, message) {
|
|
406
492
|
return { ...prev, busy: false, turnStartedAt: 0, error: message, effects: [] };
|
|
407
493
|
}
|
|
408
494
|
function stopTurn(prev) {
|
|
409
|
-
|
|
495
|
+
const text = prev.queued.map((q) => q.text).filter(Boolean).join("\n\n");
|
|
496
|
+
const attachments = prev.queued.flatMap((q) => q.attachments ?? []);
|
|
497
|
+
const returnedDraft = prev.queued.length ? { text, ...attachments.length ? { attachments } : {}, n: (prev.returnedDraft?.n ?? 0) + 1 } : prev.returnedDraft;
|
|
498
|
+
return { ...prev, busy: false, step: null, queued: [], returnedDraft, turnStartedAt: 0, turns: closeOpenTools(prev.turns, "cancelled"), effects: [] };
|
|
410
499
|
}
|
|
411
500
|
function dropLastExchange(prev) {
|
|
412
501
|
const turns = [...prev.turns];
|
|
@@ -648,6 +737,10 @@ export {
|
|
|
648
737
|
reduceAgUiEvent,
|
|
649
738
|
beginSend,
|
|
650
739
|
beginSteer,
|
|
740
|
+
queueMessage,
|
|
741
|
+
takeQueued,
|
|
742
|
+
subagentsOf,
|
|
743
|
+
markSubagent,
|
|
651
744
|
prependHistory,
|
|
652
745
|
sendFailed,
|
|
653
746
|
stopTurn,
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
|
-
export { argsDetail, beginSend, beginSteer, createPilotState, dropLastExchange, expiryMs, lastUserTurn, messagesToTurns, prependHistory, reduceAgUiEvent, resolvePermission, sendFailed, stopTurn, toPermission, } from "./reducer";
|
|
2
|
+
export { argsDetail, beginSend, beginSteer, createPilotState, dropLastExchange, expiryMs, lastUserTurn, markSubagent, messagesToTurns, prependHistory, queueMessage, reduceAgUiEvent, resolvePermission, sendFailed, stopTurn, subagentsOf, takeQueued, toPermission, } from "./reducer";
|
|
3
3
|
export { applyJsonPatch, type PatchOp } from "./jsonPatch";
|
|
4
4
|
export { isNarration, narrationToMarkdown, splitNarration } from "./narration";
|
|
5
5
|
export { cleanReplyOption, extractReplyOptions, type ReplyOptions } from "./options";
|
package/dist/core/index.js
CHANGED
|
@@ -14,20 +14,24 @@ import {
|
|
|
14
14
|
isNarration,
|
|
15
15
|
isRunning,
|
|
16
16
|
lastUserTurn,
|
|
17
|
+
markSubagent,
|
|
17
18
|
messagesToTurns,
|
|
18
19
|
narrationToMarkdown,
|
|
19
20
|
prependHistory,
|
|
21
|
+
queueMessage,
|
|
20
22
|
reduceAgUiEvent,
|
|
21
23
|
resolvePermission,
|
|
22
24
|
sendFailed,
|
|
23
25
|
splitNarration,
|
|
24
26
|
stepLabel,
|
|
25
27
|
stopTurn,
|
|
28
|
+
subagentsOf,
|
|
26
29
|
summarizeTools,
|
|
30
|
+
takeQueued,
|
|
27
31
|
toPermission,
|
|
28
32
|
toolName,
|
|
29
33
|
toolNames
|
|
30
|
-
} from "../chunks/chunk-
|
|
34
|
+
} from "../chunks/chunk-T5PZC5NA.js";
|
|
31
35
|
import {
|
|
32
36
|
applyReferences,
|
|
33
37
|
cleanUserText,
|
|
@@ -53,17 +57,21 @@ export {
|
|
|
53
57
|
isNarration,
|
|
54
58
|
isRunning,
|
|
55
59
|
lastUserTurn,
|
|
60
|
+
markSubagent,
|
|
56
61
|
messagesToTurns,
|
|
57
62
|
narrationToMarkdown,
|
|
58
63
|
prependHistory,
|
|
64
|
+
queueMessage,
|
|
59
65
|
reduceAgUiEvent,
|
|
60
66
|
resolvePermission,
|
|
61
67
|
sendFailed,
|
|
62
68
|
splitNarration,
|
|
63
69
|
stepLabel,
|
|
64
70
|
stopTurn,
|
|
71
|
+
subagentsOf,
|
|
65
72
|
summarizeTools,
|
|
66
73
|
systemNoteOf,
|
|
74
|
+
takeQueued,
|
|
67
75
|
toPermission,
|
|
68
76
|
toolName,
|
|
69
77
|
toolNames
|
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, HistoryPage, PermissionRequest, PilotState, Turn } from "./types";
|
|
2
|
+
import type { Attachment, Decision, HistoryPage, PermissionRequest, PilotState, SubagentTask, Turn } from "./types";
|
|
3
3
|
export declare function createPilotState(turns?: Turn[]): PilotState;
|
|
4
4
|
type Ev = {
|
|
5
5
|
type: string;
|
|
@@ -23,6 +23,20 @@ export declare function beginSend(prev: PilotState, text: string, attachments?:
|
|
|
23
23
|
* diga después va en una respuesta nueva debajo; el run (y su reloj) siguen siendo los mismos.
|
|
24
24
|
*/
|
|
25
25
|
export declare function beginSteer(prev: PilotState, text: string): PilotState;
|
|
26
|
+
/**
|
|
27
|
+
* El servidor no dejó entrar el mensaje (409 `run_active` / `interrupt_pending`): su burbuja sale del
|
|
28
|
+
* hilo y queda en espera; se manda solo cuando termine el run vivo (gs-agui 1 §0.7b-6).
|
|
29
|
+
*/
|
|
30
|
+
export declare function queueMessage(prev: PilotState, text: string, attachments?: Attachment[], tries?: number): PilotState;
|
|
31
|
+
/** Saca los mensajes en espera (para mandarlos juntos en un run). */
|
|
32
|
+
export declare function takeQueued(prev: PilotState): {
|
|
33
|
+
state: PilotState;
|
|
34
|
+
queued: PilotState["queued"];
|
|
35
|
+
};
|
|
36
|
+
/** Los subagentes del estado del agente (`state.subagents.tasks`); lo que no tiene `id` se descarta. */
|
|
37
|
+
export declare function subagentsOf(agentState: Record<string, unknown>): SubagentTask[];
|
|
38
|
+
/** Marca un subagente (p. ej. `stopped` al pedir detenerlo); el siguiente STATE_* manda. */
|
|
39
|
+
export declare function markSubagent(prev: PilotState, taskId: string, status: SubagentTask["status"]): PilotState;
|
|
26
40
|
/**
|
|
27
41
|
* Mensajes más viejos (`loadOlder`) delante del hilo. Las páginas empiezan en un mensaje del usuario;
|
|
28
42
|
* lo que ya se tenía (mismo id) no se repite.
|
package/dist/core/types.d.ts
CHANGED
|
@@ -46,6 +46,34 @@ export interface CustomEntry {
|
|
|
46
46
|
value: unknown;
|
|
47
47
|
timestamp?: number;
|
|
48
48
|
}
|
|
49
|
+
/** Un subagente del agente (`state.subagents.tasks[]`, gs-agui 1 §0.5). */
|
|
50
|
+
export interface SubagentTask {
|
|
51
|
+
id: string;
|
|
52
|
+
title?: string;
|
|
53
|
+
status: "running" | "completed" | "failed" | "stopped" | (string & {});
|
|
54
|
+
/** Epoch ms. */
|
|
55
|
+
startedAt?: number;
|
|
56
|
+
endedAt?: number;
|
|
57
|
+
usage?: {
|
|
58
|
+
tokens?: number;
|
|
59
|
+
toolUses?: number;
|
|
60
|
+
};
|
|
61
|
+
/** La última herramienta que usó (para el renglón «Leyendo…»). */
|
|
62
|
+
lastTool?: string;
|
|
63
|
+
/** Lo que dijo al terminar. */
|
|
64
|
+
summary?: string;
|
|
65
|
+
model?: string;
|
|
66
|
+
effort?: string;
|
|
67
|
+
/** Su resultado ya entró al hilo. */
|
|
68
|
+
delivered?: boolean;
|
|
69
|
+
}
|
|
70
|
+
/** Un mensaje que no pudo entrar al run vivo (409 `run_active`): sale solo al terminar el run. */
|
|
71
|
+
export interface QueuedMessage {
|
|
72
|
+
text: string;
|
|
73
|
+
attachments?: Attachment[];
|
|
74
|
+
/** Cuántas veces el servidor lo rechazó (se rinde a la tercera). */
|
|
75
|
+
tries: number;
|
|
76
|
+
}
|
|
49
77
|
/** Hay mensajes más viejos que los del snapshot (`CUSTOM ghosty.history`). */
|
|
50
78
|
export interface HistoryPage {
|
|
51
79
|
hasMoreBefore: boolean;
|
|
@@ -135,6 +163,20 @@ export interface PilotState {
|
|
|
135
163
|
olderCount: number;
|
|
136
164
|
/** El `ghosty.history` que acompaña al snapshot que viene. */
|
|
137
165
|
historyNext?: HistoryPage | null;
|
|
166
|
+
/** Mensajes en espera de que termine el run vivo (steer rechazado). */
|
|
167
|
+
queued: QueuedMessage[];
|
|
168
|
+
/** Lo que estaba en espera cuando la persona detuvo: regresa a la caja (`n` cambia en cada vez). */
|
|
169
|
+
returnedDraft?: {
|
|
170
|
+
text: string;
|
|
171
|
+
attachments?: Attachment[];
|
|
172
|
+
n: number;
|
|
173
|
+
} | null;
|
|
174
|
+
/** El `TEXT_MESSAGE` abierto y dónde empieza en el texto del último turno: si el servidor lo
|
|
175
|
+
* re-emite (reconexión, §0.7b-4) con el mismo `messageId`, se reescribe en vez de duplicarse. */
|
|
176
|
+
openText?: {
|
|
177
|
+
id: string;
|
|
178
|
+
start: number;
|
|
179
|
+
} | null;
|
|
138
180
|
/** Efectos de la ÚLTIMA transición. El hook los ejecuta y los descarta. */
|
|
139
181
|
effects: PilotEffect[];
|
|
140
182
|
}
|
package/dist/react/Piloto.d.ts
CHANGED
|
@@ -31,10 +31,10 @@ export interface PilotoSlots {
|
|
|
31
31
|
emptyState?: ReactNode;
|
|
32
32
|
/** Arriba a la derecha, sobre el hilo (compartir, abrir en grande…). */
|
|
33
33
|
headerActions?: ReactNode;
|
|
34
|
-
/** Pegado arriba del composer (un aviso, un banner). */
|
|
35
|
-
aboveComposer?: ReactNode;
|
|
36
|
-
/** Un panel junto al chat (subagentes, vista previa), con el estado del agente. */
|
|
37
|
-
sidePanel?: (agentState: Record<string, unknown
|
|
34
|
+
/** Pegado arriba del composer (un aviso, un banner, `<Subagents pilot={pilot} />`). */
|
|
35
|
+
aboveComposer?: ReactNode | ((pilot: Pilot) => ReactNode);
|
|
36
|
+
/** Un panel junto al chat (subagentes, vista previa), con el estado del agente y el piloto. */
|
|
37
|
+
sidePanel?: (agentState: Record<string, unknown>, pilot: Pilot) => ReactNode;
|
|
38
38
|
}
|
|
39
39
|
export interface PilotoProps extends UsePilotOptions {
|
|
40
40
|
transport: PilotTransport | null;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Pilot } from "./usePilot";
|
|
2
|
+
export interface SubagentsProps {
|
|
3
|
+
pilot: Pick<Pilot, "subagents" | "stopSubagent">;
|
|
4
|
+
/** Abierta de entrada. Default: no. */
|
|
5
|
+
defaultOpen?: boolean;
|
|
6
|
+
/** Los que ya entregaron también se listan. Default: sí. */
|
|
7
|
+
showFinished?: boolean;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function Subagents({ pilot, defaultOpen, showFinished, className }: SubagentsProps): import("react").JSX.Element | null;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { Composer, ModelPicker, mimeOf, type ComposerProps, type ModelOption, ty
|
|
|
9
9
|
export { ConfirmCard, DecisionLines } from "./ConfirmCard";
|
|
10
10
|
export { ReferenceChips, Suggestions } from "./Chips";
|
|
11
11
|
export { TypingDots, WaitLabel } from "./Status";
|
|
12
|
+
export { Subagents, type SubagentsProps } from "./Subagents";
|
|
12
13
|
export { PilotoContext, usePiloto, type MarkdownOptions, type PilotIcon, type PilotoConfig, type PilotTheme } from "./context";
|
|
13
14
|
export { STRINGS_EN, STRINGS_ES, type PilotStrings } from "./strings";
|
|
14
15
|
export * from "../core";
|
package/dist/react/index.js
CHANGED
|
@@ -14,20 +14,24 @@ import {
|
|
|
14
14
|
isNarration,
|
|
15
15
|
isRunning,
|
|
16
16
|
lastUserTurn,
|
|
17
|
+
markSubagent,
|
|
17
18
|
messagesToTurns,
|
|
18
19
|
narrationToMarkdown,
|
|
19
20
|
prependHistory,
|
|
21
|
+
queueMessage,
|
|
20
22
|
reduceAgUiEvent,
|
|
21
23
|
resolvePermission,
|
|
22
24
|
sendFailed,
|
|
23
25
|
splitNarration,
|
|
24
26
|
stepLabel,
|
|
25
27
|
stopTurn,
|
|
28
|
+
subagentsOf,
|
|
26
29
|
summarizeTools,
|
|
30
|
+
takeQueued,
|
|
27
31
|
toPermission,
|
|
28
32
|
toolName,
|
|
29
33
|
toolNames
|
|
30
|
-
} from "../chunks/chunk-
|
|
34
|
+
} from "../chunks/chunk-T5PZC5NA.js";
|
|
31
35
|
import {
|
|
32
36
|
PilotHttpError,
|
|
33
37
|
createAgUiTransport,
|
|
@@ -821,7 +825,7 @@ import {
|
|
|
821
825
|
useEffect as useEffect6,
|
|
822
826
|
useImperativeHandle,
|
|
823
827
|
useLayoutEffect,
|
|
824
|
-
useMemo as
|
|
828
|
+
useMemo as useMemo4,
|
|
825
829
|
useRef as useRef5,
|
|
826
830
|
useState as useState9
|
|
827
831
|
} from "react";
|
|
@@ -890,6 +894,11 @@ var STRINGS_ES = {
|
|
|
890
894
|
steps: { waking: "Despertando al agente\u2026", session: "Preparando la sesi\xF3n\u2026", compacting: "Resumiendo la conversaci\xF3n\u2026" },
|
|
891
895
|
loadOlder: "Cargar mensajes anteriores",
|
|
892
896
|
loadingOlder: "Cargando\u2026",
|
|
897
|
+
queued: "Se manda cuando termine",
|
|
898
|
+
subagentsWorking: (n) => n === 1 ? "1 agente trabajando" : `${n} agentes trabajando`,
|
|
899
|
+
subagentsDone: (n) => n === 1 ? "1 agente termin\xF3" : `${n} agentes terminaron`,
|
|
900
|
+
subagentStop: "Detener",
|
|
901
|
+
subagentStatus: { running: "Trabajando", completed: "Termin\xF3", failed: "Fall\xF3", stopped: "Detenido" },
|
|
893
902
|
confirmTitle: "Necesito tu permiso para",
|
|
894
903
|
approve: "Aprobar",
|
|
895
904
|
reject: "Rechazar",
|
|
@@ -958,6 +967,11 @@ var STRINGS_EN = {
|
|
|
958
967
|
steps: { waking: "Waking the agent\u2026", session: "Starting the session\u2026", compacting: "Compacting the conversation\u2026" },
|
|
959
968
|
loadOlder: "Load earlier messages",
|
|
960
969
|
loadingOlder: "Loading\u2026",
|
|
970
|
+
queued: "Sends when this finishes",
|
|
971
|
+
subagentsWorking: (n) => n === 1 ? "1 agent working" : `${n} agents working`,
|
|
972
|
+
subagentsDone: (n) => n === 1 ? "1 agent finished" : `${n} agents finished`,
|
|
973
|
+
subagentStop: "Stop",
|
|
974
|
+
subagentStatus: { running: "Working", completed: "Done", failed: "Failed", stopped: "Stopped" },
|
|
961
975
|
confirmTitle: "I need your permission to",
|
|
962
976
|
approve: "Approve",
|
|
963
977
|
reject: "Reject",
|
|
@@ -2295,7 +2309,8 @@ function WaitLabel({
|
|
|
2295
2309
|
}
|
|
2296
2310
|
|
|
2297
2311
|
// src/react/usePilot.ts
|
|
2298
|
-
import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef4, useState as useState8 } from "react";
|
|
2312
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef4, useState as useState8 } from "react";
|
|
2313
|
+
var MAX_QUEUE_TRIES = 3;
|
|
2299
2314
|
var uuid = () => {
|
|
2300
2315
|
const c = globalThis.crypto;
|
|
2301
2316
|
if (c?.randomUUID) return c.randomUUID();
|
|
@@ -2320,6 +2335,13 @@ function userMessage(text, attachments) {
|
|
|
2320
2335
|
}
|
|
2321
2336
|
return { id: uuid(), role: "user", content: parts };
|
|
2322
2337
|
}
|
|
2338
|
+
function fromUserMessage(content) {
|
|
2339
|
+
if (typeof content === "string") return { text: content, attachments: [] };
|
|
2340
|
+
const parts = Array.isArray(content) ? content : [];
|
|
2341
|
+
const text = parts.filter((p) => p?.type === "text").map((p) => p.text ?? "").join("");
|
|
2342
|
+
const attachments = parts.filter((p) => p && p.type !== "text" && p.source).map((p) => ({ mimeType: p.mimeType ?? "application/octet-stream", ...p.filename ? { name: p.filename } : {}, ...p.source.type === "url" ? { url: p.source.value } : { data: p.source.value } }));
|
|
2343
|
+
return { text, attachments };
|
|
2344
|
+
}
|
|
2323
2345
|
function usePilot(transport, opts = {}) {
|
|
2324
2346
|
const [state, setState] = useState8(() => createPilotState(opts.initialTurns ?? []));
|
|
2325
2347
|
const ref = useRef4(state);
|
|
@@ -2331,6 +2353,17 @@ function usePilot(transport, opts = {}) {
|
|
|
2331
2353
|
const ctrl = useRef4(null);
|
|
2332
2354
|
const liveRun = useRef4(null);
|
|
2333
2355
|
const answers = useRef4(/* @__PURE__ */ new Map());
|
|
2356
|
+
const queuedTries = useRef4(/* @__PURE__ */ new Map());
|
|
2357
|
+
const flushRef = useRef4(() => {
|
|
2358
|
+
});
|
|
2359
|
+
const baseInput = () => {
|
|
2360
|
+
const ctx = optsRef.current.context;
|
|
2361
|
+
return { threadId, runId: uuid(), messages: [], tools: [], context: toAgUiContext(typeof ctx === "function" ? ctx() : ctx), state: {}, forwardedProps: extraProps() };
|
|
2362
|
+
};
|
|
2363
|
+
const extraProps = () => {
|
|
2364
|
+
const f = optsRef.current.forwardedProps;
|
|
2365
|
+
return (typeof f === "function" ? f() : f) ?? {};
|
|
2366
|
+
};
|
|
2334
2367
|
const commit = useCallback2((next) => {
|
|
2335
2368
|
if (next === ref.current) return;
|
|
2336
2369
|
ref.current = next;
|
|
@@ -2347,20 +2380,12 @@ function usePilot(transport, opts = {}) {
|
|
|
2347
2380
|
async (partial, reopening = false) => {
|
|
2348
2381
|
const t = transportRef.current;
|
|
2349
2382
|
if (!t) return;
|
|
2350
|
-
const
|
|
2351
|
-
const input = {
|
|
2352
|
-
threadId,
|
|
2353
|
-
runId: uuid(),
|
|
2354
|
-
tools: [],
|
|
2355
|
-
context: toAgUiContext(typeof ctx === "function" ? ctx() : ctx),
|
|
2356
|
-
state: {},
|
|
2357
|
-
forwardedProps: {},
|
|
2358
|
-
...partial
|
|
2359
|
-
};
|
|
2383
|
+
const input = { ...baseInput(), ...partial, forwardedProps: { ...extraProps(), ...partial.forwardedProps } };
|
|
2360
2384
|
ctrl.current?.abort();
|
|
2361
2385
|
const c = new AbortController();
|
|
2362
2386
|
ctrl.current = c;
|
|
2363
2387
|
const steering = typeof input.forwardedProps?.steer === "string";
|
|
2388
|
+
const userMsg = input.messages[0];
|
|
2364
2389
|
if (!steering && (input.messages.length || input.resume?.length)) liveRun.current = input.runId;
|
|
2365
2390
|
optsRef.current.onSend?.(input);
|
|
2366
2391
|
let ended = false;
|
|
@@ -2382,7 +2407,18 @@ function usePilot(transport, opts = {}) {
|
|
|
2382
2407
|
if (c.signal.aborted) return;
|
|
2383
2408
|
const code = err instanceof PilotHttpError ? err.code : void 0;
|
|
2384
2409
|
if ((code === "run_active" || code === "interrupt_pending") && !reopening) {
|
|
2385
|
-
|
|
2410
|
+
if (userMsg) {
|
|
2411
|
+
const { text, attachments } = fromUserMessage(userMsg.content);
|
|
2412
|
+
const tries = (queuedTries.current.get(input.messages[0].id) ?? 0) + 1;
|
|
2413
|
+
if (tries < MAX_QUEUE_TRIES) {
|
|
2414
|
+
let s = queueMessage(ref.current, text, attachments, tries);
|
|
2415
|
+
if (!steering) s = { ...s, busy: false, turnStartedAt: 0 };
|
|
2416
|
+
commit({ ...s, notice: null });
|
|
2417
|
+
} else {
|
|
2418
|
+
commit(sendFailed(ref.current, err instanceof Error && err.message ? err.message : "No se pudo mandar el mensaje."));
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
} else commit({ ...ref.current, notice: err instanceof Error ? err.message : null, effects: [] });
|
|
2386
2422
|
return runNow({ messages: [] }, true);
|
|
2387
2423
|
}
|
|
2388
2424
|
if (steering && !reopening) {
|
|
@@ -2392,7 +2428,11 @@ function usePilot(transport, opts = {}) {
|
|
|
2392
2428
|
commit(sendFailed(ref.current, err instanceof Error && err.message ? err.message : "No se pudo hablar con el agente."));
|
|
2393
2429
|
return;
|
|
2394
2430
|
}
|
|
2395
|
-
if (
|
|
2431
|
+
if (c.signal.aborted) return;
|
|
2432
|
+
if (ended) {
|
|
2433
|
+
if (ref.current.queued.length) setTimeout(() => flushRef.current(), 0);
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2396
2436
|
if (!reopening) return runNow({ messages: [] }, true);
|
|
2397
2437
|
commit(sendFailed(ref.current, "Se cort\xF3 la conexi\xF3n con el agente."));
|
|
2398
2438
|
},
|
|
@@ -2418,16 +2458,38 @@ function usePilot(transport, opts = {}) {
|
|
|
2418
2458
|
await runNow({ messages: [userMessage(text, [])], forwardedProps: { steer: live } });
|
|
2419
2459
|
return;
|
|
2420
2460
|
}
|
|
2461
|
+
await sendNew(text, attachments);
|
|
2462
|
+
},
|
|
2463
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2464
|
+
[commit, runNow]
|
|
2465
|
+
);
|
|
2466
|
+
const sendNew = useCallback2(
|
|
2467
|
+
async (text, attachments, tries = 0) => {
|
|
2421
2468
|
const resume = pendingResume();
|
|
2422
2469
|
answers.current.clear();
|
|
2423
2470
|
let s = ref.current;
|
|
2424
2471
|
for (const p of s.permissions) s = resolvePermission(s, p.id, "cancelled");
|
|
2425
2472
|
commit(beginSend(s, text, attachments));
|
|
2426
|
-
|
|
2473
|
+
const msg = userMessage(text, attachments);
|
|
2474
|
+
if (tries) queuedTries.current.set(msg.id, tries);
|
|
2475
|
+
try {
|
|
2476
|
+
await runNow({ messages: [msg], ...resume.length ? { resume } : {} });
|
|
2477
|
+
} finally {
|
|
2478
|
+
queuedTries.current.delete(msg.id);
|
|
2479
|
+
}
|
|
2427
2480
|
},
|
|
2428
2481
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2429
2482
|
[commit, runNow]
|
|
2430
2483
|
);
|
|
2484
|
+
flushRef.current = () => {
|
|
2485
|
+
const s = ref.current;
|
|
2486
|
+
if (!s.queued.length || s.busy || s.permissions.length || !transportRef.current) return;
|
|
2487
|
+
const { state: next, queued } = takeQueued(s);
|
|
2488
|
+
commit(next);
|
|
2489
|
+
const text = queued.map((q) => q.text).filter(Boolean).join("\n\n");
|
|
2490
|
+
const attachments = queued.flatMap((q) => q.attachments ?? []);
|
|
2491
|
+
void sendNew(text, attachments, Math.max(...queued.map((q) => q.tries)));
|
|
2492
|
+
};
|
|
2431
2493
|
const stop = useCallback2(() => {
|
|
2432
2494
|
const run = liveRun.current ?? (ref.current.busy ? ref.current.runId : null);
|
|
2433
2495
|
liveRun.current = null;
|
|
@@ -2436,12 +2498,9 @@ function usePilot(transport, opts = {}) {
|
|
|
2436
2498
|
commit(stopTurn(ref.current));
|
|
2437
2499
|
const t = transportRef.current;
|
|
2438
2500
|
if (!t || !run) return;
|
|
2439
|
-
const
|
|
2440
|
-
void t.run(
|
|
2441
|
-
|
|
2442
|
-
() => {
|
|
2443
|
-
}
|
|
2444
|
-
).catch(() => {
|
|
2501
|
+
const input = baseInput();
|
|
2502
|
+
void t.run({ ...input, forwardedProps: { ...input.forwardedProps, cancel: run } }, () => {
|
|
2503
|
+
}).catch(() => {
|
|
2445
2504
|
});
|
|
2446
2505
|
}, [commit, threadId]);
|
|
2447
2506
|
const retry = useCallback2(() => {
|
|
@@ -2498,8 +2557,24 @@ function usePilot(transport, opts = {}) {
|
|
|
2498
2557
|
setLoadingOlder(false);
|
|
2499
2558
|
}
|
|
2500
2559
|
}, [commit]);
|
|
2560
|
+
const stopSubagent = useCallback2(
|
|
2561
|
+
async (taskId) => {
|
|
2562
|
+
const t = transportRef.current;
|
|
2563
|
+
if (!t) return;
|
|
2564
|
+
commit(markSubagent(ref.current, taskId, "stopped"));
|
|
2565
|
+
const input = baseInput();
|
|
2566
|
+
await t.run({ ...input, forwardedProps: { ...input.forwardedProps, stopSubagent: taskId } }, () => {
|
|
2567
|
+
}).catch(() => {
|
|
2568
|
+
});
|
|
2569
|
+
if (!ref.current.busy) await runNow({ messages: [] }, true);
|
|
2570
|
+
},
|
|
2571
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2572
|
+
[commit, runNow]
|
|
2573
|
+
);
|
|
2574
|
+
const refresh = useCallback2(() => runNow({ messages: [] }, true), [runNow]);
|
|
2501
2575
|
const canSteer = Boolean(transport) && (opts.steer ?? state.agentState.caps?.steer === true);
|
|
2502
|
-
|
|
2576
|
+
const subagents = useMemo3(() => subagentsOf(state.agentState), [state.agentState]);
|
|
2577
|
+
return { state, threadId, send, stop, retry, respondPermission, recordDecision, expirePermission, canSteer, loadOlder, loadingOlder, subagents, stopSubagent, refresh };
|
|
2503
2578
|
}
|
|
2504
2579
|
|
|
2505
2580
|
// src/react/Piloto.tsx
|
|
@@ -2512,8 +2587,8 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2512
2587
|
const pilot = external ?? own;
|
|
2513
2588
|
const { state } = pilot;
|
|
2514
2589
|
const { turns, busy, error, notice, permissions, olderCount } = state;
|
|
2515
|
-
const strings =
|
|
2516
|
-
const config =
|
|
2590
|
+
const strings = useMemo4(() => ({ ...locale === "en" ? STRINGS_EN : STRINGS_ES, ...props.strings }), [locale, props.strings]);
|
|
2591
|
+
const config = useMemo4(
|
|
2517
2592
|
() => ({
|
|
2518
2593
|
strings,
|
|
2519
2594
|
toolVerbs: { ...locale === "en" ? TOOL_VERBS_EN : TOOL_VERBS_ES, ...props.toolVerbs },
|
|
@@ -2548,6 +2623,13 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2548
2623
|
draftSeen.current = id;
|
|
2549
2624
|
setDraftState((d) => ({ text: draftText, n: d.n + 1 }));
|
|
2550
2625
|
}, [draftText, draftKey]);
|
|
2626
|
+
const returned = state.returnedDraft;
|
|
2627
|
+
useEffect6(() => {
|
|
2628
|
+
if (!returned) return;
|
|
2629
|
+
const typed = composerBox.current?.querySelector("textarea")?.value ?? "";
|
|
2630
|
+
const text = [returned.text, typed].filter((x) => x.trim()).join("\n\n");
|
|
2631
|
+
setDraftState((d) => ({ text, n: d.n + 1 }));
|
|
2632
|
+
}, [returned?.n]);
|
|
2551
2633
|
const send = useCallback3(
|
|
2552
2634
|
(text, attachments = []) => {
|
|
2553
2635
|
const current = refsRef.current;
|
|
@@ -2709,8 +2791,9 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2709
2791
|
] });
|
|
2710
2792
|
const withPanel = (chat) => slots.sidePanel ? /* @__PURE__ */ jsxs8("div", { className: "flex min-h-0 flex-1 flex-row", children: [
|
|
2711
2793
|
/* @__PURE__ */ jsx10("div", { className: "relative flex min-h-0 min-w-0 flex-1 flex-col", children: chat }),
|
|
2712
|
-
slots.sidePanel(state.agentState)
|
|
2794
|
+
slots.sidePanel(state.agentState, pilot)
|
|
2713
2795
|
] }) : chat;
|
|
2796
|
+
const aboveComposer = typeof slots.aboveComposer === "function" ? slots.aboveComposer(pilot) : slots.aboveComposer;
|
|
2714
2797
|
const permissionCards = cards.map((p) => /* @__PURE__ */ jsx10(ConfirmCard, { request: p, onRespond: respond, onSettled: settle, onExpire: pilot.expirePermission }, p.id));
|
|
2715
2798
|
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 ? (
|
|
2716
2799
|
// Un hilo vacío enseña lo mismo que la portada: la cara, el saludo, la caja y por dónde
|
|
@@ -2729,7 +2812,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2729
2812
|
)
|
|
2730
2813
|
] }),
|
|
2731
2814
|
/* @__PURE__ */ jsxs8("div", { className: "w-full max-w-3xl", children: [
|
|
2732
|
-
|
|
2815
|
+
aboveComposer,
|
|
2733
2816
|
composer,
|
|
2734
2817
|
props.suggestions && /* @__PURE__ */ jsx10(Suggestions, { items: props.suggestions, onPick: (s) => send(s), onPrefill: (s) => setDraftState((d) => ({ text: s, n: d.n + 1 })) })
|
|
2735
2818
|
] })
|
|
@@ -2780,6 +2863,13 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2780
2863
|
}
|
|
2781
2864
|
) }, i - olderCount);
|
|
2782
2865
|
}),
|
|
2866
|
+
state.queued.map((q, i) => (
|
|
2867
|
+
// Esperan a que termine el run vivo (el servidor no los dejó entrar): salen solos.
|
|
2868
|
+
/* @__PURE__ */ jsxs8("div", { "data-queued": true, className: "flex flex-col items-end gap-1 opacity-60", children: [
|
|
2869
|
+
/* @__PURE__ */ jsx10(UserBubble, { turn: { role: "user", text: q.text, ...q.attachments ? { attachments: q.attachments } : {} } }),
|
|
2870
|
+
/* @__PURE__ */ jsx10("span", { className: "text-xs text-text-tertiary", children: t.queued })
|
|
2871
|
+
] }, `q${i}`)
|
|
2872
|
+
)),
|
|
2783
2873
|
permissionCards,
|
|
2784
2874
|
busy && !toolRunning && !cards.length && /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1.5", children: [
|
|
2785
2875
|
/* @__PURE__ */ jsx10(TypingDots, {}),
|
|
@@ -2803,11 +2893,52 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
|
2803
2893
|
children: /* @__PURE__ */ jsx10(ArrowDown, { className: "h-4 w-4" })
|
|
2804
2894
|
}
|
|
2805
2895
|
),
|
|
2806
|
-
|
|
2896
|
+
aboveComposer,
|
|
2807
2897
|
composer
|
|
2808
2898
|
] })
|
|
2809
2899
|
] })) }) });
|
|
2810
2900
|
});
|
|
2901
|
+
|
|
2902
|
+
// src/react/Subagents.tsx
|
|
2903
|
+
import { useState as useState10 } from "react";
|
|
2904
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2905
|
+
var DOT = {
|
|
2906
|
+
running: "bg-brand-500 animate-pulse",
|
|
2907
|
+
completed: "bg-text-tertiary",
|
|
2908
|
+
failed: "bg-text-danger",
|
|
2909
|
+
stopped: "bg-text-disabled"
|
|
2910
|
+
};
|
|
2911
|
+
function Subagents({ pilot, defaultOpen = false, showFinished = true, className = "" }) {
|
|
2912
|
+
const { strings: t } = usePiloto();
|
|
2913
|
+
const [open, setOpen] = useState10(defaultOpen);
|
|
2914
|
+
const all = pilot.subagents;
|
|
2915
|
+
const tasks = showFinished ? all : all.filter((x) => x.status === "running");
|
|
2916
|
+
if (!tasks.length) return null;
|
|
2917
|
+
const running = all.filter((x) => x.status === "running").length;
|
|
2918
|
+
return /* @__PURE__ */ jsxs9("div", { "data-subagents": true, className: `mb-2 rounded-2xl border border-border-primary bg-background-secondary text-sm ${className}`, children: [
|
|
2919
|
+
/* @__PURE__ */ jsxs9("button", { type: "button", onClick: () => setOpen((o) => !o), "aria-expanded": open, className: "flex w-full items-center gap-2 px-3 py-2 text-left text-text-secondary", children: [
|
|
2920
|
+
/* @__PURE__ */ jsx11("span", { className: `h-2 w-2 shrink-0 rounded-full ${running ? DOT.running : DOT.completed}` }),
|
|
2921
|
+
/* @__PURE__ */ jsx11("span", { className: "flex-1 truncate", children: running ? t.subagentsWorking(running) : t.subagentsDone(tasks.length) }),
|
|
2922
|
+
/* @__PURE__ */ jsx11("span", { "aria-hidden": true, className: `text-text-tertiary transition-transform ${open ? "rotate-180" : ""}`, children: "\u2304" })
|
|
2923
|
+
] }),
|
|
2924
|
+
open && /* @__PURE__ */ jsx11("ul", { className: "flex flex-col border-t border-border-primary", children: tasks.map((task) => /* @__PURE__ */ jsx11(SubagentRow, { task, onStop: () => void pilot.stopSubagent(task.id) }, task.id)) })
|
|
2925
|
+
] });
|
|
2926
|
+
}
|
|
2927
|
+
function SubagentRow({ task, onStop }) {
|
|
2928
|
+
const { strings: t } = usePiloto();
|
|
2929
|
+
const line = task.status === "running" ? task.lastTool : task.summary ?? task.lastTool;
|
|
2930
|
+
return /* @__PURE__ */ jsxs9("li", { "data-subagent": task.id, "data-status": task.status, className: "flex items-center gap-2 px-3 py-2", children: [
|
|
2931
|
+
/* @__PURE__ */ jsx11("span", { className: `h-2 w-2 shrink-0 rounded-full ${DOT[task.status] ?? DOT.completed}` }),
|
|
2932
|
+
/* @__PURE__ */ jsxs9("div", { className: "min-w-0 flex-1", children: [
|
|
2933
|
+
/* @__PURE__ */ jsx11("div", { className: "truncate text-text-primary", children: task.title ?? task.id }),
|
|
2934
|
+
/* @__PURE__ */ jsxs9("div", { className: "truncate text-xs text-text-tertiary", children: [
|
|
2935
|
+
t.subagentStatus[task.status] ?? task.status,
|
|
2936
|
+
line ? ` \xB7 ${line}` : ""
|
|
2937
|
+
] })
|
|
2938
|
+
] }),
|
|
2939
|
+
task.status === "running" && /* @__PURE__ */ jsx11("button", { type: "button", onClick: onStop, className: "shrink-0 rounded-full border border-border-primary px-2.5 py-0.5 text-xs text-text-secondary transition hover:text-text-danger", children: t.subagentStop })
|
|
2940
|
+
] });
|
|
2941
|
+
}
|
|
2811
2942
|
export {
|
|
2812
2943
|
AgentBubble,
|
|
2813
2944
|
AgentSteps,
|
|
@@ -2831,6 +2962,7 @@ export {
|
|
|
2831
2962
|
RobotArmIcon,
|
|
2832
2963
|
STRINGS_EN,
|
|
2833
2964
|
STRINGS_ES,
|
|
2965
|
+
Subagents,
|
|
2834
2966
|
Suggestions,
|
|
2835
2967
|
TOOL_VERBS_EN,
|
|
2836
2968
|
TOOL_VERBS_ES,
|
|
@@ -2858,10 +2990,12 @@ export {
|
|
|
2858
2990
|
isNarration,
|
|
2859
2991
|
isRunning,
|
|
2860
2992
|
lastUserTurn,
|
|
2993
|
+
markSubagent,
|
|
2861
2994
|
messagesToTurns,
|
|
2862
2995
|
mimeOf,
|
|
2863
2996
|
narrationToMarkdown,
|
|
2864
2997
|
prependHistory,
|
|
2998
|
+
queueMessage,
|
|
2865
2999
|
readSse,
|
|
2866
3000
|
reduceAgUiEvent,
|
|
2867
3001
|
renderInline,
|
|
@@ -2870,8 +3004,10 @@ export {
|
|
|
2870
3004
|
splitNarration,
|
|
2871
3005
|
stepLabel,
|
|
2872
3006
|
stopTurn,
|
|
3007
|
+
subagentsOf,
|
|
2873
3008
|
summarizeTools,
|
|
2874
3009
|
systemNoteOf,
|
|
3010
|
+
takeQueued,
|
|
2875
3011
|
toAgUiContext,
|
|
2876
3012
|
toPermission,
|
|
2877
3013
|
toolName,
|
package/dist/react/strings.d.ts
CHANGED
|
@@ -58,6 +58,14 @@ export interface PilotStrings {
|
|
|
58
58
|
steps: Record<string, string>;
|
|
59
59
|
loadOlder: string;
|
|
60
60
|
loadingOlder: string;
|
|
61
|
+
/** Bajo un mensaje que espera a que termine el run vivo. */
|
|
62
|
+
queued: string;
|
|
63
|
+
/** «2 agentes trabajando». */
|
|
64
|
+
subagentsWorking: (n: number) => string;
|
|
65
|
+
/** Sin ninguno corriendo. */
|
|
66
|
+
subagentsDone: (n: number) => string;
|
|
67
|
+
subagentStop: string;
|
|
68
|
+
subagentStatus: Record<string, string>;
|
|
61
69
|
confirmTitle: string;
|
|
62
70
|
approve: string;
|
|
63
71
|
reject: string;
|
package/dist/react/usePilot.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BaseEvent, Message, RunAgentInput } from "@ag-ui/core";
|
|
2
|
-
import type { Attachment, Decision, HistoryPage, PermissionRequest, PilotState, Reference, Turn } from "../core/types";
|
|
2
|
+
import type { Attachment, Decision, HistoryPage, PermissionRequest, PilotState, Reference, SubagentTask, Turn } from "../core/types";
|
|
3
3
|
import { type PilotTransport } from "../transport";
|
|
4
4
|
export interface TurnDoneInfo {
|
|
5
5
|
/** El texto final de la respuesta. */
|
|
@@ -45,6 +45,11 @@ export interface UsePilotOptions {
|
|
|
45
45
|
loadOlder?: (before: string | undefined) => Promise<{
|
|
46
46
|
messages: Message[];
|
|
47
47
|
} & HistoryPage>;
|
|
48
|
+
/**
|
|
49
|
+
* Lo que viaja en `forwardedProps` de cada run, además de lo del protocolo (`cancel`, `steer`,
|
|
50
|
+
* `stopSubagent`), p. ej. `{permissions: "ask"}` en gs. Una función se evalúa al mandar.
|
|
51
|
+
*/
|
|
52
|
+
forwardedProps?: Record<string, unknown> | (() => Record<string, unknown>);
|
|
48
53
|
}
|
|
49
54
|
export interface SendOptions {
|
|
50
55
|
references?: Reference[];
|
|
@@ -68,6 +73,15 @@ export interface Pilot {
|
|
|
68
73
|
/** Antepone la página anterior del hilo (si hay `loadOlder` y `state.history.hasMoreBefore`). */
|
|
69
74
|
loadOlder: () => Promise<void>;
|
|
70
75
|
loadingOlder: boolean;
|
|
76
|
+
/** Los subagentes del agente (`state.subagents.tasks`), al día con cada STATE_SNAPSHOT/DELTA. */
|
|
77
|
+
subagents: SubagentTask[];
|
|
78
|
+
/** Detiene un subagente (`forwardedProps.stopSubagent`). No toca el run vivo. */
|
|
79
|
+
stopSubagent: (taskId: string) => Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Vuelve a pedir el hilo (POST sin mensaje → snapshot + estado + run vivo). Para cuando la app sabe
|
|
82
|
+
* por fuera que algo cambió (p. ej. el bus `/me/events` avisa que un subagente terminó).
|
|
83
|
+
*/
|
|
84
|
+
refresh: () => Promise<void>;
|
|
71
85
|
}
|
|
72
86
|
/** El contexto de la app como `context` de AG-UI: `[{description, value}]`, `value` siempre texto. */
|
|
73
87
|
export declare function toAgUiContext(ctx: unknown): RunAgentInput["context"];
|
package/dist/styles.css
CHANGED
|
@@ -478,6 +478,9 @@
|
|
|
478
478
|
.piloto .rotate-90 {
|
|
479
479
|
rotate: 90deg;
|
|
480
480
|
}
|
|
481
|
+
.piloto .rotate-180 {
|
|
482
|
+
rotate: 180deg;
|
|
483
|
+
}
|
|
481
484
|
.piloto .transform {
|
|
482
485
|
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
|
483
486
|
}
|
|
@@ -653,6 +656,10 @@
|
|
|
653
656
|
border-style: var(--tw-border-style);
|
|
654
657
|
border-width: 2px;
|
|
655
658
|
}
|
|
659
|
+
.piloto .border-t {
|
|
660
|
+
border-top-style: var(--tw-border-style);
|
|
661
|
+
border-top-width: 1px;
|
|
662
|
+
}
|
|
656
663
|
.piloto .border-b-2 {
|
|
657
664
|
border-bottom-style: var(--tw-border-style);
|
|
658
665
|
border-bottom-width: 2px;
|
|
@@ -843,6 +850,12 @@
|
|
|
843
850
|
background-color: color-mix(in oklab, var(--color-sidebar) 80%, transparent);
|
|
844
851
|
}
|
|
845
852
|
}
|
|
853
|
+
.piloto .bg-text-danger {
|
|
854
|
+
background-color: var(--color-text-danger);
|
|
855
|
+
}
|
|
856
|
+
.piloto .bg-text-disabled {
|
|
857
|
+
background-color: var(--color-text-disabled);
|
|
858
|
+
}
|
|
846
859
|
.piloto .bg-text-primary {
|
|
847
860
|
background-color: var(--color-text-primary);
|
|
848
861
|
}
|
|
@@ -852,6 +865,9 @@
|
|
|
852
865
|
background-color: color-mix(in oklab, var(--color-text-secondary) 70%, transparent);
|
|
853
866
|
}
|
|
854
867
|
}
|
|
868
|
+
.piloto .bg-text-tertiary {
|
|
869
|
+
background-color: var(--color-text-tertiary);
|
|
870
|
+
}
|
|
855
871
|
.piloto .bg-transparent {
|
|
856
872
|
background-color: transparent;
|
|
857
873
|
}
|
|
@@ -1131,6 +1147,9 @@
|
|
|
1131
1147
|
.piloto .opacity-0 {
|
|
1132
1148
|
opacity: 0%;
|
|
1133
1149
|
}
|
|
1150
|
+
.piloto .opacity-60 {
|
|
1151
|
+
opacity: 60%;
|
|
1152
|
+
}
|
|
1134
1153
|
.piloto .opacity-70 {
|
|
1135
1154
|
opacity: 70%;
|
|
1136
1155
|
}
|
|
@@ -1375,6 +1394,9 @@
|
|
|
1375
1394
|
.piloto .hover\:text-foreground:hover {
|
|
1376
1395
|
color: var(--color-foreground);
|
|
1377
1396
|
}
|
|
1397
|
+
.piloto .hover\:text-text-danger:hover {
|
|
1398
|
+
color: var(--color-text-danger);
|
|
1399
|
+
}
|
|
1378
1400
|
.piloto .hover\:text-text-primary:hover {
|
|
1379
1401
|
color: var(--color-text-primary);
|
|
1380
1402
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghostykit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|