ghostykit 0.5.1 → 0.6.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 +11 -4
- package/dist/chunks/{chunk-ULNSZHZV.js → chunk-4XKQ6XAJ.js} +72 -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,10 +111,12 @@ 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
|
}
|
|
117
|
-
var
|
|
119
|
+
var NOT_IN_TURN = /* @__PURE__ */ new Set(["ghosty.title", "ghosty.injected", "ghosty.history", "ghosty.tool", "ghosty.replace", "ghosty.page"]);
|
|
118
120
|
function patchAssistant(s, patch) {
|
|
119
121
|
const turns = [...s.turns];
|
|
120
122
|
const last = turns[turns.length - 1];
|
|
@@ -199,7 +201,7 @@ function messagesToTurns(messages) {
|
|
|
199
201
|
} else if (m.role === "assistant") {
|
|
200
202
|
const t = assistant();
|
|
201
203
|
if (!t.id && m.id) t.id = m.id;
|
|
202
|
-
const custom = m.custom;
|
|
204
|
+
const custom = m.metadata?.custom;
|
|
203
205
|
if (Array.isArray(custom) && custom.length) t.custom = [...t.custom ?? [], ...custom.filter((c) => c && typeof c.name === "string")];
|
|
204
206
|
const text = m.content ?? "";
|
|
205
207
|
if (text) t.text = t.text ? `${t.text}
|
|
@@ -255,7 +257,9 @@ function applySnapshot(s, messages) {
|
|
|
255
257
|
turns: [...prefix, ...turns],
|
|
256
258
|
olderCount: prefix.length,
|
|
257
259
|
history: keepOlder ? s.history : s.historyNext ?? null,
|
|
258
|
-
historyNext: void 0
|
|
260
|
+
historyNext: void 0,
|
|
261
|
+
// Lo abierto no viene en el snapshot: si sigue, el servidor lo re-emite enseguida.
|
|
262
|
+
openText: null
|
|
259
263
|
};
|
|
260
264
|
}
|
|
261
265
|
function reduceAgUiEvent(prev, e, now = Date.now()) {
|
|
@@ -263,20 +267,30 @@ function reduceAgUiEvent(prev, e, now = Date.now()) {
|
|
|
263
267
|
switch (e.type) {
|
|
264
268
|
case "RUN_STARTED":
|
|
265
269
|
return { ...s, busy: true, error: null, step: null, runId: e.runId ?? s.runId };
|
|
266
|
-
case "TEXT_MESSAGE_START":
|
|
270
|
+
case "TEXT_MESSAGE_START": {
|
|
267
271
|
if (e.role && e.role !== "assistant") return s;
|
|
268
|
-
|
|
272
|
+
const id = typeof e.messageId === "string" ? e.messageId : null;
|
|
273
|
+
const last = s.turns[s.turns.length - 1];
|
|
274
|
+
if (id && s.openText?.id === id && last?.role === "assistant" && last.text.length >= s.openText.start) {
|
|
275
|
+
const start = s.openText.start;
|
|
276
|
+
return patchAssistant(s, (t) => ({ ...t, text: t.text.slice(0, start) }));
|
|
277
|
+
}
|
|
278
|
+
const next = patchAssistant(s, (t) => ({ ...t, text: t.text && !t.text.endsWith("\n") ? `${t.text}
|
|
269
279
|
|
|
270
280
|
` : t.text }));
|
|
281
|
+
return { ...next, openText: id ? { id, start: next.turns[next.turns.length - 1].text.length } : null };
|
|
282
|
+
}
|
|
271
283
|
case "TEXT_MESSAGE_CONTENT":
|
|
272
284
|
case "TEXT_MESSAGE_CHUNK":
|
|
273
285
|
if (e.role && e.role !== "assistant") return s;
|
|
274
286
|
if (typeof e.delta !== "string" || !e.delta) return s;
|
|
275
287
|
return patchAssistant(s, (t) => ({ ...t, text: t.text + e.delta }));
|
|
276
288
|
case "TEXT_MESSAGE_END":
|
|
277
|
-
return s;
|
|
278
|
-
case "TOOL_CALL_START":
|
|
279
|
-
|
|
289
|
+
return s.openText && s.openText.id === e.messageId ? { ...s, openText: null } : s;
|
|
290
|
+
case "TOOL_CALL_START": {
|
|
291
|
+
const known = s.turns[s.turns.length - 1]?.tools?.some((x) => x.id === e.toolCallId);
|
|
292
|
+
return upsertTool(s, e.toolCallId, { title: e.toolCallName, status: "in_progress", ...known ? { args: "" } : {} });
|
|
293
|
+
}
|
|
280
294
|
case "TOOL_CALL_CHUNK": {
|
|
281
295
|
const known = s.turns[s.turns.length - 1]?.tools?.find((x) => x.id === e.toolCallId);
|
|
282
296
|
let next = known ? s : upsertTool(s, e.toolCallId, { title: e.toolCallName, status: "in_progress" });
|
|
@@ -332,7 +346,18 @@ function reduceAgUiEvent(prev, e, now = Date.now()) {
|
|
|
332
346
|
if (str(v.detail)) patch.detail = str(v.detail);
|
|
333
347
|
return upsertTool(s, v.toolCallId, patch);
|
|
334
348
|
}
|
|
335
|
-
if (
|
|
349
|
+
if (e.name === "ghosty.replace") {
|
|
350
|
+
const content = e.value?.content;
|
|
351
|
+
if (typeof content !== "string") return s;
|
|
352
|
+
return patchAssistant(s, (t) => {
|
|
353
|
+
const cut = t.cuts?.length ? t.cuts[t.cuts.length - 1] : 0;
|
|
354
|
+
const head = t.text.slice(0, cut);
|
|
355
|
+
return { ...t, text: head.trim() ? `${head}
|
|
356
|
+
|
|
357
|
+
${content}` : content };
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
if (typeof e.name !== "string" || NOT_IN_TURN.has(e.name)) return s;
|
|
336
361
|
return patchAssistant(s, (t) => ({ ...t, custom: [...t.custom ?? [], { name: e.name, value: e.value, ...typeof e.timestamp === "number" ? { timestamp: e.timestamp } : {} }] }));
|
|
337
362
|
default:
|
|
338
363
|
return s;
|
|
@@ -344,7 +369,7 @@ function toHistory(v) {
|
|
|
344
369
|
return { hasMoreBefore: h.hasMoreBefore === true, ...typeof h.before === "string" ? { before: h.before } : {} };
|
|
345
370
|
}
|
|
346
371
|
function finish(s0, e, now, failed = false) {
|
|
347
|
-
let s = { ...s0, busy: false, step: null };
|
|
372
|
+
let s = { ...s0, busy: false, step: null, openText: null };
|
|
348
373
|
const outcome = e.outcome;
|
|
349
374
|
const interrupted = outcome?.type === "interrupt" && Array.isArray(outcome.interrupts) && outcome.interrupts.length > 0;
|
|
350
375
|
const clockOffset = typeof e.timestamp === "number" ? now - e.timestamp : 0;
|
|
@@ -356,10 +381,12 @@ function finish(s0, e, now, failed = false) {
|
|
|
356
381
|
s = { ...s, turns: s.turns.map((t) => t.tools?.some((x) => waiting.has(x.id)) ? { ...t, tools: t.tools.map((x) => waiting.has(x.id) && x.status !== "completed" ? { ...x, status: "pending" } : x) } : t) };
|
|
357
382
|
}
|
|
358
383
|
}
|
|
359
|
-
const
|
|
384
|
+
const costUsd = typeof e.metadata?.costUsd === "number" ? e.metadata.costUsd : void 0;
|
|
385
|
+
const tokens = Array.isArray(e.usage) && e.usage.length ? e.usage.reduce(
|
|
360
386
|
(a, u) => ({ ...a, used: a.used + (u.totalTokens ?? (u.inputTokens ?? 0) + (u.outputTokens ?? 0)), input: (a.input ?? 0) + (u.inputTokens ?? 0), output: (a.output ?? 0) + (u.outputTokens ?? 0) }),
|
|
361
387
|
{ used: 0, size: 0, cost: 0 }
|
|
362
388
|
) : null;
|
|
389
|
+
const usage = tokens || costUsd !== void 0 ? { ...tokens ?? { used: 0, size: 0, cost: 0 }, ...costUsd !== void 0 ? { cost: costUsd } : {} } : null;
|
|
363
390
|
const t0 = s.turnStartedAt;
|
|
364
391
|
const seconds = t0 ? Math.max(1, Math.round((now - t0) / 1e3)) : void 0;
|
|
365
392
|
const turns = [...s.turns];
|
|
@@ -382,7 +409,32 @@ function beginSend(prev, text, attachments = [], now = Date.now()) {
|
|
|
382
409
|
return { ...prev, turns, busy: true, error: null, notice: null, turnStartedAt: now, effects: [{ type: "turn-start" }] };
|
|
383
410
|
}
|
|
384
411
|
function beginSteer(prev, text) {
|
|
385
|
-
return { ...prev, turns: [...prev.turns, { role: "user", text }], notice: null, effects: [] };
|
|
412
|
+
return { ...prev, turns: [...prev.turns, { role: "user", text }], notice: null, openText: null, effects: [] };
|
|
413
|
+
}
|
|
414
|
+
function queueMessage(prev, text, attachments = [], tries = 1) {
|
|
415
|
+
const turns = [...prev.turns];
|
|
416
|
+
for (let i = turns.length - 1; i >= 0; i--) {
|
|
417
|
+
if (turns[i].role === "user" && turns[i].text === text) {
|
|
418
|
+
turns.splice(i, 1);
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return { ...prev, turns, queued: [...prev.queued, { text, ...attachments.length ? { attachments } : {}, tries }], effects: [] };
|
|
423
|
+
}
|
|
424
|
+
function takeQueued(prev) {
|
|
425
|
+
return { state: { ...prev, queued: [], effects: [] }, queued: prev.queued };
|
|
426
|
+
}
|
|
427
|
+
function subagentsOf(agentState) {
|
|
428
|
+
const tasks = agentState.subagents?.tasks;
|
|
429
|
+
if (!Array.isArray(tasks)) return [];
|
|
430
|
+
return tasks.filter((t) => Boolean(t) && typeof t === "object" && typeof t.id === "string");
|
|
431
|
+
}
|
|
432
|
+
function markSubagent(prev, taskId, status) {
|
|
433
|
+
const tasks = subagentsOf(prev.agentState);
|
|
434
|
+
if (!tasks.some((t) => t.id === taskId)) return prev;
|
|
435
|
+
const subagents = { ...prev.agentState.subagents, tasks: tasks.map((t) => t.id === taskId ? { ...t, status } : t) };
|
|
436
|
+
const agentState = { ...prev.agentState, subagents };
|
|
437
|
+
return { ...prev, agentState, effects: [{ type: "state", state: agentState }] };
|
|
386
438
|
}
|
|
387
439
|
function prependHistory(prev, messages, page) {
|
|
388
440
|
const have = new Set(prev.turns.map((t) => t.id).filter(Boolean));
|
|
@@ -393,7 +445,10 @@ function sendFailed(prev, message) {
|
|
|
393
445
|
return { ...prev, busy: false, turnStartedAt: 0, error: message, effects: [] };
|
|
394
446
|
}
|
|
395
447
|
function stopTurn(prev) {
|
|
396
|
-
|
|
448
|
+
const text = prev.queued.map((q) => q.text).filter(Boolean).join("\n\n");
|
|
449
|
+
const attachments = prev.queued.flatMap((q) => q.attachments ?? []);
|
|
450
|
+
const returnedDraft = prev.queued.length ? { text, ...attachments.length ? { attachments } : {}, n: (prev.returnedDraft?.n ?? 0) + 1 } : prev.returnedDraft;
|
|
451
|
+
return { ...prev, busy: false, step: null, queued: [], returnedDraft, turnStartedAt: 0, turns: closeOpenTools(prev.turns, "cancelled"), effects: [] };
|
|
397
452
|
}
|
|
398
453
|
function dropLastExchange(prev) {
|
|
399
454
|
const turns = [...prev.turns];
|
|
@@ -635,6 +690,10 @@ export {
|
|
|
635
690
|
reduceAgUiEvent,
|
|
636
691
|
beginSend,
|
|
637
692
|
beginSteer,
|
|
693
|
+
queueMessage,
|
|
694
|
+
takeQueued,
|
|
695
|
+
subagentsOf,
|
|
696
|
+
markSubagent,
|
|
638
697
|
prependHistory,
|
|
639
698
|
sendFailed,
|
|
640
699
|
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-4XKQ6XAJ.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-4XKQ6XAJ.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.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",
|