ghostykit 0.2.0 → 0.2.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 CHANGED
@@ -107,6 +107,7 @@ const ref = useRef<PilotoHandle>(null);
107
107
  agentName="Admin assistant"
108
108
  welcome="How can I help with your crons?"
109
109
  suggestions={["Why did the monitor fail?"]}
110
+ draft={{ text: "Why did the monitor fail?", key: row.id }} // prefilled + focused, not sent
110
111
  context={() => ({ screen: "crons", selected })} // sent with EVERY turn
111
112
  toolVerbs={{ run_cron: { one: "Ran a cron", many: "Ran %n crons", running: "Running the cron…" } }}
112
113
  upload={async (file) => ({ url: await myUpload(file) })}
@@ -52,8 +52,16 @@ function parse(raw) {
52
52
  }
53
53
  }
54
54
  function defaultHistory(json) {
55
- const msgs = json?.messages ?? [];
56
- return msgs.filter((m) => typeof m.text === "string").map((m) => ({ role: m.role === "user" ? "user" : "assistant", text: m.text }));
55
+ const j = json;
56
+ const turns = (j?.messages ?? []).filter((m) => typeof m.text === "string").map((m) => ({ role: m.role === "user" ? "user" : "assistant", text: m.text }));
57
+ const permissions = (j?.pending ?? []).filter((p) => typeof p === "object" && p !== null && typeof p.id === "string").map((p) => ({
58
+ id: p.id,
59
+ title: String(p.title ?? ""),
60
+ ...typeof p.tool === "string" ? { tool: p.tool } : {},
61
+ options: Array.isArray(p.options) ? p.options : [],
62
+ ...p.expiresAt != null ? { expiresAt: p.expiresAt } : {}
63
+ }));
64
+ return { turns, permissions };
57
65
  }
58
66
  function uuid() {
59
67
  const c = globalThis.crypto;
@@ -146,9 +154,9 @@ function createPostStreamTransport(target, options = {}) {
146
154
  };
147
155
  },
148
156
  async history() {
149
- if (!historyUrl) return [];
157
+ if (!historyUrl) return { turns: [] };
150
158
  const res = await doFetch(historyUrl, { credentials, headers: { accept: "application/json" } });
151
- if (!res.ok) return [];
159
+ if (!res.ok) return { turns: [] };
152
160
  return (opts.parseHistory ?? defaultHistory)(await res.json().catch(() => null));
153
161
  },
154
162
  async send(input) {
@@ -411,6 +411,13 @@ function resolvePermission(prev, id, optionId, resolved, detail, asked) {
411
411
  s = { ...s, turns };
412
412
  return s;
413
413
  }
414
+ function applyHistory(prev, turns, permissions = []) {
415
+ let s = { ...prev, effects: [] };
416
+ if (turns.length && !prev.turns.length) s.turns = turns;
417
+ const fresh = permissions.filter((p) => !prev.permissions.some((q) => q.id === p.id));
418
+ if (fresh.length) s.permissions = [...prev.permissions, ...fresh];
419
+ return s.turns === prev.turns && !fresh.length ? prev : s;
420
+ }
414
421
  function appendAgentMessages(prev, texts) {
415
422
  if (!texts.length) return prev;
416
423
  return { ...prev, effects: [], turns: [...prev.turns, ...texts.map((text) => ({ role: "assistant", text, arrived: true }))] };
@@ -643,6 +650,7 @@ export {
643
650
  stopTurn,
644
651
  dropLastExchange,
645
652
  resolvePermission,
653
+ applyHistory,
646
654
  appendAgentMessages,
647
655
  reconcileWith,
648
656
  lastUserTurn,
@@ -1,5 +1,5 @@
1
1
  export * from "./types";
2
- export { RESTART_NOTICE, RESUME_TEXT, appendAgentMessages, beginSend, createPilotState, dropLastExchange, expiryMs, lastUserTurn, reconcileWith, reducePilotEvent, resolvePermission, resumeMode, sendAccepted, sendFailed, sendRejectedBusy, stopTurn, } from "./reducer";
2
+ export { RESTART_NOTICE, RESUME_TEXT, appendAgentMessages, applyHistory, beginSend, createPilotState, dropLastExchange, expiryMs, lastUserTurn, reconcileWith, reducePilotEvent, resolvePermission, resumeMode, sendAccepted, sendFailed, sendRejectedBusy, stopTurn, } from "./reducer";
3
3
  export { reconcileTurns } from "./reconcile";
4
4
  export { isNarration, narrationToMarkdown, splitNarration } from "./narration";
5
5
  export { cleanReplyOption, extractReplyOptions, type ReplyOptions } from "./options";
@@ -4,6 +4,7 @@ import {
4
4
  TOOL_VERBS_EN,
5
5
  TOOL_VERBS_ES,
6
6
  appendAgentMessages,
7
+ applyHistory,
7
8
  beginSend,
8
9
  cleanReplyOption,
9
10
  createPilotState,
@@ -28,7 +29,7 @@ import {
28
29
  stopTurn,
29
30
  summarizeTools,
30
31
  toolName
31
- } from "../chunks/chunk-XWOGK2HN.js";
32
+ } from "../chunks/chunk-O6FCE4CS.js";
32
33
  import {
33
34
  applyReferences,
34
35
  cleanUserText,
@@ -41,6 +42,7 @@ export {
41
42
  TOOL_VERBS_EN,
42
43
  TOOL_VERBS_ES,
43
44
  appendAgentMessages,
45
+ applyHistory,
44
46
  applyReferences,
45
47
  beginSend,
46
48
  cleanReplyOption,
@@ -36,6 +36,11 @@ export declare function dropLastExchange(prev: PilotState): PilotState;
36
36
  export declare function resolvePermission(prev: PilotState, id: string, optionId: string, resolved: boolean, detail?: string,
37
37
  /** La tarjeta como estaba al contestarla (el stream pudo retirarla mientras tanto). */
38
38
  asked?: PermissionRequest): PilotState;
39
+ /**
40
+ * El historial guardado (al montar): el hilo, si la pantalla sigue vacía, y las tarjetas de permiso
41
+ * que siguen abiertas (sin duplicar las que el stream ya trajo).
42
+ */
43
+ export declare function applyHistory(prev: PilotState, turns: Turn[], permissions?: PermissionRequest[]): PilotState;
39
44
  /** Un mensaje del agente que entró fuera de un turno (el remate de los subagentes). */
40
45
  export declare function appendAgentMessages(prev: PilotState, texts: string[]): PilotState;
41
46
  /** La copia del hilo cambió: trae los mensajes que el usuario mandó desde OTRO cliente. Sólo en reposo. */
@@ -0,0 +1,5 @@
1
+ import { type ReactNode } from "react";
2
+ export declare function renderInline(text: string): ReactNode[];
3
+ export declare function InlineMarkdown({ children }: {
4
+ children: string;
5
+ }): import("react").JSX.Element;
@@ -65,6 +65,14 @@ export interface PilotoProps extends UsePilotOptions {
65
65
  * `transport.respondPermission`. Devuelve `{resolved:false}` si ya no estaba abierto.
66
66
  */
67
67
  onPermission?: PermissionHandler;
68
+ /**
69
+ * Una pregunta lista en la caja (sin mandarla) y el foco en ella: al abrir el copiloto desde una
70
+ * fila («Pídeselo»). Cambiar el texto —o `key`, para repetir el mismo— la vuelve a poner.
71
+ */
72
+ draft?: string | {
73
+ text: string;
74
+ key?: string | number;
75
+ } | null;
68
76
  /** Referencias controladas (si no, Piloto las guarda; agrégalas con `ref.addReference`). */
69
77
  references?: Reference[];
70
78
  onReferencesChange?: (refs: Reference[]) => void;
@@ -3,6 +3,7 @@ export { usePilot, useSseTransport, type PermissionAnswer, type PermissionHandle
3
3
  export { AgentBubble, CopyUserMessage, DeliveryChips, ReplyOptionButtons, TurnFooter, UsageStats, UserBubble, type AgentBubbleProps } from "./Bubble";
4
4
  export { AgentSteps, ClawIcon, DEFAULT_TOOL_ICONS, Narration, ToolRow } from "./Steps";
5
5
  export { LinkSafetyModal, Markdown } from "./Markdown";
6
+ export { InlineMarkdown, renderInline } from "./InlineMarkdown";
6
7
  export { useTextAtPace } from "./TextAtPace";
7
8
  export { Composer, ModelPicker, mimeOf, type ComposerProps, type UploadResult } from "./Composer";
8
9
  export { ConfirmCard, DecisionLines } from "./ConfirmCard";
@@ -11,4 +12,4 @@ export { ConnectingState, TypingDots, WaitLabel } from "./Status";
11
12
  export { PilotoContext, usePiloto, type MarkdownOptions, type PilotIcon, type PilotoConfig, type PilotTheme } from "./context";
12
13
  export { STRINGS_EN, STRINGS_ES, type PilotStrings } from "./strings";
13
14
  export * from "../core";
14
- export { PilotHttpError, createPostStreamTransport, createSseTransport, readSse, type PilotListener, type PilotTransport, type PostStreamTransportOptions, type SseTransportOptions, type SubscribeOptions, } from "../transport";
15
+ export { PilotHttpError, createPostStreamTransport, createSseTransport, readSse, type PilotHistory, type PilotListener, type PilotTransport, type PostStreamTransportOptions, type SseTransportOptions, type SubscribeOptions, } from "../transport";
@@ -4,6 +4,7 @@ import {
4
4
  TOOL_VERBS_EN,
5
5
  TOOL_VERBS_ES,
6
6
  appendAgentMessages,
7
+ applyHistory,
7
8
  beginSend,
8
9
  cleanReplyOption,
9
10
  createPilotState,
@@ -28,13 +29,13 @@ import {
28
29
  stopTurn,
29
30
  summarizeTools,
30
31
  toolName
31
- } from "../chunks/chunk-XWOGK2HN.js";
32
+ } from "../chunks/chunk-O6FCE4CS.js";
32
33
  import {
33
34
  PilotHttpError,
34
35
  createPostStreamTransport,
35
36
  createSseTransport,
36
37
  readSse
37
- } from "../chunks/chunk-KZOMMG2D.js";
38
+ } from "../chunks/chunk-D6JXJWYM.js";
38
39
  import {
39
40
  applyReferences,
40
41
  cleanUserText,
@@ -1312,14 +1313,46 @@ function Markdown({ children, animate = false, className }) {
1312
1313
 
1313
1314
  // src/react/Steps.tsx
1314
1315
  import { useState as useState4 } from "react";
1315
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1316
+
1317
+ // src/react/InlineMarkdown.tsx
1318
+ import { Fragment as Fragment2 } from "react";
1319
+ import { jsx as jsx4 } from "react/jsx-runtime";
1320
+ var TOKEN = /(`[^`\n]+`)|(\*\*[^*\n]+\*\*|__[^_\n]+__)|(\*[^*\s][^*\n]*\*|_[^_\s][^_\n]*_)|(\[[^\]\n]+\]\(https?:\/\/[^)\s]+\))/g;
1321
+ function renderInline(text) {
1322
+ const out = [];
1323
+ let last = 0;
1324
+ let k = 0;
1325
+ for (const m of text.matchAll(TOKEN)) {
1326
+ const at = m.index ?? 0;
1327
+ if (at > last) out.push(text.slice(last, at));
1328
+ const t = m[0];
1329
+ if (m[1]) out.push(/* @__PURE__ */ jsx4("code", { children: t.slice(1, -1) }, k++));
1330
+ else if (m[2]) out.push(/* @__PURE__ */ jsx4("strong", { children: renderInline(t.slice(2, -2)) }, k++));
1331
+ else if (m[3]) out.push(/* @__PURE__ */ jsx4("em", { children: renderInline(t.slice(1, -1)) }, k++));
1332
+ else {
1333
+ const [, label, href] = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(t) ?? [];
1334
+ out.push(
1335
+ /* @__PURE__ */ jsx4("a", { href, target: "_blank", rel: "noopener noreferrer", children: label }, k++)
1336
+ );
1337
+ }
1338
+ last = at + t.length;
1339
+ }
1340
+ if (last < text.length) out.push(text.slice(last));
1341
+ return out;
1342
+ }
1343
+ function InlineMarkdown({ children }) {
1344
+ return /* @__PURE__ */ jsx4(Fragment2, { children: renderInline(children) });
1345
+ }
1346
+
1347
+ // src/react/Steps.tsx
1348
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1316
1349
  function ClawIcon({ className }) {
1317
- return /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", className: `piloto-claw ${className ?? ""}`, "aria-hidden": "true", children: /* @__PURE__ */ jsxs3("g", { className: "piloto-claw-rig", children: [
1318
- /* @__PURE__ */ jsx4("path", { d: "M12 2v4" }),
1319
- /* @__PURE__ */ jsx4("rect", { x: "8.5", y: "6", width: "7", height: "3.5", rx: "1" }),
1320
- /* @__PURE__ */ jsx4("g", { className: "piloto-claw-l", children: /* @__PURE__ */ jsx4("path", { d: "M9.5 9.5 6 14l2 4.5" }) }),
1321
- /* @__PURE__ */ jsx4("g", { className: "piloto-claw-r", children: /* @__PURE__ */ jsx4("path", { d: "M14.5 9.5 18 14l-2 4.5" }) }),
1322
- /* @__PURE__ */ jsx4("path", { d: "M12 9.5v4" })
1350
+ return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", className: `piloto-claw ${className ?? ""}`, "aria-hidden": "true", children: /* @__PURE__ */ jsxs3("g", { className: "piloto-claw-rig", children: [
1351
+ /* @__PURE__ */ jsx5("path", { d: "M12 2v4" }),
1352
+ /* @__PURE__ */ jsx5("rect", { x: "8.5", y: "6", width: "7", height: "3.5", rx: "1" }),
1353
+ /* @__PURE__ */ jsx5("g", { className: "piloto-claw-l", children: /* @__PURE__ */ jsx5("path", { d: "M9.5 9.5 6 14l2 4.5" }) }),
1354
+ /* @__PURE__ */ jsx5("g", { className: "piloto-claw-r", children: /* @__PURE__ */ jsx5("path", { d: "M14.5 9.5 18 14l-2 4.5" }) }),
1355
+ /* @__PURE__ */ jsx5("path", { d: "M12 9.5v4" })
1323
1356
  ] }) });
1324
1357
  }
1325
1358
  var DEFAULT_TOOL_ICONS = {
@@ -1369,8 +1402,8 @@ function AgentSteps({ tools, live }) {
1369
1402
  const shown = groups.slice(-VISIBLE);
1370
1403
  const hidden = groups.length - shown.length;
1371
1404
  return /* @__PURE__ */ jsxs3("div", { className: "piloto-steps mb-3 rounded-(--piloto-radius-card) px-3 py-2.5 transition-[background-color,border-color] duration-300", "data-live": live || void 0, children: [
1372
- /* @__PURE__ */ jsx4("button", { type: "button", onClick: () => setOpen((v) => !v), "aria-expanded": open, title: open ? t.stepsCollapse : t.stepsExpand, className: "block w-full cursor-pointer text-left", children: open ? /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1.5 text-xs font-medium text-text-primary", children: [
1373
- /* @__PURE__ */ jsx4(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 rotate-90 text-text-tertiary" }),
1405
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setOpen((v) => !v), "aria-expanded": open, title: open ? t.stepsCollapse : t.stepsExpand, className: "block w-full cursor-pointer text-left", children: open ? /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1.5 text-xs font-medium text-text-primary", children: [
1406
+ /* @__PURE__ */ jsx5(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 rotate-90 text-text-tertiary" }),
1374
1407
  /* @__PURE__ */ jsxs3("span", { className: "truncate", children: [
1375
1408
  summarizeTools(tools, toolVerbs),
1376
1409
  failed > 0 && /* @__PURE__ */ jsxs3("span", { className: "text-text-danger", children: [
@@ -1379,10 +1412,10 @@ function AgentSteps({ tools, live }) {
1379
1412
  ] })
1380
1413
  ] })
1381
1414
  ] }) : /* @__PURE__ */ jsxs3("ul", { className: "flex flex-col gap-1.5", children: [
1382
- hidden > 0 && /* @__PURE__ */ jsx4("li", { className: "text-[11px] font-medium text-text-tertiary", children: t.earlierSteps(hidden) }),
1383
- shown.map((g) => /* @__PURE__ */ jsx4(StepLine, { tool: g.tool, times: g.times, live }, g.tool.id))
1415
+ hidden > 0 && /* @__PURE__ */ jsx5("li", { className: "text-[11px] font-medium text-text-tertiary", children: t.earlierSteps(hidden) }),
1416
+ shown.map((g) => /* @__PURE__ */ jsx5(StepLine, { tool: g.tool, times: g.times, live }, g.tool.id))
1384
1417
  ] }) }),
1385
- open && /* @__PURE__ */ jsx4("ul", { className: "mt-2 flex flex-col gap-1", children: tools.map((x) => /* @__PURE__ */ jsx4(ToolRow, { tool: x }, x.id)) })
1418
+ open && /* @__PURE__ */ jsx5("ul", { className: "mt-2 flex flex-col gap-1", children: tools.map((x) => /* @__PURE__ */ jsx5(ToolRow, { tool: x }, x.id)) })
1386
1419
  ] });
1387
1420
  }
1388
1421
  function StepLine({ tool, times, live }) {
@@ -1391,11 +1424,11 @@ function StepLine({ tool, times, live }) {
1391
1424
  const spins = isRunning(tool) && live;
1392
1425
  const label = stepLabel(tool, toolVerbs, t.step);
1393
1426
  return /* @__PURE__ */ jsxs3("li", { className: "piloto-step flex min-w-0 items-center gap-2 text-[13px] leading-6 text-text-secondary", children: [
1394
- /* @__PURE__ */ jsx4("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", "aria-label": spins ? t.running : tool.status === "failed" ? t.failed : t.done, children: spins ? /* @__PURE__ */ jsx4(LoaderCircle, { className: "h-4 w-4 animate-spin text-text-info" }) : tool.status === "failed" ? /* @__PURE__ */ jsx4(CircleX, { className: "h-4 w-4 text-text-danger" }) : /* @__PURE__ */ jsx4(CircleCheck, { className: "h-4 w-4 text-text-success" }) }),
1395
- /* @__PURE__ */ jsx4(Icon2, { className: "h-[22px] w-[22px] shrink-0 text-text-secondary" }),
1427
+ /* @__PURE__ */ jsx5("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", "aria-label": spins ? t.running : tool.status === "failed" ? t.failed : t.done, children: spins ? /* @__PURE__ */ jsx5(LoaderCircle, { className: "h-4 w-4 animate-spin text-text-info" }) : tool.status === "failed" ? /* @__PURE__ */ jsx5(CircleX, { className: "h-4 w-4 text-text-danger" }) : /* @__PURE__ */ jsx5(CircleCheck, { className: "h-4 w-4 text-text-success" }) }),
1428
+ /* @__PURE__ */ jsx5(Icon2, { className: "h-[22px] w-[22px] shrink-0 text-text-secondary" }),
1396
1429
  /* @__PURE__ */ jsxs3("span", { className: "min-w-0 truncate", children: [
1397
- /* @__PURE__ */ jsx4("span", { className: spins ? "text-text-primary" : void 0, children: label }),
1398
- tool.detail && /* @__PURE__ */ jsx4("span", { className: "piloto-step-detail", children: tool.detail }),
1430
+ /* @__PURE__ */ jsx5("span", { className: spins ? "text-text-primary" : void 0, children: label }),
1431
+ tool.detail && /* @__PURE__ */ jsx5("span", { className: "piloto-step-detail", children: tool.detail }),
1399
1432
  times > 1 && /* @__PURE__ */ jsxs3("span", { className: "ml-1 tabular-nums text-text-tertiary", children: [
1400
1433
  "\xD7",
1401
1434
  times
@@ -1414,30 +1447,30 @@ function ToolRow({ tool }) {
1414
1447
  const s = STATUS[tool.status ?? "pending"] ?? STATUS.pending;
1415
1448
  const KindIcon = iconFor(tool, toolIcons);
1416
1449
  return /* @__PURE__ */ jsxs3("li", { className: "piloto-tool-row flex items-start gap-2.5 rounded-(--piloto-radius-input) border border-border-secondary bg-background-secondary/50 px-3 py-2 transition-colors hover:border-border-primary hover:bg-background-secondary", children: [
1417
- /* @__PURE__ */ jsx4("span", { className: "mt-px flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-background-primary text-text-secondary shadow-sm", children: /* @__PURE__ */ jsx4(KindIcon, { className: "h-3.5 w-3.5" }) }),
1450
+ /* @__PURE__ */ jsx5("span", { className: "mt-px flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-background-primary text-text-secondary shadow-sm", children: /* @__PURE__ */ jsx5(KindIcon, { className: "h-3.5 w-3.5" }) }),
1418
1451
  /* @__PURE__ */ jsxs3("span", { className: "min-w-0 flex-1", children: [
1419
- /* @__PURE__ */ jsx4("span", { className: "block min-w-0 truncate text-xs font-medium text-text-primary", children: stepLabel(tool, toolVerbs, tool.id) }),
1420
- tool.detail && /* @__PURE__ */ jsx4("span", { className: "mt-0.5 block truncate text-[11px] text-text-tertiary", children: tool.detail }),
1421
- tool.path && /* @__PURE__ */ jsx4("span", { className: "mt-0.5 block truncate font-mono text-[10px] text-text-tertiary", children: tool.path })
1452
+ /* @__PURE__ */ jsx5("span", { className: "block min-w-0 truncate text-xs font-medium text-text-primary", children: stepLabel(tool, toolVerbs, tool.id) }),
1453
+ tool.detail && /* @__PURE__ */ jsx5("span", { className: "mt-0.5 block truncate text-[11px] text-text-tertiary", children: tool.detail }),
1454
+ tool.path && /* @__PURE__ */ jsx5("span", { className: "mt-0.5 block truncate font-mono text-[10px] text-text-tertiary", children: tool.path })
1422
1455
  ] }),
1423
- /* @__PURE__ */ jsx4("span", { className: "mt-0.5 shrink-0", title: t[s.key], "aria-label": t[s.key], children: /* @__PURE__ */ jsx4(s.Icon, { className: `h-3.5 w-3.5 ${s.className}` }) })
1456
+ /* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", title: t[s.key], "aria-label": t[s.key], children: /* @__PURE__ */ jsx5(s.Icon, { className: `h-3.5 w-3.5 ${s.className}` }) })
1424
1457
  ] });
1425
1458
  }
1426
1459
  function Narration({ steps, children }) {
1427
1460
  return /* @__PURE__ */ jsxs3("ul", { className: "piloto-narration mb-3 flex flex-col gap-1.5", children: [
1428
1461
  steps.map((p, i) => /* @__PURE__ */ jsxs3("li", { className: "piloto-fade-up flex items-start gap-2 text-text-secondary", children: [
1429
- /* @__PURE__ */ jsx4("span", { className: "mt-[3px] grid size-4 shrink-0 place-items-center rounded-full bg-brand-500/15 text-brand-600", children: /* @__PURE__ */ jsx4(CheckMini, {}) }),
1430
- /* @__PURE__ */ jsx4("span", { className: "min-w-0", children: p })
1462
+ /* @__PURE__ */ jsx5("span", { className: "mt-[3px] grid size-4 shrink-0 place-items-center rounded-full bg-brand-500/15 text-brand-600", children: /* @__PURE__ */ jsx5(CheckMini, {}) }),
1463
+ /* @__PURE__ */ jsx5("span", { className: "piloto-narration-text min-w-0", children: /* @__PURE__ */ jsx5(InlineMarkdown, { children: p }) })
1431
1464
  ] }, i)),
1432
1465
  children
1433
1466
  ] });
1434
1467
  }
1435
1468
  function CheckMini() {
1436
- return /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 24 24", width: "11", height: "11", fill: "none", stroke: "currentColor", strokeWidth: 3, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M20 6 9 17l-5-5" }) });
1469
+ return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 24 24", width: "11", height: "11", fill: "none", stroke: "currentColor", strokeWidth: 3, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("path", { d: "M20 6 9 17l-5-5" }) });
1437
1470
  }
1438
1471
 
1439
1472
  // src/react/Bubble.tsx
1440
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1473
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1441
1474
  async function copyText(text) {
1442
1475
  try {
1443
1476
  await navigator.clipboard.writeText(text);
@@ -1450,7 +1483,7 @@ function UserBubble({ turn }) {
1450
1483
  const { strings: t, formatUserText } = usePiloto();
1451
1484
  const note = systemNoteOf(turn.text);
1452
1485
  if (note) {
1453
- return /* @__PURE__ */ jsx5("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs4("span", { className: "piloto-system-note max-w-[80%] truncate rounded-full bg-background-secondary px-3 py-1 text-[11px] text-text-tertiary", children: [
1486
+ return /* @__PURE__ */ jsx6("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs4("span", { className: "piloto-system-note max-w-[80%] truncate rounded-full bg-background-secondary px-3 py-1 text-[11px] text-text-tertiary", children: [
1454
1487
  "\u23F0 ",
1455
1488
  note
1456
1489
  ] }) });
@@ -1464,23 +1497,23 @@ function UserBubble({ turn }) {
1464
1497
  "\u21AA ",
1465
1498
  t.steered
1466
1499
  ] }),
1467
- docs.length > 0 && /* @__PURE__ */ jsx5("div", { className: "mb-1 flex max-w-[75%] flex-wrap justify-end gap-2", children: docs.map((a, i) => /* @__PURE__ */ jsxs4("span", { className: "piloto-attachment-card inline-flex max-w-[16rem] items-center gap-1.5 rounded-(--piloto-radius-input) border border-border-primary bg-background-primary px-2.5 py-1.5 text-xs text-text-secondary", children: [
1468
- /* @__PURE__ */ jsx5(FileText, { className: "h-3.5 w-3.5 shrink-0" }),
1469
- a.uri ? /* @__PURE__ */ jsx5("a", { href: a.uri, target: "_blank", rel: "noopener noreferrer", className: "truncate hover:underline", children: a.name ?? a.mimeType }) : /* @__PURE__ */ jsx5("span", { className: "truncate", children: a.name ?? a.mimeType })
1500
+ docs.length > 0 && /* @__PURE__ */ jsx6("div", { className: "mb-1 flex max-w-[75%] flex-wrap justify-end gap-2", children: docs.map((a, i) => /* @__PURE__ */ jsxs4("span", { className: "piloto-attachment-card inline-flex max-w-[16rem] items-center gap-1.5 rounded-(--piloto-radius-input) border border-border-primary bg-background-primary px-2.5 py-1.5 text-xs text-text-secondary", children: [
1501
+ /* @__PURE__ */ jsx6(FileText, { className: "h-3.5 w-3.5 shrink-0" }),
1502
+ a.uri ? /* @__PURE__ */ jsx6("a", { href: a.uri, target: "_blank", rel: "noopener noreferrer", className: "truncate hover:underline", children: a.name ?? a.mimeType }) : /* @__PURE__ */ jsx6("span", { className: "truncate", children: a.name ?? a.mimeType })
1470
1503
  ] }, i)) }),
1471
1504
  (inBubble.length > 0 || visible.trim()) && /* @__PURE__ */ jsxs4("div", { className: "piloto-bubble-user max-w-[75%] whitespace-pre-wrap break-words rounded-(--piloto-radius-bubble) px-4 py-3 text-[15px] leading-[22px]", children: [
1472
- inBubble.length > 0 && /* @__PURE__ */ jsx5("div", { className: `flex flex-wrap justify-end gap-1.5 ${visible.trim() ? "mb-2" : ""}`, children: inBubble.map(
1473
- (a, i) => a.mimeType.startsWith("image/") ? /* @__PURE__ */ jsx5("img", { src: a.data ? `data:${a.mimeType};base64,${a.data}` : a.uri, alt: "", className: "h-20 w-20 rounded-lg object-cover" }, i) : /* @__PURE__ */ jsx5("audio", { src: a.uri, controls: true, preload: "metadata", className: "h-9 max-w-[16rem]" }, i)
1505
+ inBubble.length > 0 && /* @__PURE__ */ jsx6("div", { className: `flex flex-wrap justify-end gap-1.5 ${visible.trim() ? "mb-2" : ""}`, children: inBubble.map(
1506
+ (a, i) => a.mimeType.startsWith("image/") ? /* @__PURE__ */ jsx6("img", { src: a.data ? `data:${a.mimeType};base64,${a.data}` : a.uri, alt: "", className: "h-20 w-20 rounded-lg object-cover" }, i) : /* @__PURE__ */ jsx6("audio", { src: a.uri, controls: true, preload: "metadata", className: "h-9 max-w-[16rem]" }, i)
1474
1507
  ) }),
1475
1508
  visible
1476
1509
  ] }),
1477
- visible.trim() && /* @__PURE__ */ jsx5(CopyUserMessage, { text: visible })
1510
+ visible.trim() && /* @__PURE__ */ jsx6(CopyUserMessage, { text: visible })
1478
1511
  ] });
1479
1512
  }
1480
1513
  function CopyUserMessage({ text }) {
1481
1514
  const { strings: t } = usePiloto();
1482
1515
  const [copied, setCopied] = useState5(false);
1483
- return /* @__PURE__ */ jsx5(
1516
+ return /* @__PURE__ */ jsx6(
1484
1517
  "button",
1485
1518
  {
1486
1519
  type: "button",
@@ -1493,20 +1526,20 @@ function CopyUserMessage({ text }) {
1493
1526
  "aria-label": copied ? t.copied : t.copyMessage,
1494
1527
  title: copied ? t.copied : t.copy,
1495
1528
  className: `piloto-copy-user grid h-7 w-7 place-items-center rounded-md text-text-tertiary transition-opacity hover:bg-background-secondary hover:text-text-primary focus-visible:opacity-100 [@media(hover:none)]:opacity-100 ${copied ? "opacity-100" : "opacity-0 group-hover/msg:opacity-100"}`,
1496
- children: copied ? /* @__PURE__ */ jsx5(Check, { className: "h-3.5 w-3.5 text-text-success" }) : /* @__PURE__ */ jsx5(Copy, { className: "h-3.5 w-3.5" })
1529
+ children: copied ? /* @__PURE__ */ jsx6(Check, { className: "h-3.5 w-3.5 text-text-success" }) : /* @__PURE__ */ jsx6(Copy, { className: "h-3.5 w-3.5" })
1497
1530
  }
1498
1531
  );
1499
1532
  }
1500
1533
  function AnswerText({ text, live, whole = false }) {
1501
1534
  const { visible, animate } = useTextAtPace(text, live, whole);
1502
1535
  if (!visible) return null;
1503
- return /* @__PURE__ */ jsx5(Markdown, { animate, children: visible });
1536
+ return /* @__PURE__ */ jsx6(Markdown, { animate, children: visible });
1504
1537
  }
1505
1538
  function ReplyOptionButtons({ options, onPick }) {
1506
1539
  const { strings: t } = usePiloto();
1507
- return /* @__PURE__ */ jsx5("ul", { className: "piloto-options mt-3 flex flex-col gap-2", "aria-label": t.replyOptions, children: options.items.map((item, i) => {
1540
+ return /* @__PURE__ */ jsx6("ul", { className: "piloto-options mt-3 flex flex-col gap-2", "aria-label": t.replyOptions, children: options.items.map((item, i) => {
1508
1541
  const clean = cleanReplyOption(item);
1509
- return /* @__PURE__ */ jsx5("li", { className: "piloto-chunk-in", children: /* @__PURE__ */ jsxs4(
1542
+ return /* @__PURE__ */ jsx6("li", { className: "piloto-chunk-in", children: /* @__PURE__ */ jsxs4(
1510
1543
  "button",
1511
1544
  {
1512
1545
  type: "button",
@@ -1517,8 +1550,8 @@ function ReplyOptionButtons({ options, onPick }) {
1517
1550
  i + 1,
1518
1551
  "."
1519
1552
  ] }),
1520
- /* @__PURE__ */ jsx5("span", { className: "min-w-0 flex-1", children: clean }),
1521
- /* @__PURE__ */ jsx5(ArrowUpRight, { className: "h-4 w-4 shrink-0 text-text-tertiary transition-colors group-hover:text-text-primary" })
1553
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1", children: clean }),
1554
+ /* @__PURE__ */ jsx6(ArrowUpRight, { className: "h-4 w-4 shrink-0 text-text-tertiary transition-colors group-hover:text-text-primary" })
1522
1555
  ]
1523
1556
  }
1524
1557
  ) }, i);
@@ -1530,12 +1563,12 @@ function UsageStats({ used, size, cost }) {
1530
1563
  const pct = size > 0 ? Math.min(100, Math.round(used / size * 100)) : 0;
1531
1564
  return /* @__PURE__ */ jsxs4("div", { className: "piloto-usage mt-2 flex items-center gap-4 text-[11px] text-text-tertiary", children: [
1532
1565
  /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-1", children: [
1533
- /* @__PURE__ */ jsx5(Gauge, { className: "h-3 w-3" }),
1566
+ /* @__PURE__ */ jsx6(Gauge, { className: "h-3 w-3" }),
1534
1567
  used.toLocaleString(t.numberLocale),
1535
1568
  size > 0 && ` / ${size.toLocaleString(t.numberLocale)} (${pct}%)`
1536
1569
  ] }),
1537
1570
  cost > 0 && /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-1", children: [
1538
- /* @__PURE__ */ jsx5(Coins, { className: "h-3 w-3" }),
1571
+ /* @__PURE__ */ jsx6(Coins, { className: "h-3 w-3" }),
1539
1572
  cost.toLocaleString(t.numberLocale, { style: "currency", currency: "USD", maximumFractionDigits: 4 })
1540
1573
  ] })
1541
1574
  ] });
@@ -1544,7 +1577,7 @@ function TurnFooter({ text, seconds, onRetry, showCopy = true }) {
1544
1577
  const { strings: t } = usePiloto();
1545
1578
  const [copied, setCopied] = useState5(false);
1546
1579
  return /* @__PURE__ */ jsxs4("div", { className: "piloto-footer mt-2 flex items-center gap-3 text-xs text-text-tertiary", children: [
1547
- seconds ? /* @__PURE__ */ jsx5("span", { children: t.worked(seconds) }) : null,
1580
+ seconds ? /* @__PURE__ */ jsx6("span", { children: t.worked(seconds) }) : null,
1548
1581
  showCopy && /* @__PURE__ */ jsxs4(
1549
1582
  "button",
1550
1583
  {
@@ -1557,13 +1590,13 @@ function TurnFooter({ text, seconds, onRetry, showCopy = true }) {
1557
1590
  },
1558
1591
  className: "flex items-center gap-1 transition-colors hover:text-text-primary",
1559
1592
  children: [
1560
- copied ? /* @__PURE__ */ jsx5(Check, { className: "h-3.5 w-3.5 text-text-success" }) : /* @__PURE__ */ jsx5(Copy, { className: "h-3.5 w-3.5" }),
1593
+ copied ? /* @__PURE__ */ jsx6(Check, { className: "h-3.5 w-3.5 text-text-success" }) : /* @__PURE__ */ jsx6(Copy, { className: "h-3.5 w-3.5" }),
1561
1594
  copied ? t.copied : t.copy
1562
1595
  ]
1563
1596
  }
1564
1597
  ),
1565
1598
  onRetry && /* @__PURE__ */ jsxs4("button", { type: "button", onClick: onRetry, className: "flex items-center gap-1 transition-colors hover:text-text-primary", children: [
1566
- /* @__PURE__ */ jsx5(RotateCcw, { className: "h-3.5 w-3.5" }),
1599
+ /* @__PURE__ */ jsx6(RotateCcw, { className: "h-3.5 w-3.5" }),
1567
1600
  t.retry
1568
1601
  ] })
1569
1602
  ] });
@@ -1571,9 +1604,9 @@ function TurnFooter({ text, seconds, onRetry, showCopy = true }) {
1571
1604
  function DeliveryChips({ deliveries }) {
1572
1605
  const shown = deliveries.filter((d) => !d.capture);
1573
1606
  if (!shown.length) return null;
1574
- return /* @__PURE__ */ jsx5("div", { className: "mt-2 flex flex-wrap gap-1.5", children: shown.map((d, i) => /* @__PURE__ */ jsxs4("span", { className: "piloto-delivery inline-flex max-w-[16rem] items-center gap-1.5 rounded-(--piloto-radius-input) border border-border-primary px-2 py-1 text-xs text-text-secondary", children: [
1575
- /* @__PURE__ */ jsx5(FileText, { className: "h-3.5 w-3.5 shrink-0" }),
1576
- /* @__PURE__ */ jsx5("span", { className: "truncate", children: d.name })
1607
+ return /* @__PURE__ */ jsx6("div", { className: "mt-2 flex flex-wrap gap-1.5", children: shown.map((d, i) => /* @__PURE__ */ jsxs4("span", { className: "piloto-delivery inline-flex max-w-[16rem] items-center gap-1.5 rounded-(--piloto-radius-input) border border-border-primary px-2 py-1 text-xs text-text-secondary", children: [
1608
+ /* @__PURE__ */ jsx6(FileText, { className: "h-3.5 w-3.5 shrink-0" }),
1609
+ /* @__PURE__ */ jsx6("span", { className: "truncate", children: d.name })
1577
1610
  ] }, i)) });
1578
1611
  }
1579
1612
  function AgentBubble({ turn, live, onRetry, onOption, showUsage = true, showCopy = true, renderDeliveries }) {
@@ -1583,28 +1616,28 @@ function AgentBubble({ turn, live, onRetry, onOption, showUsage = true, showCopy
1583
1616
  const options = onOption && !live && visible ? extractReplyOptions(visible) : null;
1584
1617
  return /* @__PURE__ */ jsxs4("div", { className: "piloto-agent min-w-0 max-w-[92%]", children: [
1585
1618
  turn.thought && /* @__PURE__ */ jsxs4("details", { className: "piloto-thought mb-3 text-xs text-text-secondary", children: [
1586
- /* @__PURE__ */ jsx5("summary", { className: "cursor-pointer select-none", children: t.thinking }),
1587
- /* @__PURE__ */ jsx5("p", { className: "mt-2 whitespace-pre-wrap border-l-2 border-border-secondary pl-3", children: turn.thought })
1619
+ /* @__PURE__ */ jsx6("summary", { className: "cursor-pointer select-none", children: t.thinking }),
1620
+ /* @__PURE__ */ jsx6("p", { className: "mt-2 whitespace-pre-wrap border-l-2 border-border-secondary pl-3", children: turn.thought })
1588
1621
  ] }),
1589
- turn.tools && turn.tools.length > 0 && /* @__PURE__ */ jsx5(AgentSteps, { tools: turn.tools, live }),
1590
- narrated.steps.length > 0 && /* @__PURE__ */ jsx5(Narration, { steps: narrated.steps }),
1591
- options ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1592
- options.before && /* @__PURE__ */ jsx5(AnswerText, { text: options.before, live }),
1593
- /* @__PURE__ */ jsx5(ReplyOptionButtons, { options, onPick: onOption })
1622
+ turn.tools && turn.tools.length > 0 && /* @__PURE__ */ jsx6(AgentSteps, { tools: turn.tools, live }),
1623
+ narrated.steps.length > 0 && /* @__PURE__ */ jsx6(Narration, { steps: narrated.steps }),
1624
+ options ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1625
+ options.before && /* @__PURE__ */ jsx6(AnswerText, { text: options.before, live }),
1626
+ /* @__PURE__ */ jsx6(ReplyOptionButtons, { options, onPick: onOption })
1594
1627
  ] }) : (
1595
1628
  // Cada tramo nuevo tras una herramienta monta su propio ritmo (empieza de cero). Lo que llegó
1596
1629
  // entero fuera de un turno se escribe al mismo ritmo; al recargar ya sale completo.
1597
- visible && /* @__PURE__ */ jsx5(AnswerText, { text: visible, live: live || !!turn.arrived, whole: !!turn.arrived }, `r${narrated.steps.length}`)
1630
+ visible && /* @__PURE__ */ jsx6(AnswerText, { text: visible, live: live || !!turn.arrived, whole: !!turn.arrived }, `r${narrated.steps.length}`)
1598
1631
  ),
1599
- renderDeliveries ? renderDeliveries(turn, live) : turn.deliveries?.length ? /* @__PURE__ */ jsx5(DeliveryChips, { deliveries: turn.deliveries }) : null,
1600
- turn.decisions?.length ? /* @__PURE__ */ jsx5(DecisionLines, { decisions: turn.decisions }) : null,
1601
- showUsage && turn.usage && /* @__PURE__ */ jsx5(UsageStats, { ...turn.usage }),
1602
- turn.text && !live && /* @__PURE__ */ jsx5(TurnFooter, { text: turn.text, seconds: turn.seconds, onRetry, showCopy })
1632
+ renderDeliveries ? renderDeliveries(turn, live) : turn.deliveries?.length ? /* @__PURE__ */ jsx6(DeliveryChips, { deliveries: turn.deliveries }) : null,
1633
+ turn.decisions?.length ? /* @__PURE__ */ jsx6(DecisionLines, { decisions: turn.decisions }) : null,
1634
+ showUsage && turn.usage && /* @__PURE__ */ jsx6(UsageStats, { ...turn.usage }),
1635
+ turn.text && !live && /* @__PURE__ */ jsx6(TurnFooter, { text: turn.text, seconds: turn.seconds, onRetry, showCopy })
1603
1636
  ] });
1604
1637
  }
1605
1638
 
1606
1639
  // src/react/Chips.tsx
1607
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1640
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1608
1641
  function ReferenceChips({
1609
1642
  references,
1610
1643
  onRemove,
@@ -1612,29 +1645,29 @@ function ReferenceChips({
1612
1645
  }) {
1613
1646
  const { strings: t } = usePiloto();
1614
1647
  if (!references.length) return null;
1615
- return /* @__PURE__ */ jsx6("div", { className: "piloto-references flex flex-wrap gap-1.5", children: references.map((r) => /* @__PURE__ */ jsxs5("span", { className: "piloto-reference inline-flex max-w-full items-center gap-1.5 rounded-full border py-1 pl-2.5 pr-1 text-xs text-text-primary", children: [
1616
- /* @__PURE__ */ jsx6(AtSign, { className: "h-3 w-3 shrink-0 text-brand-600" }),
1617
- /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => onActivate?.(r), className: "shrink-0 font-semibold underline-offset-2 hover:underline", children: r.label }),
1648
+ return /* @__PURE__ */ jsx7("div", { className: "piloto-references flex flex-wrap gap-1.5", children: references.map((r) => /* @__PURE__ */ jsxs5("span", { className: "piloto-reference inline-flex max-w-full items-center gap-1.5 rounded-full border py-1 pl-2.5 pr-1 text-xs text-text-primary", children: [
1649
+ /* @__PURE__ */ jsx7(AtSign, { className: "h-3 w-3 shrink-0 text-brand-600" }),
1650
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onActivate?.(r), className: "shrink-0 font-semibold underline-offset-2 hover:underline", children: r.label }),
1618
1651
  r.hint && /* @__PURE__ */ jsxs5("span", { className: "truncate text-text-secondary", children: [
1619
1652
  "\xAB",
1620
1653
  r.hint,
1621
1654
  "\xBB"
1622
1655
  ] }),
1623
- /* @__PURE__ */ jsx6(
1656
+ /* @__PURE__ */ jsx7(
1624
1657
  "button",
1625
1658
  {
1626
1659
  type: "button",
1627
1660
  onClick: () => onRemove(r.id),
1628
1661
  "aria-label": t.removeReference,
1629
1662
  className: "grid size-5 shrink-0 cursor-pointer place-items-center rounded-full text-text-tertiary hover:bg-background-secondary hover:text-text-primary",
1630
- children: /* @__PURE__ */ jsx6(X, { className: "h-3 w-3" })
1663
+ children: /* @__PURE__ */ jsx7(X, { className: "h-3 w-3" })
1631
1664
  }
1632
1665
  )
1633
1666
  ] }, r.id)) });
1634
1667
  }
1635
1668
  function Suggestions({ items, onPick, onPrefill }) {
1636
1669
  if (!items.length) return null;
1637
- return /* @__PURE__ */ jsx6("div", { className: "piloto-suggestions mt-3 flex flex-wrap justify-center gap-2", children: items.map((s, i) => /* @__PURE__ */ jsx6(
1670
+ return /* @__PURE__ */ jsx7("div", { className: "piloto-suggestions mt-3 flex flex-wrap justify-center gap-2", children: items.map((s, i) => /* @__PURE__ */ jsx7(
1638
1671
  "button",
1639
1672
  {
1640
1673
  type: "button",
@@ -1648,7 +1681,7 @@ function Suggestions({ items, onPick, onPrefill }) {
1648
1681
 
1649
1682
  // src/react/Composer.tsx
1650
1683
  import { useCallback, useEffect as useEffect3, useRef as useRef3, useState as useState6 } from "react";
1651
- import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1684
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1652
1685
  var MAX_HEIGHT = 240;
1653
1686
  var BY_EXTENSION = {
1654
1687
  png: "image/png",
@@ -1940,13 +1973,13 @@ function Composer({
1940
1973
  };
1941
1974
  const canSend = Boolean(value.trim() || files.length > 0 || references.length > 0);
1942
1975
  const iconButton = "flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-background-secondary hover:text-text-primary disabled:opacity-30";
1943
- return /* @__PURE__ */ jsx7("div", { className: "piloto-composer rounded-(--piloto-radius-input)", children: /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-2 p-3", children: [
1944
- withFiles && dragging && /* @__PURE__ */ jsx7("div", { className: "piloto-drop pointer-events-none fixed inset-0 z-[1000] flex items-center justify-center p-6", children: /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-center gap-2 rounded-(--piloto-radius-card) border-2 border-dashed border-brand-500 bg-background-primary px-10 py-8 text-center shadow-(--piloto-shadow-card)", children: [
1945
- /* @__PURE__ */ jsx7(Paperclip, { className: "size-7 text-brand-600" }),
1946
- /* @__PURE__ */ jsx7("p", { className: "text-base font-semibold text-text-primary", children: t.dropFiles }),
1947
- /* @__PURE__ */ jsx7("p", { className: "text-xs text-text-secondary", children: t.dropHint })
1976
+ return /* @__PURE__ */ jsx8("div", { className: "piloto-composer rounded-(--piloto-radius-input)", children: /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-2 p-3", children: [
1977
+ withFiles && dragging && /* @__PURE__ */ jsx8("div", { className: "piloto-drop pointer-events-none fixed inset-0 z-[1000] flex items-center justify-center p-6", children: /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-center gap-2 rounded-(--piloto-radius-card) border-2 border-dashed border-brand-500 bg-background-primary px-10 py-8 text-center shadow-(--piloto-shadow-card)", children: [
1978
+ /* @__PURE__ */ jsx8(Paperclip, { className: "size-7 text-brand-600" }),
1979
+ /* @__PURE__ */ jsx8("p", { className: "text-base font-semibold text-text-primary", children: t.dropFiles }),
1980
+ /* @__PURE__ */ jsx8("p", { className: "text-xs text-text-secondary", children: t.dropHint })
1948
1981
  ] }) }),
1949
- /* @__PURE__ */ jsx7(
1982
+ /* @__PURE__ */ jsx8(
1950
1983
  "input",
1951
1984
  {
1952
1985
  ref: fileRef,
@@ -1959,36 +1992,36 @@ function Composer({
1959
1992
  }
1960
1993
  }
1961
1994
  ),
1962
- notice && /* @__PURE__ */ jsx7("p", { className: "text-xs text-text-danger", children: notice }),
1995
+ notice && /* @__PURE__ */ jsx8("p", { className: "text-xs text-text-danger", children: notice }),
1963
1996
  uploading && /* @__PURE__ */ jsxs6("p", { className: "flex items-center gap-1.5 text-xs text-text-secondary", children: [
1964
- /* @__PURE__ */ jsx7(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }),
1997
+ /* @__PURE__ */ jsx8(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }),
1965
1998
  files.length > 1 ? t.uploadingN(files.length) : t.uploading
1966
1999
  ] }),
1967
- files.length > 0 && /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2", children: files.map((a) => /* @__PURE__ */ jsxs6("div", { className: "relative h-16 w-16 overflow-hidden rounded-lg border border-border-primary bg-background-secondary", children: [
1968
- mimeOf(a.file).startsWith("image/") ? /* @__PURE__ */ jsx7("img", { src: a.url, alt: a.file.name, className: "h-full w-full object-cover" }) : (
2000
+ files.length > 0 && /* @__PURE__ */ jsx8("div", { className: "flex flex-wrap gap-2", children: files.map((a) => /* @__PURE__ */ jsxs6("div", { className: "relative h-16 w-16 overflow-hidden rounded-lg border border-border-primary bg-background-secondary", children: [
2001
+ mimeOf(a.file).startsWith("image/") ? /* @__PURE__ */ jsx8("img", { src: a.url, alt: a.file.name, className: "h-full w-full object-cover" }) : (
1969
2002
  // Un PDF pintado con <img> es un recuadro roto: nombre, extensión y peso.
1970
2003
  /* @__PURE__ */ jsxs6("span", { className: "flex h-full w-full flex-col items-center justify-center gap-1 px-1 text-center", title: a.file.name, children: [
1971
- /* @__PURE__ */ jsx7(FileText, { className: "h-4 w-4 shrink-0 text-text-tertiary" }),
1972
- /* @__PURE__ */ jsx7("span", { className: "w-full truncate text-[9px] font-medium uppercase leading-none text-text-secondary", children: a.file.name.match(/\.([A-Za-z0-9]+)$/)?.[1] ?? "file" }),
1973
- /* @__PURE__ */ jsx7("span", { className: "w-full truncate text-[9px] leading-none text-text-tertiary", children: shortSize(a.file.size) })
2004
+ /* @__PURE__ */ jsx8(FileText, { className: "h-4 w-4 shrink-0 text-text-tertiary" }),
2005
+ /* @__PURE__ */ jsx8("span", { className: "w-full truncate text-[9px] font-medium uppercase leading-none text-text-secondary", children: a.file.name.match(/\.([A-Za-z0-9]+)$/)?.[1] ?? "file" }),
2006
+ /* @__PURE__ */ jsx8("span", { className: "w-full truncate text-[9px] leading-none text-text-tertiary", children: shortSize(a.file.size) })
1974
2007
  ] })
1975
2008
  ),
1976
- /* @__PURE__ */ jsx7(
2009
+ /* @__PURE__ */ jsx8(
1977
2010
  "button",
1978
2011
  {
1979
2012
  type: "button",
1980
2013
  onClick: () => removeFile(a.id),
1981
2014
  "aria-label": t.removeAttachment,
1982
2015
  className: "absolute right-0.5 top-0.5 flex h-5 w-5 items-center justify-center rounded-full bg-background-inverse/80 text-text-inverse shadow-sm transition-colors hover:bg-background-inverse",
1983
- children: /* @__PURE__ */ jsx7(X, { className: "h-3 w-3" })
2016
+ children: /* @__PURE__ */ jsx8(X, { className: "h-3 w-3" })
1984
2017
  }
1985
2018
  )
1986
2019
  ] }, a.id)) }),
1987
- /* @__PURE__ */ jsx7(ReferenceChips, { references, onRemove: (id) => onRemoveReference?.(id), onActivate: onActivateReference }),
2020
+ /* @__PURE__ */ jsx8(ReferenceChips, { references, onRemove: (id) => onRemoveReference?.(id), onActivate: onActivateReference }),
1988
2021
  recording ? (
1989
2022
  // Grabando: la barra sustituye al campo de texto — punto rojo, reloj, la onda y las dos salidas.
1990
2023
  /* @__PURE__ */ jsxs6("div", { className: "flex min-h-[24px] items-center gap-3 px-1", children: [
1991
- /* @__PURE__ */ jsx7(
2024
+ /* @__PURE__ */ jsx8(
1992
2025
  "button",
1993
2026
  {
1994
2027
  type: "button",
@@ -1998,19 +2031,19 @@ function Composer({
1998
2031
  },
1999
2032
  "aria-label": t.cancelVoice,
2000
2033
  className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-text-secondary hover:bg-background-secondary hover:text-text-primary",
2001
- children: /* @__PURE__ */ jsx7(X, { className: "h-4 w-4" })
2034
+ children: /* @__PURE__ */ jsx8(X, { className: "h-4 w-4" })
2002
2035
  }
2003
2036
  ),
2004
- /* @__PURE__ */ jsx7("span", { className: "piloto-rec-dot h-2 w-2 shrink-0 rounded-full" }),
2037
+ /* @__PURE__ */ jsx8("span", { className: "piloto-rec-dot h-2 w-2 shrink-0 rounded-full" }),
2005
2038
  /* @__PURE__ */ jsxs6("span", { className: "w-10 shrink-0 font-mono text-xs tabular-nums text-text-secondary", children: [
2006
2039
  Math.floor(seconds / 60),
2007
2040
  ":",
2008
2041
  String(seconds % 60).padStart(2, "0")
2009
2042
  ] }),
2010
- /* @__PURE__ */ jsx7("div", { className: "flex h-6 min-w-0 flex-1 items-center gap-[2px] overflow-hidden", children: Array.from({ length: 60 }, (_, i) => wave[wave.length - 60 + i] ?? 0).map((v, i) => /* @__PURE__ */ jsx7("span", { className: "w-[3px] shrink-0 rounded-full bg-text-secondary/70", style: { height: `${Math.max(3, Math.round(v * 22))}px` } }, i)) }),
2011
- /* @__PURE__ */ jsx7("button", { type: "button", onClick: toggleRecording, "aria-label": t.stopAndSendVoice, className: "piloto-send flex h-8 w-8 shrink-0 items-center justify-center rounded-full", children: /* @__PURE__ */ jsx7(ArrowUp, { className: "h-4 w-4" }) })
2043
+ /* @__PURE__ */ jsx8("div", { className: "flex h-6 min-w-0 flex-1 items-center gap-[2px] overflow-hidden", children: Array.from({ length: 60 }, (_, i) => wave[wave.length - 60 + i] ?? 0).map((v, i) => /* @__PURE__ */ jsx8("span", { className: "w-[3px] shrink-0 rounded-full bg-text-secondary/70", style: { height: `${Math.max(3, Math.round(v * 22))}px` } }, i)) }),
2044
+ /* @__PURE__ */ jsx8("button", { type: "button", onClick: toggleRecording, "aria-label": t.stopAndSendVoice, className: "piloto-send flex h-8 w-8 shrink-0 items-center justify-center rounded-full", children: /* @__PURE__ */ jsx8(ArrowUp, { className: "h-4 w-4" }) })
2012
2045
  ] })
2013
- ) : /* @__PURE__ */ jsx7(
2046
+ ) : /* @__PURE__ */ jsx8(
2014
2047
  "textarea",
2015
2048
  {
2016
2049
  ref,
@@ -2032,8 +2065,8 @@ function Composer({
2032
2065
  ),
2033
2066
  /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-2", children: [
2034
2067
  /* @__PURE__ */ jsxs6("div", { className: "flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden", children: [
2035
- withFiles && /* @__PURE__ */ jsxs6(Fragment3, { children: [
2036
- /* @__PURE__ */ jsx7(
2068
+ withFiles && /* @__PURE__ */ jsxs6(Fragment4, { children: [
2069
+ /* @__PURE__ */ jsx8(
2037
2070
  "button",
2038
2071
  {
2039
2072
  type: "button",
@@ -2046,10 +2079,10 @@ function Composer({
2046
2079
  "aria-label": canSeeImages ? t.attachImage : t.noImages,
2047
2080
  title: canSeeImages ? t.attachImage : t.noImages,
2048
2081
  className: iconButton,
2049
- children: /* @__PURE__ */ jsx7(Image, { className: "h-4 w-4" })
2082
+ children: /* @__PURE__ */ jsx8(Image, { className: "h-4 w-4" })
2050
2083
  }
2051
2084
  ),
2052
- /* @__PURE__ */ jsx7(
2085
+ /* @__PURE__ */ jsx8(
2053
2086
  "button",
2054
2087
  {
2055
2088
  type: "button",
@@ -2062,16 +2095,16 @@ function Composer({
2062
2095
  "aria-label": t.attachFile,
2063
2096
  title: t.attachFile,
2064
2097
  className: iconButton,
2065
- children: /* @__PURE__ */ jsx7(Paperclip, { className: "h-4 w-4" })
2098
+ children: /* @__PURE__ */ jsx8(Paperclip, { className: "h-4 w-4" })
2066
2099
  }
2067
2100
  )
2068
2101
  ] }),
2069
2102
  extras,
2070
- models.length > 0 && onModelChange && /* @__PURE__ */ jsx7(ModelPicker, { models, current: currentModel, onChange: onModelChange })
2103
+ models.length > 0 && onModelChange && /* @__PURE__ */ jsx8(ModelPicker, { models, current: currentModel, onChange: onModelChange })
2071
2104
  ] }),
2072
2105
  /* @__PURE__ */ jsxs6("div", { className: "flex shrink-0 items-center gap-1.5", children: [
2073
- canRecord && !recording && /* @__PURE__ */ jsx7("button", { type: "button", onClick: toggleRecording, disabled: busy || transcribing, "aria-label": t.recordVoice, title: t.voiceNote, className: `${iconButton} h-8 w-8`, children: transcribing ? /* @__PURE__ */ jsx7(LoaderCircle, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx7(Mic, { className: "h-4 w-4" }) }),
2074
- /* @__PURE__ */ jsx7(
2106
+ canRecord && !recording && /* @__PURE__ */ jsx8("button", { type: "button", onClick: toggleRecording, disabled: busy || transcribing, "aria-label": t.recordVoice, title: t.voiceNote, className: `${iconButton} h-8 w-8`, children: transcribing ? /* @__PURE__ */ jsx8(LoaderCircle, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx8(Mic, { className: "h-4 w-4" }) }),
2107
+ /* @__PURE__ */ jsx8(
2075
2108
  "button",
2076
2109
  {
2077
2110
  type: "button",
@@ -2080,7 +2113,7 @@ function Composer({
2080
2113
  "aria-label": busy ? t.stop : uploading ? t.uploading : t.send,
2081
2114
  "data-ready": busy || canSend || uploading || void 0,
2082
2115
  className: "piloto-send flex h-8 w-8 items-center justify-center rounded-full transition-colors",
2083
- children: busy ? /* @__PURE__ */ jsx7(Square, { className: "h-3 w-3 fill-current" }) : uploading ? /* @__PURE__ */ jsx7(LoaderCircle, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx7(ArrowUp, { className: "h-4 w-4" })
2116
+ children: busy ? /* @__PURE__ */ jsx8(Square, { className: "h-3 w-3 fill-current" }) : uploading ? /* @__PURE__ */ jsx8(LoaderCircle, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx8(ArrowUp, { className: "h-4 w-4" })
2084
2117
  }
2085
2118
  )
2086
2119
  ] })
@@ -2115,13 +2148,13 @@ function ModelPicker({ models, current, onChange }) {
2115
2148
  onClick: () => setOpen((v) => !v),
2116
2149
  className: "flex max-w-56 items-center gap-1.5 rounded-full border border-border-primary px-2.5 py-1 text-xs text-text-secondary transition-colors hover:bg-background-secondary hover:text-text-primary",
2117
2150
  children: [
2118
- /* @__PURE__ */ jsx7(Cpu, { className: "h-3.5 w-3.5 shrink-0" }),
2119
- /* @__PURE__ */ jsx7("span", { className: "truncate", children: active?.name ?? t.model }),
2120
- /* @__PURE__ */ jsx7(ChevronDown, { className: "h-3.5 w-3.5 shrink-0 opacity-70" })
2151
+ /* @__PURE__ */ jsx8(Cpu, { className: "h-3.5 w-3.5 shrink-0" }),
2152
+ /* @__PURE__ */ jsx8("span", { className: "truncate", children: active?.name ?? t.model }),
2153
+ /* @__PURE__ */ jsx8(ChevronDown, { className: "h-3.5 w-3.5 shrink-0 opacity-70" })
2121
2154
  ]
2122
2155
  }
2123
2156
  ),
2124
- open && /* @__PURE__ */ jsx7("div", { className: "piloto-menu absolute bottom-full left-0 z-20 mb-1 max-h-80 min-w-52 overflow-y-auto rounded-(--piloto-radius-input) border border-border-primary bg-background-primary py-1 shadow-(--piloto-shadow-card)", children: models.map((m) => /* @__PURE__ */ jsxs6(
2157
+ open && /* @__PURE__ */ jsx8("div", { className: "piloto-menu absolute bottom-full left-0 z-20 mb-1 max-h-80 min-w-52 overflow-y-auto rounded-(--piloto-radius-input) border border-border-primary bg-background-primary py-1 shadow-(--piloto-shadow-card)", children: models.map((m) => /* @__PURE__ */ jsxs6(
2125
2158
  "button",
2126
2159
  {
2127
2160
  type: "button",
@@ -2131,8 +2164,8 @@ function ModelPicker({ models, current, onChange }) {
2131
2164
  },
2132
2165
  className: "flex w-full items-center justify-between gap-3 px-3 py-1.5 text-left text-xs text-text-secondary transition-colors hover:bg-background-secondary hover:text-text-primary",
2133
2166
  children: [
2134
- /* @__PURE__ */ jsx7("span", { className: "truncate", children: m.name }),
2135
- m.value === current && /* @__PURE__ */ jsx7(Check, { className: "h-3.5 w-3.5 shrink-0 text-text-success" })
2167
+ /* @__PURE__ */ jsx8("span", { className: "truncate", children: m.name }),
2168
+ m.value === current && /* @__PURE__ */ jsx8(Check, { className: "h-3.5 w-3.5 shrink-0 text-text-success" })
2136
2169
  ]
2137
2170
  },
2138
2171
  m.value
@@ -2143,11 +2176,11 @@ function ModelPicker({ models, current, onChange }) {
2143
2176
  // src/react/Status.tsx
2144
2177
  import { AnimatePresence, motion } from "motion/react";
2145
2178
  import { useEffect as useEffect4, useState as useState7 } from "react";
2146
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2179
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2147
2180
  var WAKING_GIVEUP_S = 45;
2148
2181
  var STILL_WORKING_S = 90;
2149
2182
  function TypingDots() {
2150
- return /* @__PURE__ */ jsx8("div", { className: "piloto-typing", "aria-hidden": true, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx8("span", { className: "piloto-typing-dot" }, i)) });
2183
+ return /* @__PURE__ */ jsx9("div", { className: "piloto-typing", "aria-hidden": true, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx9("span", { className: "piloto-typing-dot" }, i)) });
2151
2184
  }
2152
2185
  function WaitLabel({
2153
2186
  phase,
@@ -2170,15 +2203,15 @@ function WaitLabel({
2170
2203
  elsewhere || seconds >= STILL_WORKING_S ? t.stillWorking : phrases[Math.floor(seconds / 4) % phrases.length]
2171
2204
  ) : elsewhere ? t.stillWorking : t.howItGoes(seconds);
2172
2205
  const lost = phase === "waking" && seconds >= WAKING_GIVEUP_S;
2173
- return /* @__PURE__ */ jsx8("div", { className: "piloto-wait flex flex-col gap-0.5 text-sm text-text-secondary", children: lost ? /* @__PURE__ */ jsxs7("p", { className: "text-text-primary", children: [
2206
+ return /* @__PURE__ */ jsx9("div", { className: "piloto-wait flex flex-col gap-0.5 text-sm text-text-secondary", children: lost ? /* @__PURE__ */ jsxs7("p", { className: "text-text-primary", children: [
2174
2207
  t.cantStart,
2175
2208
  " ",
2176
- /* @__PURE__ */ jsx8("button", { type: "button", onClick: onLost, className: "cursor-pointer underline underline-offset-2", children: t.retry })
2209
+ /* @__PURE__ */ jsx9("button", { type: "button", onClick: onLost, className: "cursor-pointer underline underline-offset-2", children: t.retry })
2177
2210
  ] }) : (
2178
2211
  // La frase cambia con fundido (sale y entra): de golpe se leía brusco.
2179
2212
  /* @__PURE__ */ jsxs7("p", { className: "flex items-center", children: [
2180
- /* @__PURE__ */ jsx8(AnimatePresence, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx8(motion.span, { className: "piloto-wait-text", initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.3, ease: "easeInOut" }, children: what }, what) }),
2181
- showDuration && /* @__PURE__ */ jsx8("span", { className: "piloto-wait-time", children: formatDuration(seconds) })
2213
+ /* @__PURE__ */ jsx9(AnimatePresence, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx9(motion.span, { className: "piloto-wait-text", initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.3, ease: "easeInOut" }, children: what }, what) }),
2214
+ showDuration && /* @__PURE__ */ jsx9("span", { className: "piloto-wait-time", children: formatDuration(seconds) })
2182
2215
  ] })
2183
2216
  ) });
2184
2217
  }
@@ -2188,7 +2221,7 @@ function ConnectingState({ phase, error, avatar, onRetry }) {
2188
2221
  const current = PHASES.indexOf(phase);
2189
2222
  return /* @__PURE__ */ jsxs7(motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.3 }, className: "piloto-connecting flex min-h-[40vh] flex-col items-center justify-center gap-8 text-center", children: [
2190
2223
  /* @__PURE__ */ jsxs7("div", { className: "relative flex h-20 w-20 items-center justify-center", children: [
2191
- !error && [0, 1].map((i) => /* @__PURE__ */ jsx8(
2224
+ !error && [0, 1].map((i) => /* @__PURE__ */ jsx9(
2192
2225
  motion.span,
2193
2226
  {
2194
2227
  className: "absolute inset-0 rounded-full border border-border-primary",
@@ -2198,34 +2231,34 @@ function ConnectingState({ phase, error, avatar, onRetry }) {
2198
2231
  },
2199
2232
  i
2200
2233
  )),
2201
- /* @__PURE__ */ jsx8(
2234
+ /* @__PURE__ */ jsx9(
2202
2235
  motion.div,
2203
2236
  {
2204
2237
  className: "grid h-12 w-12 place-items-center",
2205
2238
  animate: error ? { scale: 1, filter: "grayscale(1)" } : { scale: [1, 1.06, 1] },
2206
2239
  transition: error ? { duration: 0.3 } : { duration: 2.2, repeat: Infinity, ease: "easeInOut" },
2207
- children: avatar ?? /* @__PURE__ */ jsx8("span", { className: "piloto-avatar-default" })
2240
+ children: avatar ?? /* @__PURE__ */ jsx9("span", { className: "piloto-avatar-default" })
2208
2241
  }
2209
2242
  )
2210
2243
  ] }),
2211
- /* @__PURE__ */ jsx8("ol", { className: "flex flex-col gap-3 text-left", children: t.connectSteps.map((step, i) => {
2244
+ /* @__PURE__ */ jsx9("ol", { className: "flex flex-col gap-3 text-left", children: t.connectSteps.map((step, i) => {
2212
2245
  const done = !error && i < current;
2213
2246
  const active = !error && i === current;
2214
2247
  const failed = !!error && i === current;
2215
2248
  return /* @__PURE__ */ jsxs7("li", { className: "flex items-center gap-3", children: [
2216
- /* @__PURE__ */ jsx8("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", children: done ? /* @__PURE__ */ jsx8("span", { className: "piloto-pop flex h-5 w-5 items-center justify-center rounded-full bg-background-success", children: /* @__PURE__ */ jsx8(Check, { className: "h-3 w-3 text-text-inverse", strokeWidth: 3 }) }) : active ? /* @__PURE__ */ jsx8("span", { className: "piloto-pulse h-2.5 w-2.5 rounded-full bg-text-primary" }) : failed ? /* @__PURE__ */ jsx8("span", { className: "h-2.5 w-2.5 rounded-full bg-text-danger" }) : /* @__PURE__ */ jsx8("span", { className: "h-2 w-2 rounded-full bg-border-primary" }) }),
2249
+ /* @__PURE__ */ jsx9("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", children: done ? /* @__PURE__ */ jsx9("span", { className: "piloto-pop flex h-5 w-5 items-center justify-center rounded-full bg-background-success", children: /* @__PURE__ */ jsx9(Check, { className: "h-3 w-3 text-text-inverse", strokeWidth: 3 }) }) : active ? /* @__PURE__ */ jsx9("span", { className: "piloto-pulse h-2.5 w-2.5 rounded-full bg-text-primary" }) : failed ? /* @__PURE__ */ jsx9("span", { className: "h-2.5 w-2.5 rounded-full bg-text-danger" }) : /* @__PURE__ */ jsx9("span", { className: "h-2 w-2 rounded-full bg-border-primary" }) }),
2217
2250
  /* @__PURE__ */ jsxs7("div", { className: "leading-tight", children: [
2218
2251
  /* @__PURE__ */ jsxs7("p", { className: `text-sm ${active || failed ? "text-text-primary" : done ? "text-text-secondary" : "text-text-tertiary"}`, children: [
2219
2252
  step.label,
2220
- active && /* @__PURE__ */ jsx8("span", { className: "piloto-ellipsis inline-block w-4 text-left", children: "\u2026" })
2253
+ active && /* @__PURE__ */ jsx9("span", { className: "piloto-ellipsis inline-block w-4 text-left", children: "\u2026" })
2221
2254
  ] }),
2222
- active && /* @__PURE__ */ jsx8("p", { className: "text-xs text-text-tertiary", children: step.hint })
2255
+ active && /* @__PURE__ */ jsx9("p", { className: "text-xs text-text-tertiary", children: step.hint })
2223
2256
  ] })
2224
2257
  ] }, i);
2225
2258
  }) }),
2226
2259
  error && /* @__PURE__ */ jsxs7("div", { className: "flex max-w-md flex-col items-center gap-3", children: [
2227
- /* @__PURE__ */ jsx8("p", { className: "text-sm text-text-danger", children: error }),
2228
- onRetry && /* @__PURE__ */ jsx8("button", { type: "button", onClick: onRetry, className: "rounded-full border border-border-primary px-4 py-1.5 text-sm text-text-primary hover:bg-background-secondary", children: t.connectRetry })
2260
+ /* @__PURE__ */ jsx9("p", { className: "text-sm text-text-danger", children: error }),
2261
+ onRetry && /* @__PURE__ */ jsx9("button", { type: "button", onClick: onRetry, className: "rounded-full border border-border-primary px-4 py-1.5 text-sm text-text-primary hover:bg-background-secondary", children: t.connectRetry })
2229
2262
  ] })
2230
2263
  ] });
2231
2264
  }
@@ -2279,8 +2312,10 @@ function usePilot(transport, opts = {}) {
2279
2312
  );
2280
2313
  };
2281
2314
  if (transport.history && !optsRef.current.initialTurns) {
2282
- transport.history().then((turns) => {
2283
- if (!gone && turns.length && !ref.current.turns.length) commit({ ...ref.current, turns, effects: [] });
2315
+ transport.history().then((h) => {
2316
+ if (gone) return;
2317
+ const { turns, permissions } = Array.isArray(h) ? { turns: h, permissions: [] } : h;
2318
+ commit(applyHistory(ref.current, turns, permissions));
2284
2319
  }).catch(() => {
2285
2320
  }).finally(subscribe);
2286
2321
  } else subscribe();
@@ -2401,7 +2436,7 @@ function useSseTransport(base, opts) {
2401
2436
  }
2402
2437
 
2403
2438
  // src/react/Piloto.tsx
2404
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2439
+ import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2405
2440
  var useIsoLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect;
2406
2441
  var ANCHOR_GAP = 24;
2407
2442
  var Piloto = forwardRef3(function Piloto2(props, handle) {
@@ -2435,8 +2470,17 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2435
2470
  },
2436
2471
  [props.references, props.onReferencesChange]
2437
2472
  );
2438
- const [draft, setDraftState] = useState9({ text: "", n: 0 });
2439
2473
  const composerBox = useRef5(null);
2474
+ const draftText = typeof props.draft === "string" ? props.draft : props.draft?.text;
2475
+ const draftKey = typeof props.draft === "object" && props.draft ? props.draft.key : void 0;
2476
+ const [draft, setDraftState] = useState9(() => ({ text: draftText ?? "", n: 0 }));
2477
+ const draftSeen = useRef5(`${draftText}\0${draftKey}`);
2478
+ useEffect6(() => {
2479
+ const id = `${draftText}\0${draftKey}`;
2480
+ if (draftText == null || id === draftSeen.current) return;
2481
+ draftSeen.current = id;
2482
+ setDraftState((d) => ({ text: draftText, n: d.n + 1 }));
2483
+ }, [draftText, draftKey]);
2440
2484
  const send = useCallback3(
2441
2485
  (text, attachments = []) => {
2442
2486
  const current = refsRef.current;
@@ -2567,7 +2611,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2567
2611
  const id = setTimeout(() => setSlowConnect(true), 800);
2568
2612
  return () => clearTimeout(id);
2569
2613
  }, [connected]);
2570
- const composer = /* @__PURE__ */ jsx9("div", { ref: composerBox, children: /* @__PURE__ */ jsx9(
2614
+ const composer = /* @__PURE__ */ jsx10("div", { ref: composerBox, children: /* @__PURE__ */ jsx10(
2571
2615
  Composer,
2572
2616
  {
2573
2617
  initialValue: draft.text,
@@ -2591,20 +2635,27 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2591
2635
  },
2592
2636
  draft.n
2593
2637
  ) });
2594
- const permissionCards = cards.map((p) => /* @__PURE__ */ jsx9(ConfirmCard, { request: p, onRespond: respond, onSettled: settle, onExpire: pilot.expirePermission }, p.id));
2595
- return /* @__PURE__ */ jsx9(PilotoContext.Provider, { value: config, children: /* @__PURE__ */ jsx9("div", { ...rootProps(theme, props.skin), "data-busy": busy || void 0, className: `piloto ${props.className ?? ""}`, style: props.style, children: empty ? /* @__PURE__ */ jsxs8("div", { className: "piloto-empty flex min-h-0 flex-1 flex-col items-center justify-center px-4 pb-[8vh]", children: [
2596
- slots.headerActions && /* @__PURE__ */ jsx9("div", { className: "absolute right-4 top-3 flex gap-2", children: slots.headerActions }),
2597
- !connected && slowConnect ? /* @__PURE__ */ jsx9(ConnectingState, { phase, error, avatar: props.avatar }) : slots.emptyState ?? /* @__PURE__ */ jsxs8("div", { className: "piloto-welcome mb-6 flex flex-col items-center gap-3 text-center", children: [
2598
- props.avatar,
2599
- props.welcome && /* @__PURE__ */ jsx9("div", { className: "text-2xl font-semibold text-text-primary", children: props.welcome })
2600
- ] }),
2601
- /* @__PURE__ */ jsxs8("div", { className: "w-full max-w-3xl", children: [
2602
- slots.aboveComposer,
2603
- composer,
2604
- props.suggestions && /* @__PURE__ */ jsx9(Suggestions, { items: props.suggestions, onPick: (s) => send(s), onPrefill: (s) => setDraftState((d) => ({ text: s, n: d.n + 1 })) })
2638
+ const permissionCards = cards.map((p) => /* @__PURE__ */ jsx10(ConfirmCard, { request: p, onRespond: respond, onSettled: settle, onExpire: pilot.expirePermission }, p.id));
2639
+ return /* @__PURE__ */ jsx10(PilotoContext.Provider, { value: config, children: /* @__PURE__ */ jsx10("div", { ...rootProps(theme, props.skin), "data-busy": busy || void 0, className: `piloto ${props.className ?? ""}`, style: props.style, children: empty ? (
2640
+ // Si no cabe (panel bajo, saludo largo), se desplaza en vez de recortarse: el centrado es con
2641
+ // márgenes automáticos, que nunca empujan contenido fuera del contenedor.
2642
+ /* @__PURE__ */ jsxs8("div", { className: "piloto-empty", children: [
2643
+ slots.headerActions && /* @__PURE__ */ jsx10("div", { className: "absolute right-4 top-3 flex gap-2", children: slots.headerActions }),
2644
+ /* @__PURE__ */ jsxs8("div", { className: "piloto-empty-inner", children: [
2645
+ !connected && slowConnect ? /* @__PURE__ */ jsx10(ConnectingState, { phase, error, avatar: props.avatar }) : slots.emptyState ?? /* @__PURE__ */ jsxs8("div", { className: "piloto-welcome mb-6 flex flex-col items-center gap-3 text-center", children: [
2646
+ props.avatar,
2647
+ props.welcome && // Un saludo largo (una explicación) va en letra de lectura; uno corto, en la de cartel.
2648
+ /* @__PURE__ */ jsx10("div", { className: "piloto-welcome-text", "data-long": typeof props.welcome === "string" && props.welcome.length > 60 ? "" : void 0, children: props.welcome })
2649
+ ] }),
2650
+ /* @__PURE__ */ jsxs8("div", { className: "w-full max-w-3xl", children: [
2651
+ slots.aboveComposer,
2652
+ composer,
2653
+ props.suggestions && /* @__PURE__ */ jsx10(Suggestions, { items: props.suggestions, onPick: (s) => send(s), onPrefill: (s) => setDraftState((d) => ({ text: s, n: d.n + 1 })) })
2654
+ ] })
2655
+ ] })
2605
2656
  ] })
2606
- ] }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
2607
- slots.headerActions && /* @__PURE__ */ jsx9("div", { className: "flex shrink-0 justify-end gap-2 px-4 pt-3", children: slots.headerActions }),
2657
+ ) : /* @__PURE__ */ jsxs8(Fragment5, { children: [
2658
+ slots.headerActions && /* @__PURE__ */ jsx10("div", { className: "flex shrink-0 justify-end gap-2 px-4 pt-3", children: slots.headerActions }),
2608
2659
  /* @__PURE__ */ jsxs8(
2609
2660
  "div",
2610
2661
  {
@@ -2625,7 +2676,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2625
2676
  turns.map((turn, i) => {
2626
2677
  const isLast = i === turns.length - 1;
2627
2678
  const live = busy && isLast;
2628
- return /* @__PURE__ */ jsx9("div", { "data-turn": i, children: turn.role === "user" ? /* @__PURE__ */ jsx9(UserBubble, { turn }) : /* @__PURE__ */ jsx9(
2679
+ return /* @__PURE__ */ jsx10("div", { "data-turn": i, children: turn.role === "user" ? /* @__PURE__ */ jsx10(UserBubble, { turn }) : /* @__PURE__ */ jsx10(
2629
2680
  AgentBubble,
2630
2681
  {
2631
2682
  turn,
@@ -2640,30 +2691,30 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2640
2691
  }),
2641
2692
  permissionCards,
2642
2693
  busy && !toolRunning && !cards.length && /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1.5", children: [
2643
- /* @__PURE__ */ jsx9(TypingDots, {}),
2644
- /* @__PURE__ */ jsx9(WaitLabel, { phase, since: state.turnStartedAt, elsewhere: state.elsewhere, onLost: pilot.abandonAndRetry, showDuration: features.duration !== false })
2694
+ /* @__PURE__ */ jsx10(TypingDots, {}),
2695
+ /* @__PURE__ */ jsx10(WaitLabel, { phase, since: state.turnStartedAt, elsewhere: state.elsewhere, onLost: pilot.abandonAndRetry, showDuration: features.duration !== false })
2645
2696
  ] }),
2646
2697
  interrupted && !busy && /* @__PURE__ */ jsxs8("p", { className: "piloto-interrupted text-sm text-text-secondary", children: [
2647
2698
  /me quedé a medias|i stopped halfway/i.test(last?.text ?? "") ? t.interruptedOurFault : t.interrupted(interrupted.reason),
2648
2699
  " ",
2649
- /* @__PURE__ */ jsx9("button", { type: "button", onClick: pilot.resume, className: "cursor-pointer text-text-primary underline underline-offset-2", children: interrupted.canContinue ? t.continueTurn : t.resume })
2700
+ /* @__PURE__ */ jsx10("button", { type: "button", onClick: pilot.resume, className: "cursor-pointer text-text-primary underline underline-offset-2", children: interrupted.canContinue ? t.continueTurn : t.resume })
2650
2701
  ] }),
2651
- notice && /* @__PURE__ */ jsx9("p", { className: "piloto-notice text-sm text-text-tertiary", children: notice }),
2652
- error && /* @__PURE__ */ jsx9("p", { className: "piloto-error text-sm text-text-danger", children: error })
2702
+ notice && /* @__PURE__ */ jsx10("p", { className: "piloto-notice text-sm text-text-tertiary", children: notice }),
2703
+ error && /* @__PURE__ */ jsx10("p", { className: "piloto-error text-sm text-text-danger", children: error })
2653
2704
  ] }),
2654
- /* @__PURE__ */ jsx9("div", { ref: spacer, "aria-hidden": true })
2705
+ /* @__PURE__ */ jsx10("div", { ref: spacer, "aria-hidden": true })
2655
2706
  ]
2656
2707
  }
2657
2708
  ),
2658
2709
  /* @__PURE__ */ jsxs8("div", { className: "piloto-dock relative mx-auto w-full px-4 pb-4", children: [
2659
- !atBottom && /* @__PURE__ */ jsx9(
2710
+ !atBottom && /* @__PURE__ */ jsx10(
2660
2711
  "button",
2661
2712
  {
2662
2713
  type: "button",
2663
2714
  onClick: toBottom,
2664
2715
  "aria-label": t.goToLatest,
2665
2716
  className: "piloto-to-bottom absolute -top-12 left-1/2 z-10 flex h-9 w-9 -translate-x-1/2 items-center justify-center rounded-full border border-border-primary bg-background-secondary text-text-secondary shadow-(--piloto-shadow-card) transition hover:text-text-primary",
2666
- children: /* @__PURE__ */ jsx9(ArrowDown, { className: "h-4 w-4" })
2717
+ children: /* @__PURE__ */ jsx10(ArrowDown, { className: "h-4 w-4" })
2667
2718
  }
2668
2719
  ),
2669
2720
  slots.aboveComposer,
@@ -2682,6 +2733,7 @@ export {
2682
2733
  DEFAULT_TOOL_ICONS,
2683
2734
  DecisionLines,
2684
2735
  DeliveryChips,
2736
+ InlineMarkdown,
2685
2737
  LinkSafetyModal,
2686
2738
  Markdown,
2687
2739
  ModelPicker,
@@ -2705,6 +2757,7 @@ export {
2705
2757
  UserBubble,
2706
2758
  WaitLabel,
2707
2759
  appendAgentMessages,
2760
+ applyHistory,
2708
2761
  applyReferences,
2709
2762
  beginSend,
2710
2763
  cleanReplyOption,
@@ -2727,6 +2780,7 @@ export {
2727
2780
  reconcileTurns,
2728
2781
  reconcileWith,
2729
2782
  reducePilotEvent,
2783
+ renderInline,
2730
2784
  resolvePermission,
2731
2785
  resumeMode,
2732
2786
  sendAccepted,
package/dist/styles.css CHANGED
@@ -844,9 +844,6 @@
844
844
  .piloto .pb-4 {
845
845
  padding-bottom: calc(var(--spacing) * 4);
846
846
  }
847
- .piloto .pb-\[8vh\] {
848
- padding-bottom: 8vh;
849
- }
850
847
  .piloto .pl-2\.5 {
851
848
  padding-left: calc(var(--spacing) * 2.5);
852
849
  }
@@ -1710,6 +1707,7 @@
1710
1707
  line-height: 1.55;
1711
1708
  -webkit-font-smoothing: antialiased;
1712
1709
  isolation: isolate;
1710
+ container-type: inline-size;
1713
1711
  }
1714
1712
 
1715
1713
  /* ── Ghosty, oscuro: la sombra dura se vuelve lavanda (negro sobre negro no se ve). ── */
@@ -2072,14 +2070,47 @@
2072
2070
  border: var(--piloto-border-width) solid var(--piloto-border);
2073
2071
  box-shadow: 3px 3px 0 var(--piloto-shadow-color);
2074
2072
  }
2075
- .piloto .piloto-welcome > div {
2073
+ /* El estado vacío: si no cabe, se desplaza (nunca recorta el saludo ni la sombra del composer). */
2074
+ .piloto .piloto-empty {
2075
+ position: relative;
2076
+ display: flex;
2077
+ flex-direction: column;
2078
+ flex: 1 1 auto;
2079
+ min-height: 0;
2080
+ overflow-y: auto;
2081
+ padding: 16px 16px 24px;
2082
+ }
2083
+ .piloto .piloto-empty-inner {
2084
+ margin: auto 0;
2085
+ width: 100%;
2086
+ display: flex;
2087
+ flex-direction: column;
2088
+ align-items: center;
2089
+ padding-bottom: 6vh;
2090
+ }
2091
+ .piloto .piloto-welcome-text {
2076
2092
  font-family: var(--piloto-font-display);
2077
2093
  font-weight: 800;
2078
- font-size: clamp(30px, 5vw, 44px);
2094
+ /* Al ancho del CONTENEDOR (un panel lateral no es la ventana). */
2095
+ font-size: clamp(26px, 8cqi, 44px);
2079
2096
  line-height: 1;
2080
2097
  letter-spacing: 0.01em;
2081
2098
  max-width: 18ch;
2099
+ color: var(--piloto-text);
2100
+ text-wrap: balance;
2101
+ }
2102
+ .piloto .piloto-welcome-text[data-long] {
2103
+ font-family: var(--piloto-font);
2104
+ font-weight: 600;
2105
+ font-size: clamp(16px, 4.2cqi, 19px);
2106
+ line-height: 1.45;
2107
+ letter-spacing: 0;
2108
+ max-width: 44ch;
2109
+ color: var(--piloto-text-2);
2082
2110
  }
2111
+ .piloto .piloto-narration-text code { background: var(--piloto-surface-2); padding: 0.05rem 0.3rem; border-radius: 4px; font-size: 0.88em; font-family: var(--piloto-font-mono); }
2112
+ .piloto .piloto-narration-text strong { font-weight: 700; color: var(--piloto-text); }
2113
+ .piloto .piloto-narration-text a { text-decoration: underline; text-decoration-color: var(--piloto-accent); text-underline-offset: 2px; }
2083
2114
  .piloto .piloto-wait-time { margin-left: 0.7em; color: var(--piloto-text-3); font-variant-numeric: tabular-nums; }
2084
2115
  .piloto .piloto-system-note { border: var(--piloto-hairline) solid var(--piloto-border-2); }
2085
2116
 
@@ -7,7 +7,7 @@ import {
7
7
  createPostStreamTransport,
8
8
  createSseTransport,
9
9
  readSse
10
- } from "../chunks/chunk-KZOMMG2D.js";
10
+ } from "../chunks/chunk-D6JXJWYM.js";
11
11
  export {
12
12
  MIN_SILENCE_MS,
13
13
  MISSED_BEATS,
@@ -1,5 +1,5 @@
1
1
  import type { SendInput, Turn } from "../core/types";
2
- import { type PilotTransport } from "./types";
2
+ import { type PilotHistory, type PilotTransport } from "./types";
3
3
  /** Un evento SSE ya separado: nombre + data crudo. */
4
4
  export interface SseMessage {
5
5
  event: string;
@@ -22,8 +22,8 @@ export interface PostStreamTransportOptions {
22
22
  attachUrl?: string | null;
23
23
  /** URL del historial (JSON). Default `url`; `null` = sin historial. */
24
24
  historyUrl?: string | null;
25
- /** Cómo leer el historial. Default: `{messages:[{role:"user"|"agent",text}]}`. */
26
- parseHistory?: (json: unknown) => Turn[];
25
+ /** Cómo leer el historial. Default: `{messages:[{role:"user"|"agent",text}], pending:[permission…]}`. */
26
+ parseHistory?: (json: unknown) => Turn[] | PilotHistory;
27
27
  /** URL para cancelar (POST). Sin ella, «detener» sólo deja de escuchar: el turno sigue en el servidor. */
28
28
  cancelUrl?: string;
29
29
  /** Silencio (sin un solo byte, ni `: ka`) tras el que la conexión se da por muerta. Default 60 s. */
@@ -1,4 +1,9 @@
1
- import type { SendInput, SendResult, Turn } from "../core/types";
1
+ import type { PermissionRequest, SendInput, SendResult, Turn } from "../core/types";
2
+ /** El hilo guardado y las tarjetas de permiso que siguen abiertas (EasyBits: `pending`). */
3
+ export interface PilotHistory {
4
+ turns: Turn[];
5
+ permissions?: PermissionRequest[];
6
+ }
2
7
  /** Un evento con nombre y su `data` ya parseado (`null` si no traía o no era JSON). */
3
8
  export type PilotListener = (name: string, data: unknown) => void;
4
9
  export interface SubscribeOptions {
@@ -19,7 +24,7 @@ export interface PilotTransport {
19
24
  resolved: boolean;
20
25
  }>;
21
26
  /** El hilo guardado, para pintarlo al montar (si la app no pasa `initialTurns`). */
22
- history?(): Promise<Turn[]>;
27
+ history?(): Promise<Turn[] | PilotHistory>;
23
28
  /**
24
29
  * ¿Se puede mandar un mensaje con un turno en vuelo (steer)? Default `true` (Ghosty). Con
25
30
  * `false` (el servidor contesta 409) el composer no deja mandar hasta que el turno cierre.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostykit",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Piloto UI \u2014 the in-app copilot chat from Ghosty Studio: streaming, agent steps, attachments, voice, reconnection and human approval cards. Pure event reducer + SSE transport + React components.",
5
5
  "type": "module",
6
6
  "main": "./dist/core/index.js",