ghostykit 0.3.2 → 0.4.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.
@@ -1,10 +1,7 @@
1
1
  import {
2
- RESTART_NOTICE,
3
- RESUME_TEXT,
4
2
  TOOL_VERBS_EN,
5
3
  TOOL_VERBS_ES,
6
- appendAgentMessages,
7
- applyHistory,
4
+ argsDetail,
8
5
  beginSend,
9
6
  cleanReplyOption,
10
7
  createPilotState,
@@ -15,27 +12,24 @@ import {
15
12
  isNarration,
16
13
  isRunning,
17
14
  lastUserTurn,
15
+ messagesToTurns,
18
16
  narrationToMarkdown,
19
- reconcileTurns,
20
- reconcileWith,
21
- reducePilotEvent,
17
+ reduceAgUiEvent,
22
18
  resolvePermission,
23
- resumeMode,
24
- sendAccepted,
25
19
  sendFailed,
26
- sendRejectedBusy,
27
20
  splitNarration,
28
21
  stepLabel,
29
22
  stopTurn,
30
23
  summarizeTools,
31
- toolName
32
- } from "../chunks/chunk-O6FCE4CS.js";
24
+ toPermission,
25
+ toolName,
26
+ toolNames
27
+ } from "../chunks/chunk-JQEB37IY.js";
33
28
  import {
34
29
  PilotHttpError,
35
- createPostStreamTransport,
36
- createSseTransport,
30
+ createAgUiTransport,
37
31
  readSse
38
- } from "../chunks/chunk-XZHX2NZT.js";
32
+ } from "../chunks/chunk-TMEHQZWL.js";
39
33
  import {
40
34
  applyReferences,
41
35
  cleanUserText,
@@ -43,9 +37,6 @@ import {
43
37
  systemNoteOf
44
38
  } from "../chunks/chunk-6U545WJG.js";
45
39
 
46
- // src/react/Piloto.tsx
47
- import { AnimatePresence as AnimatePresence3 } from "motion/react";
48
-
49
40
  // ../../node_modules/lucide-react/dist/esm/createLucideIcon.mjs
50
41
  import { forwardRef as forwardRef2, createElement as createElement3 } from "react";
51
42
 
@@ -827,7 +818,7 @@ import {
827
818
  useEffect as useEffect6,
828
819
  useImperativeHandle,
829
820
  useLayoutEffect,
830
- useMemo as useMemo4,
821
+ useMemo as useMemo3,
831
822
  useRef as useRef5,
832
823
  useState as useState9
833
824
  } from "react";
@@ -846,7 +837,6 @@ var STRINGS_ES = {
846
837
  numberLocale: "es-MX",
847
838
  placeholder: (agent) => `Escr\xEDbele a ${agent}`,
848
839
  placeholderDefault: "Escr\xEDbele al agente",
849
- placeholderConnecting: "Conectando con el agente\u2026",
850
840
  send: "Enviar",
851
841
  stop: "Detener",
852
842
  uploading: "Subiendo tu archivo\u2026",
@@ -872,20 +862,9 @@ var STRINGS_ES = {
872
862
  voiceFailed: "no se pudo mandar la nota de voz",
873
863
  model: "Modelo",
874
864
  thinking: "Pensando\u2026",
875
- steered: "Se lo pas\xE9 al turno en curso",
876
- waitPhase: {
877
- waking: "Me estoy poniendo los lentes \u{1F453}",
878
- connecting: "Estoy abriendo mi libreta",
879
- session: "Estoy pensando"
880
- },
881
865
  howItGoes: (s) => s < 6 ? "Pensando\u2026" : s < 15 ? "Trabajando\u2026" : s < 60 ? "Estoy en ello\u2026" : s < 120 ? "Esto me lleva un poco\u2026" : s < 300 ? "Llevo un buen rato\u2026" : "Me est\xE1 tomando mucho \u2014 puedes detenerme",
882
866
  stillWorking: "Estoy en ello\u2026",
883
- cantStart: "No pude arrancar.",
884
867
  retry: "Reintentar",
885
- resume: "Retomar",
886
- continueTurn: "Continuar",
887
- interrupted: (reason) => `Me qued\xE9 a medias: ${reason}.`,
888
- interruptedOurFault: "Se cort\xF3 por una falla nuestra, no por tu mensaje.",
889
868
  copy: "Copiar",
890
869
  copied: "Copiado",
891
870
  copyMessage: "Copiar mensaje",
@@ -899,12 +878,6 @@ var STRINGS_ES = {
899
878
  failed: "Fall\xF3",
900
879
  step: "Paso",
901
880
  replyOptions: "Opciones para responder",
902
- connectSteps: [
903
- { label: "Despertando al agente", hint: "si estaba dormido, unos segundos" },
904
- { label: "Abriendo el canal", hint: "conexi\xF3n segura con su m\xE1quina" },
905
- { label: "Creando la sesi\xF3n", hint: "el agente carga su contexto" }
906
- ],
907
- connectRetry: "Reintentar",
908
881
  linkTitle: "Vas a salir de aqu\xED",
909
882
  linkCopy: "Copiar",
910
883
  linkCopied: "Copiado",
@@ -914,13 +887,13 @@ var STRINGS_ES = {
914
887
  confirmTitle: "Necesito tu permiso para",
915
888
  approve: "Aprobar",
916
889
  reject: "Rechazar",
917
- confirmExpired: "Este pedido ya no estaba abierto (se agot\xF3 el plazo).",
918
- confirmFailed: "No se pudo mandar tu respuesta. Intenta de nuevo.",
919
890
  expiresIn: (t) => `Vence en ${t}`,
920
891
  expiredNow: "Venci\xF3 sin respuesta",
921
892
  decisionApproved: "Aprobaste",
922
893
  decisionRejected: "Rechazaste",
923
894
  decisionExpired: "Venci\xF3",
895
+ decisionCancelled: "Descartaste",
896
+ stampCancelled: "Descartado",
924
897
  stampApproved: "Aprobado",
925
898
  stampRejected: "Rechazado",
926
899
  stampExpired: "Vencido"
@@ -929,7 +902,6 @@ var STRINGS_EN = {
929
902
  numberLocale: "en-US",
930
903
  placeholder: (agent) => `Message ${agent}`,
931
904
  placeholderDefault: "Message the agent",
932
- placeholderConnecting: "Connecting to the agent\u2026",
933
905
  send: "Send",
934
906
  stop: "Stop",
935
907
  uploading: "Uploading your file\u2026",
@@ -955,20 +927,9 @@ var STRINGS_EN = {
955
927
  voiceFailed: "couldn't send the voice note",
956
928
  model: "Model",
957
929
  thinking: "Thinking\u2026",
958
- steered: "Passed it to the running turn",
959
- waitPhase: {
960
- waking: "Putting my glasses on \u{1F453}",
961
- connecting: "Opening my notebook",
962
- session: "Thinking"
963
- },
964
930
  howItGoes: (s) => s < 6 ? "Thinking\u2026" : s < 15 ? "Working\u2026" : s < 60 ? "On it\u2026" : s < 120 ? "This is taking a bit\u2026" : s < 300 ? "Been at it a while\u2026" : "This is taking long \u2014 you can stop me",
965
931
  stillWorking: "On it\u2026",
966
- cantStart: "Couldn't start.",
967
932
  retry: "Retry",
968
- resume: "Resume",
969
- continueTurn: "Continue",
970
- interrupted: (reason) => `I stopped halfway: ${reason}.`,
971
- interruptedOurFault: "It stopped because of an error on our side, not your message.",
972
933
  copy: "Copy",
973
934
  copied: "Copied",
974
935
  copyMessage: "Copy message",
@@ -982,12 +943,6 @@ var STRINGS_EN = {
982
943
  failed: "Failed",
983
944
  step: "Step",
984
945
  replyOptions: "Reply options",
985
- connectSteps: [
986
- { label: "Waking the agent", hint: "if it was asleep, a few seconds" },
987
- { label: "Opening the channel", hint: "secure connection to its machine" },
988
- { label: "Creating the session", hint: "the agent loads its context" }
989
- ],
990
- connectRetry: "Retry",
991
946
  linkTitle: "You're about to leave",
992
947
  linkCopy: "Copy",
993
948
  linkCopied: "Copied",
@@ -997,13 +952,13 @@ var STRINGS_EN = {
997
952
  confirmTitle: "I need your permission to",
998
953
  approve: "Approve",
999
954
  reject: "Reject",
1000
- confirmExpired: "This request was no longer open (it timed out).",
1001
- confirmFailed: "Couldn't send your answer. Try again.",
1002
955
  expiresIn: (t) => `Expires in ${t}`,
1003
956
  expiredNow: "Expired without an answer",
1004
957
  decisionApproved: "You approved",
1005
958
  decisionRejected: "You rejected",
1006
959
  decisionExpired: "Expired",
960
+ decisionCancelled: "You dismissed",
961
+ stampCancelled: "Dismissed",
1007
962
  stampApproved: "Approved",
1008
963
  stampRejected: "Rejected",
1009
964
  stampExpired: "Expired"
@@ -1053,9 +1008,8 @@ function ConfirmCard({
1053
1008
  }) {
1054
1009
  const { strings: t } = usePiloto();
1055
1010
  const [pending, setPending] = useState(null);
1056
- const [problem, setProblem] = useState(null);
1057
1011
  const [stamp, setStamp] = useState(null);
1058
- const time = useRemaining(request.expiresAt);
1012
+ const time = useRemaining(request.deadline ?? request.expiresAt);
1059
1013
  const expired = time !== null && time.left <= 0;
1060
1014
  const urgent = time !== null && !expired && time.left < 6e4;
1061
1015
  useEffect(() => {
@@ -1064,25 +1018,19 @@ function ConfirmCard({
1064
1018
  const id = setTimeout(() => onExpire?.(request.id), STAMP_MS + 600);
1065
1019
  return () => clearTimeout(id);
1066
1020
  }, [expired, stamp, onExpire, request.id]);
1067
- const options = request.options.length ? request.options : [
1021
+ const ordered = [
1068
1022
  { optionId: "reject", name: t.reject, kind: "reject_once" },
1069
- { optionId: "allow", name: t.approve, kind: "allow_once" }
1023
+ { optionId: "approve", name: t.approve, kind: "allow_once" }
1070
1024
  ];
1071
- const ordered = [...options.filter(isRejectOption), ...options.filter((o) => !isRejectOption(o))];
1072
- const answer = async (o) => {
1025
+ const answer = (o) => {
1073
1026
  setPending(o.optionId);
1074
- setProblem(null);
1075
- try {
1076
- const r = await onRespond(request.id, o.optionId);
1077
- const status = r && r.resolved === false ? "expired" : isRejectOption(o) ? "rejected" : "approved";
1078
- setStamp(status);
1079
- setTimeout(() => onSettled?.(request.id, o.optionId, r), STAMP_MS);
1080
- } catch {
1081
- setProblem(t.confirmFailed);
1082
- setPending(null);
1083
- }
1027
+ const approved = !isRejectOption(o);
1028
+ onRespond(request.id, approved);
1029
+ const status = approved ? "approved" : "rejected";
1030
+ setStamp(status);
1031
+ setTimeout(() => onSettled?.(request.id, status), STAMP_MS);
1084
1032
  };
1085
- const stampLabel = stamp === "approved" ? t.stampApproved : stamp === "rejected" ? t.stampRejected : t.stampExpired;
1033
+ const stampLabel = stamp === "approved" ? t.stampApproved : stamp === "rejected" ? t.stampRejected : stamp === "cancelled" ? t.stampCancelled : t.stampExpired;
1086
1034
  const locked = pending !== null || stamp !== null;
1087
1035
  return /* @__PURE__ */ jsxs("section", { className: "piloto-ticket", "data-state": stamp ?? "open", "data-urgent": urgent || void 0, "aria-label": t.confirmTitle, children: [
1088
1036
  /* @__PURE__ */ jsxs("div", { className: "piloto-ticket-head", children: [
@@ -1104,7 +1052,7 @@ function ConfirmCard({
1104
1052
  {
1105
1053
  type: "button",
1106
1054
  disabled: locked || expired,
1107
- onClick: () => void answer(o),
1055
+ onClick: () => answer(o),
1108
1056
  className: isRejectOption(o) ? "piloto-btn piloto-btn-quiet" : "piloto-btn piloto-btn-go",
1109
1057
  "aria-busy": pending === o.optionId || void 0,
1110
1058
  children: o.name
@@ -1113,7 +1061,6 @@ function ConfirmCard({
1113
1061
  )) })
1114
1062
  ] }),
1115
1063
  time !== null && /* @__PURE__ */ jsx("div", { className: "piloto-fuse", "aria-hidden": true, children: /* @__PURE__ */ jsx("span", { style: { transform: `scaleX(${Math.max(0, Math.min(1, time.left / time.total))})` } }) }),
1116
- problem && /* @__PURE__ */ jsx("p", { className: "piloto-ticket-problem", role: "alert", children: problem }),
1117
1064
  stamp && /* @__PURE__ */ jsx("span", { className: "piloto-stamp", "data-kind": stamp, role: "status", children: stampLabel })
1118
1065
  ] });
1119
1066
  }
@@ -1121,8 +1068,8 @@ function DecisionLines({ decisions }) {
1121
1068
  const { strings: t } = usePiloto();
1122
1069
  if (!decisions.length) return null;
1123
1070
  return /* @__PURE__ */ jsx("ul", { className: "piloto-decisions", children: decisions.map((d) => {
1124
- const Icon2 = d.status === "approved" ? CircleCheck : d.status === "rejected" ? CircleSlash : Clock;
1125
- const label = d.status === "approved" ? t.decisionApproved : d.status === "rejected" ? t.decisionRejected : t.decisionExpired;
1071
+ const Icon2 = d.status === "approved" ? CircleCheck : d.status === "expired" ? Clock : CircleSlash;
1072
+ const label = d.status === "approved" ? t.decisionApproved : d.status === "rejected" ? t.decisionRejected : d.status === "cancelled" ? t.decisionCancelled : t.decisionExpired;
1126
1073
  return /* @__PURE__ */ jsxs("li", { className: "piloto-decision", "data-status": d.status, children: [
1127
1074
  /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4 shrink-0", "aria-hidden": true }),
1128
1075
  /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
@@ -1461,8 +1408,8 @@ var DEFAULT_TOOL_ICONS = {
1461
1408
  stop: OctagonX
1462
1409
  };
1463
1410
  function iconFor(t, icons) {
1464
- const name = toolName(t);
1465
- return name && icons[name] || t.kind && (icons[t.kind] ?? DEFAULT_TOOL_ICONS[t.kind]) || ClawIcon;
1411
+ for (const name of toolNames(t)) if (icons[name]) return icons[name];
1412
+ return t.kind && (icons[t.kind] ?? DEFAULT_TOOL_ICONS[t.kind]) || ClawIcon;
1466
1413
  }
1467
1414
  var VISIBLE = 5;
1468
1415
  function AgentSteps({ tools, live }) {
@@ -1501,7 +1448,13 @@ function StepLine({ tool, times, live }) {
1501
1448
  const spins = isRunning(tool) && live;
1502
1449
  const label = stepLabel(tool, toolVerbs, t.step);
1503
1450
  return /* @__PURE__ */ jsxs3("li", { className: "gs-paso-in flex min-w-0 items-center gap-2 text-[13px] leading-6 text-text-secondary", children: [
1504
- /* @__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" }) }),
1451
+ /* @__PURE__ */ jsx5("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", "aria-label": spins ? t.running : tool.status === "failed" ? t.failed : t.done, children: spins ? /* @__PURE__ */ jsx5(LoaderCircle, { className: "h-4 w-4 animate-spin text-text-info" }) : tool.status === "cancelled" ? (
1452
+ // El run se detuvo antes de que terminara.
1453
+ /* @__PURE__ */ jsx5(CircleSlash, { className: "h-4 w-4 text-text-tertiary" })
1454
+ ) : tool.status === "pending" ? (
1455
+ // Espera aprobación (el run se detuvo en un interrupt): ni gira ni está lista.
1456
+ /* @__PURE__ */ jsx5(Clock, { className: "h-4 w-4 text-text-tertiary" })
1457
+ ) : 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" }) }),
1505
1458
  /* @__PURE__ */ jsx5(KindIcon, { className: "h-[22px] w-[22px] shrink-0 text-text-secondary" }),
1506
1459
  /* @__PURE__ */ jsxs3("span", { className: "min-w-0 truncate", children: [
1507
1460
  /* @__PURE__ */ jsx5("span", { className: spins ? "text-text-primary" : void 0, children: label }),
@@ -1520,6 +1473,7 @@ var STATUS = {
1520
1473
  pending: { Icon: Clock, className: "text-text-tertiary", key: "step" },
1521
1474
  in_progress: { Icon: LoaderCircle, className: "animate-spin text-text-info", key: "running" },
1522
1475
  completed: { Icon: CircleCheck, className: "text-text-success", key: "done" },
1476
+ cancelled: { Icon: CircleSlash, className: "text-text-tertiary", key: "step" },
1523
1477
  failed: { Icon: CircleX, className: "text-text-danger", key: "failed" }
1524
1478
  };
1525
1479
  function ToolRow({ tool }) {
@@ -1572,17 +1526,13 @@ function UserBubble({ turn }) {
1572
1526
  }
1573
1527
  const visible = formatUserText(turn.text);
1574
1528
  const atts = turn.attachments ?? [];
1575
- const inBubble = atts.filter((a) => a.mimeType.startsWith("image/") && (a.data || a.uri) || a.mimeType.startsWith("audio/") && a.uri);
1529
+ const inBubble = atts.filter((a) => a.mimeType.startsWith("image/") && (a.data || a.url) || a.mimeType.startsWith("audio/") && a.url);
1576
1530
  const docs = atts.filter((a) => !inBubble.includes(a));
1577
1531
  return /* @__PURE__ */ jsxs4("div", { className: "group/msg flex flex-col items-end gap-1", children: [
1578
- turn.steer && /* @__PURE__ */ jsxs4("span", { className: "text-[11px] text-text-tertiary", children: [
1579
- "\u21AA ",
1580
- t.steered
1581
- ] }),
1582
1532
  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(
1583
1533
  "a",
1584
1534
  {
1585
- href: a.uri,
1535
+ href: a.url,
1586
1536
  target: "_blank",
1587
1537
  rel: "noopener noreferrer",
1588
1538
  className: "inline-flex max-w-[16rem] items-center gap-1.5 rounded-lg border border-border-primary px-2 py-1 text-xs text-text-tertiary",
@@ -1595,7 +1545,7 @@ function UserBubble({ turn }) {
1595
1545
  )) }),
1596
1546
  (inBubble.length > 0 || visible.trim()) && /* @__PURE__ */ jsxs4("div", { className: "gc-msg-user max-w-[75%] whitespace-pre-wrap break-words rounded-(--piloto-radius-bubble) bg-(--piloto-user-bubble) px-4 py-3 font-sans text-base leading-[22px] text-(--piloto-user-text)", children: [
1597
1547
  inBubble.length > 0 && /* @__PURE__ */ jsx6("div", { className: `flex flex-wrap justify-end gap-1.5 ${visible.trim() ? "mb-2" : ""}`, children: inBubble.map(
1598
- (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)
1548
+ (a, i) => a.mimeType.startsWith("image/") ? /* @__PURE__ */ jsx6("img", { src: a.data ? `data:${a.mimeType};base64,${a.data}` : a.url, alt: "", className: "h-20 w-20 rounded-lg object-cover" }, i) : /* @__PURE__ */ jsx6("audio", { src: a.url, controls: true, preload: "metadata", className: "h-9 max-w-[16rem]" }, i)
1599
1549
  ) }),
1600
1550
  visible
1601
1551
  ] }),
@@ -1692,14 +1642,6 @@ function TurnFooter({ text, seconds, onRetry, showCopy = true, showDuration = tr
1692
1642
  ] })
1693
1643
  ] });
1694
1644
  }
1695
- function DeliveryChips({ deliveries }) {
1696
- const shown = deliveries.filter((d) => !d.capture);
1697
- if (!shown.length) return null;
1698
- return /* @__PURE__ */ jsx6("div", { className: "mt-2 flex flex-wrap gap-1.5", children: shown.map((d, i) => /* @__PURE__ */ jsxs4("span", { className: "inline-flex max-w-[16rem] items-center gap-1.5 rounded-lg border border-border-primary px-2 py-1 text-xs text-text-tertiary", children: [
1699
- /* @__PURE__ */ jsx6("span", { "aria-hidden": true, children: "\u{1F4C4}" }),
1700
- /* @__PURE__ */ jsx6("span", { className: "truncate", children: d.name })
1701
- ] }, i)) });
1702
- }
1703
1645
  function AgentBubble({ turn, live, onRetry, onOption, showUsage = true, showCopy = true, showDuration = true, renderDeliveries }) {
1704
1646
  const { strings: t } = usePiloto();
1705
1647
  const narrated = splitNarration(turn.text ?? "", turn.cuts);
@@ -1717,9 +1659,9 @@ function AgentBubble({ turn, live, onRetry, onOption, showUsage = true, showCopy
1717
1659
  /* @__PURE__ */ jsx6(ReplyOptionButtons, { options, onPick: onOption })
1718
1660
  ] }) : (
1719
1661
  // Cada tramo nuevo tras una herramienta monta su propio ritmo (empieza de cero).
1720
- visible && /* @__PURE__ */ jsx6(AnswerText, { text: visible, live: live || !!turn.arrived, whole: !!turn.arrived }, `r${narrated.steps.length}`)
1662
+ visible && /* @__PURE__ */ jsx6(AnswerText, { text: visible, live }, `r${narrated.steps.length}`)
1721
1663
  ),
1722
- renderDeliveries ? renderDeliveries(turn, live) : turn.deliveries?.length ? /* @__PURE__ */ jsx6(DeliveryChips, { deliveries: turn.deliveries }) : null,
1664
+ renderDeliveries?.(turn, live),
1723
1665
  turn.decisions?.length ? /* @__PURE__ */ jsx6(DecisionLines, { decisions: turn.decisions }) : null,
1724
1666
  showUsage && turn.usage && /* @__PURE__ */ jsx6(UsageStats, { ...turn.usage }),
1725
1667
  turn.text && !live && /* @__PURE__ */ jsx6(TurnFooter, { text: turn.text, seconds: turn.seconds, onRetry, showCopy, showDuration })
@@ -1976,7 +1918,7 @@ function Composer({
1976
1918
  const mime = mimeOf(a.file);
1977
1919
  if (mime.startsWith("image/") || isText(mime)) return readInline(a.file);
1978
1920
  const up = await upload(a.file);
1979
- return { name: a.file.name, mimeType: mime, data: "", uri: up.url, ...up.id ? { fileId: up.id } : {} };
1921
+ return { name: a.file.name, mimeType: mime, url: up.url };
1980
1922
  })
1981
1923
  );
1982
1924
  } catch (e) {
@@ -2059,7 +2001,7 @@ function Composer({
2059
2001
  const [said, up] = await Promise.all([transcribe(blob), upload ? upload(new File([blob], name, { type })) : Promise.resolve(null)]);
2060
2002
  const text = said.trim();
2061
2003
  if (!text) throw new Error(t.voiceEmpty);
2062
- const atts = up ? [{ name, mimeType: type, data: "", uri: up.url, ...up.id ? { fileId: up.id } : {} }] : [];
2004
+ const atts = up ? [{ name, mimeType: type, url: up.url }] : [];
2063
2005
  onSubmit(voiceNoteText ? voiceNoteText(text) : text, atts);
2064
2006
  setNotice(null);
2065
2007
  } catch (e) {
@@ -2299,36 +2241,16 @@ function ModelPicker({ models, current, onChange }) {
2299
2241
  ] });
2300
2242
  }
2301
2243
 
2302
- // src/react/connectingGate.ts
2303
- var CONNECTING_DELAY_MS = 1200;
2304
- var CONNECTING_MIN_MS = 700;
2305
- var initialGate = (now, connected) => ({ shownAt: null, waitingSince: connected ? null : now });
2306
- function stepGate(g, connected, now, delay = CONNECTING_DELAY_MS, min = CONNECTING_MIN_MS) {
2307
- const waitingSince = connected ? null : g.waitingSince ?? now;
2308
- let shownAt = g.shownAt;
2309
- if (shownAt === null && waitingSince !== null && now - waitingSince >= delay) shownAt = now;
2310
- if (shownAt !== null && connected && now - shownAt >= min) shownAt = null;
2311
- const gate = { shownAt, waitingSince };
2312
- let recheckAt = null;
2313
- if (shownAt === null && waitingSince !== null) recheckAt = waitingSince + delay;
2314
- else if (shownAt !== null && connected) recheckAt = shownAt + min;
2315
- return { gate, visible: shownAt !== null, recheckAt };
2316
- }
2317
-
2318
2244
  // src/react/Status.tsx
2319
2245
  import { AnimatePresence as AnimatePresence2, motion as motion2 } from "motion/react";
2320
2246
  import { useEffect as useEffect4, useState as useState7 } from "react";
2321
2247
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2322
- var WAKING_GIVEUP_S = 45;
2323
2248
  var STILL_WORKING_S = 90;
2324
2249
  function TypingDots() {
2325
2250
  return /* @__PURE__ */ jsx9("div", { className: "gc-typing flex gap-1", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx9("span", { className: "gc-typing-dot h-2 w-2 animate-bounce rounded-full", style: { "--i": i } }, i)) });
2326
2251
  }
2327
2252
  function WaitLabel({
2328
- phase,
2329
2253
  since,
2330
- elsewhere,
2331
- onLost,
2332
2254
  showDuration = true
2333
2255
  }) {
2334
2256
  const { strings: t } = usePiloto();
@@ -2340,111 +2262,53 @@ function WaitLabel({
2340
2262
  }, []);
2341
2263
  const seconds = Math.max(0, Math.floor((now - (since || mounted)) / 1e3));
2342
2264
  const phrases = t.thinkingPhrases;
2343
- const what = phase !== "session" ? t.waitPhase[phase] : phrases?.length ? elsewhere || seconds >= STILL_WORKING_S ? t.stillWorking : phrases[Math.floor(seconds / 4) % phrases.length] : elsewhere ? t.stillWorking : t.howItGoes(seconds);
2344
- const lost = phase === "waking" && seconds >= WAKING_GIVEUP_S;
2345
- return /* @__PURE__ */ jsx9("div", { className: "flex flex-col gap-0.5 text-sm text-text-secondary", children: lost ? /* @__PURE__ */ jsxs7("p", { className: "text-text-primary", children: [
2346
- t.cantStart,
2347
- " ",
2348
- /* @__PURE__ */ jsx9("button", { type: "button", onClick: onLost, className: "cursor-pointer underline underline-offset-2", children: t.retry })
2349
- ] }) : (
2350
- // La frase cambia con fundido (sale y entra), como en claude.ai.
2351
- /* @__PURE__ */ jsxs7("p", { className: "flex items-center", children: [
2352
- /* @__PURE__ */ jsx9(AnimatePresence2, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx9(
2353
- motion2.span,
2354
- {
2355
- className: "gc-wait-text",
2356
- initial: { opacity: 0 },
2357
- animate: { opacity: 1 },
2358
- exit: { opacity: 0 },
2359
- transition: { duration: 0.3, ease: "easeInOut" },
2360
- children: what
2361
- },
2362
- what
2363
- ) }),
2364
- showDuration && /* @__PURE__ */ jsxs7("span", { className: "tabular-nums text-text-tertiary", children: [
2365
- "\xA0\xB7 ",
2366
- formatDuration(seconds)
2367
- ] })
2265
+ const what = phrases?.length ? seconds >= STILL_WORKING_S ? t.stillWorking : phrases[Math.floor(seconds / 4) % phrases.length] : t.howItGoes(seconds);
2266
+ return /* @__PURE__ */ jsx9("div", { className: "flex flex-col gap-0.5 text-sm text-text-secondary", children: /* @__PURE__ */ jsxs7("p", { className: "flex items-center", children: [
2267
+ /* @__PURE__ */ jsx9(AnimatePresence2, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx9(
2268
+ motion2.span,
2269
+ {
2270
+ className: "gc-wait-text",
2271
+ initial: { opacity: 0 },
2272
+ animate: { opacity: 1 },
2273
+ exit: { opacity: 0 },
2274
+ transition: { duration: 0.3, ease: "easeInOut" },
2275
+ children: what
2276
+ },
2277
+ what
2278
+ ) }),
2279
+ showDuration && /* @__PURE__ */ jsxs7("span", { className: "tabular-nums text-text-tertiary", children: [
2280
+ "\xA0\xB7 ",
2281
+ formatDuration(seconds)
2368
2282
  ] })
2369
- ) });
2370
- }
2371
- var PHASES = ["waking", "connecting", "session"];
2372
- function ConnectingState({ phase, error, avatar, onRetry }) {
2373
- const { strings: t } = usePiloto();
2374
- const current = PHASES.indexOf(phase);
2375
- return /* @__PURE__ */ jsxs7(
2376
- motion2.div,
2377
- {
2378
- initial: { opacity: 0, y: 8 },
2379
- animate: { opacity: 1, y: 0 },
2380
- exit: { opacity: 0, y: -8 },
2381
- transition: { duration: 0.3 },
2382
- className: "flex min-h-[50vh] flex-col items-center justify-center gap-8 text-center",
2383
- children: [
2384
- /* @__PURE__ */ jsxs7("div", { className: "relative flex h-20 w-20 items-center justify-center", children: [
2385
- !error && [0, 1].map((i) => /* @__PURE__ */ jsx9(
2386
- motion2.span,
2387
- {
2388
- className: "absolute inset-0 rounded-full border border-border-primary",
2389
- initial: { scale: 0.6, opacity: 0.8 },
2390
- animate: { scale: 1.6, opacity: 0 },
2391
- transition: { duration: 2.2, repeat: Infinity, ease: "easeOut", delay: i * 1.1 }
2392
- },
2393
- i
2394
- )),
2395
- /* @__PURE__ */ jsx9(
2396
- motion2.div,
2397
- {
2398
- className: "grid h-12 w-12 place-items-center",
2399
- animate: error ? { scale: 1, filter: "grayscale(1)" } : { scale: [1, 1.06, 1] },
2400
- transition: error ? { duration: 0.3 } : { duration: 2.2, repeat: Infinity, ease: "easeInOut" },
2401
- children: avatar ?? /* @__PURE__ */ jsx9("span", { className: "h-12 w-12 rounded-full bg-brand-500" })
2402
- }
2403
- )
2404
- ] }),
2405
- /* @__PURE__ */ jsx9("ol", { className: "flex flex-col gap-3 text-left", children: t.connectSteps.map((step, i) => {
2406
- const done = !error && i < current;
2407
- const active = !error && i === current;
2408
- const failed = !!error && i === current;
2409
- return /* @__PURE__ */ jsxs7("li", { className: "flex items-center gap-3", children: [
2410
- /* @__PURE__ */ jsx9("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", children: /* @__PURE__ */ jsx9(AnimatePresence2, { mode: "wait", initial: false, children: done ? /* @__PURE__ */ jsx9(motion2.span, { initial: { scale: 0 }, animate: { scale: 1 }, className: "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-success", strokeWidth: 3 }) }, "done") : active ? /* @__PURE__ */ jsx9(
2411
- motion2.span,
2412
- {
2413
- className: "h-2.5 w-2.5 rounded-full bg-text-primary",
2414
- animate: { scale: [1, 1.5, 1], opacity: [1, 0.5, 1] },
2415
- transition: { duration: 1.1, repeat: Infinity, ease: "easeInOut" }
2416
- },
2417
- "active"
2418
- ) : failed ? /* @__PURE__ */ jsx9("span", { className: "h-2.5 w-2.5 rounded-full bg-text-danger" }, "failed") : /* @__PURE__ */ jsx9("span", { className: "h-2 w-2 rounded-full bg-border-primary" }, "idle") }) }),
2419
- /* @__PURE__ */ jsxs7("div", { className: "leading-tight", children: [
2420
- /* @__PURE__ */ jsxs7("p", { className: `text-sm ${active || failed ? "text-text-primary" : done ? "text-text-secondary" : "text-text-tertiary"}`, children: [
2421
- step.label,
2422
- active && /* @__PURE__ */ jsx9(
2423
- motion2.span,
2424
- {
2425
- className: "inline-block w-4 text-left",
2426
- animate: { opacity: [0, 1, 1, 0] },
2427
- transition: { duration: 1.4, repeat: Infinity, times: [0, 0.2, 0.8, 1] },
2428
- children: "\u2026"
2429
- }
2430
- )
2431
- ] }),
2432
- active && /* @__PURE__ */ jsx9("p", { className: "text-xs text-text-tertiary", children: step.hint })
2433
- ] })
2434
- ] }, i);
2435
- }) }),
2436
- error && /* @__PURE__ */ jsxs7(motion2.div, { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, className: "flex max-w-md flex-col items-center gap-3", children: [
2437
- /* @__PURE__ */ jsx9("p", { className: "text-sm text-text-danger", children: error }),
2438
- 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 })
2439
- ] })
2440
- ]
2441
- }
2442
- );
2283
+ ] }) });
2443
2284
  }
2444
2285
 
2445
2286
  // src/react/usePilot.ts
2446
- import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef4, useState as useState8 } from "react";
2447
- var message = (e, fallback) => e instanceof Error && e.message ? e.message : fallback;
2287
+ import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef4, useState as useState8 } from "react";
2288
+ var uuid = () => {
2289
+ const c = globalThis.crypto;
2290
+ if (c?.randomUUID) return c.randomUUID();
2291
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
2292
+ const r = Math.random() * 16 | 0;
2293
+ return (ch === "x" ? r : r & 3 | 8).toString(16);
2294
+ });
2295
+ };
2296
+ function toAgUiContext(ctx) {
2297
+ if (ctx === void 0 || ctx === null) return [];
2298
+ if (Array.isArray(ctx) && ctx.every((c) => c && typeof c === "object" && "description" in c && "value" in c)) return ctx;
2299
+ const str = (v) => typeof v === "string" ? v : JSON.stringify(v);
2300
+ if (typeof ctx === "object" && !Array.isArray(ctx)) return Object.entries(ctx).filter(([, v]) => v !== void 0).map(([k, v]) => ({ description: k, value: str(v) }));
2301
+ return [{ description: "context", value: str(ctx) }];
2302
+ }
2303
+ function userMessage(text, attachments) {
2304
+ if (!attachments.length) return { id: uuid(), role: "user", content: text };
2305
+ const parts = text ? [{ type: "text", text }] : [];
2306
+ for (const a of attachments) {
2307
+ const type = a.mimeType.startsWith("image/") ? "image" : a.mimeType.startsWith("audio/") ? "audio" : "document";
2308
+ parts.push({ type, mimeType: a.mimeType, ...a.name ? { filename: a.name } : {}, source: a.url ? { type: "url", value: a.url } : { type: "data", value: a.data ?? "", mimeType: a.mimeType } });
2309
+ }
2310
+ return { id: uuid(), role: "user", content: parts };
2311
+ }
2448
2312
  function usePilot(transport, opts = {}) {
2449
2313
  const [state, setState] = useState8(() => createPilotState(opts.initialTurns ?? []));
2450
2314
  const ref = useRef4(state);
@@ -2452,100 +2316,109 @@ function usePilot(transport, opts = {}) {
2452
2316
  optsRef.current = opts;
2453
2317
  const transportRef = useRef4(transport);
2454
2318
  transportRef.current = transport;
2455
- const pendingWakes = useRef4([]);
2319
+ const [threadId] = useState8(() => opts.threadId ?? uuid());
2320
+ const ctrl = useRef4(null);
2321
+ const liveRun = useRef4(null);
2322
+ const answers = useRef4(/* @__PURE__ */ new Map());
2456
2323
  const commit = useCallback2((next) => {
2457
- const prev = ref.current;
2458
- if (next === prev) return;
2324
+ if (next === ref.current) return;
2459
2325
  ref.current = next;
2460
2326
  setState(next);
2461
2327
  const o = optsRef.current;
2462
2328
  for (const e of next.effects) {
2463
2329
  if (e.type === "turn-start") o.onTurnStart?.();
2464
- else if (e.type === "turn-done") o.onTurnDone?.({ text: e.text, turnId: e.turnId, seconds: e.seconds });
2465
- else if (e.type === "turn-interrupted") {
2466
- const last = next.turns[next.turns.length - 1];
2467
- o.onTurnDone?.({ text: last?.role === "assistant" ? last.text : "", turnId: e.turnId, interrupted: { reason: e.reason, turnId: e.turnId } });
2468
- } else if (e.type === "needs-login") o.onNeedsLogin?.();
2330
+ else if (e.type === "turn-done") o.onTurnDone?.({ text: e.text, runId: e.runId, seconds: e.seconds, error: e.error, interrupted: e.interrupted });
2469
2331
  else if (e.type === "title") o.onTitle?.(e.title);
2470
- else if (e.type === "disconnected") o.onDisconnected?.();
2471
- }
2472
- if (prev.busy && !next.busy && pendingWakes.current.length) {
2473
- const texts = pendingWakes.current.splice(0);
2474
- const after = appendAgentMessages(ref.current, texts);
2475
- ref.current = after;
2476
- setState(after);
2477
2332
  }
2478
2333
  }, []);
2479
- useEffect5(() => {
2480
- if (!transport) return;
2481
- let off = null;
2482
- let gone = false;
2483
- const subscribe = () => {
2484
- if (gone) return;
2485
- off = transport.subscribe(
2486
- (name, data) => {
2487
- optsRef.current.onEvent?.(name, data);
2488
- commit(reducePilotEvent(ref.current, name, data));
2489
- },
2490
- { isBusy: () => ref.current.busy }
2491
- );
2492
- };
2493
- if (transport.history && !optsRef.current.initialTurns) {
2494
- transport.history().then((h) => {
2495
- if (gone) return;
2496
- const { turns, permissions } = Array.isArray(h) ? { turns: h, permissions: [] } : h;
2497
- commit(applyHistory(ref.current, turns, permissions));
2498
- }).catch(() => {
2499
- }).finally(subscribe);
2500
- } else subscribe();
2501
- return () => {
2502
- gone = true;
2503
- off?.();
2504
- };
2505
- }, [transport, commit]);
2506
- const first = useRef4(true);
2507
- useEffect5(() => {
2508
- if (first.current) {
2509
- first.current = false;
2510
- return;
2511
- }
2512
- if (opts.initialTurns) commit(reconcileWith(ref.current, opts.initialTurns));
2513
- }, [opts.initialTurns, commit]);
2514
- const send = useCallback2(
2515
- async (text, attachments = [], extra = {}) => {
2334
+ const runNow = useCallback2(
2335
+ async (partial, reopening = false) => {
2516
2336
  const t = transportRef.current;
2517
2337
  if (!t) return;
2518
- const s0 = ref.current;
2519
- const inFlight = s0.busy;
2520
2338
  const ctx = optsRef.current.context;
2521
2339
  const input = {
2522
- text,
2523
- attachments,
2524
- firstTurn: s0.turns.length === 0,
2525
- ...extra.resume ? { resume: true } : {},
2526
- ...extra.resumeTurnId ? { resumeTurnId: extra.resumeTurnId } : {},
2527
- ...extra.references?.length ? { references: extra.references } : {},
2528
- ...ctx !== void 0 ? { context: typeof ctx === "function" ? ctx() : ctx } : {}
2340
+ threadId,
2341
+ runId: uuid(),
2342
+ tools: [],
2343
+ context: toAgUiContext(typeof ctx === "function" ? ctx() : ctx),
2344
+ state: {},
2345
+ forwardedProps: {},
2346
+ ...partial
2529
2347
  };
2530
- commit(beginSend(s0, text, attachments));
2348
+ ctrl.current?.abort();
2349
+ const c = new AbortController();
2350
+ ctrl.current = c;
2351
+ if (input.messages.length || input.resume?.length) liveRun.current = input.runId;
2531
2352
  optsRef.current.onSend?.(input);
2353
+ let ended = false;
2532
2354
  try {
2533
- const r = await t.send(input);
2534
- commit(sendAccepted(ref.current, r, inFlight));
2535
- } catch (e) {
2536
- if (e?.code === "turn_busy") {
2537
- commit(sendRejectedBusy(ref.current, message(e, "Ya hay un turno en curso.")));
2538
- return;
2355
+ await t.run(
2356
+ input,
2357
+ (e) => {
2358
+ if (c.signal.aborted) return;
2359
+ optsRef.current.onEvent?.(e);
2360
+ if (e.type === "RUN_FINISHED" || e.type === "RUN_ERROR") {
2361
+ ended = true;
2362
+ liveRun.current = null;
2363
+ }
2364
+ commit(reduceAgUiEvent(ref.current, e));
2365
+ },
2366
+ c.signal
2367
+ );
2368
+ } catch (err) {
2369
+ if (c.signal.aborted) return;
2370
+ const code = err instanceof PilotHttpError ? err.code : void 0;
2371
+ if ((code === "run_active" || code === "interrupt_pending") && !reopening) {
2372
+ commit({ ...ref.current, notice: err instanceof Error ? err.message : null, effects: [] });
2373
+ return runNow({ messages: [] }, true);
2539
2374
  }
2540
- commit(sendFailed(ref.current, message(e, "No se pudo mandar el mensaje"), Boolean(e?.needsLogin)));
2375
+ commit(sendFailed(ref.current, err instanceof Error && err.message ? err.message : "No se pudo hablar con el agente."));
2376
+ return;
2541
2377
  }
2378
+ if (ended || c.signal.aborted) return;
2379
+ if (!reopening) return runNow({ messages: [] }, true);
2380
+ commit(sendFailed(ref.current, "Se cort\xF3 la conexi\xF3n con el agente."));
2542
2381
  },
2543
- [commit]
2382
+ [commit, threadId]
2383
+ );
2384
+ useEffect5(() => {
2385
+ if (!transport || !opts.threadId) return;
2386
+ void runNow({ messages: [] }, true);
2387
+ return () => ctrl.current?.abort();
2388
+ }, [transport]);
2389
+ const pendingResume = () => [
2390
+ ...ref.current.permissions.filter((p) => !answers.current.has(p.id)).map((p) => ({ interruptId: p.id, status: "cancelled" })),
2391
+ ...[...answers.current.entries()].map(([interruptId, a]) => ({ interruptId, ...a }))
2392
+ ];
2393
+ const send = useCallback2(
2394
+ async (text, attachments = [], _extra = {}) => {
2395
+ if (!transportRef.current || ref.current.busy) return;
2396
+ const resume = pendingResume();
2397
+ answers.current.clear();
2398
+ let s = ref.current;
2399
+ for (const p of s.permissions) s = resolvePermission(s, p.id, "cancelled");
2400
+ commit(beginSend(s, text, attachments));
2401
+ await runNow({ messages: [userMessage(text, attachments)], ...resume.length ? { resume } : {} });
2402
+ },
2403
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2404
+ [commit, runNow]
2544
2405
  );
2545
2406
  const stop = useCallback2(() => {
2407
+ const run = liveRun.current ?? (ref.current.busy ? ref.current.runId : null);
2408
+ liveRun.current = null;
2409
+ ctrl.current?.abort();
2410
+ ctrl.current = null;
2546
2411
  commit(stopTurn(ref.current));
2547
- void transportRef.current?.cancel();
2548
- }, [commit]);
2412
+ const t = transportRef.current;
2413
+ if (!t || !run) return;
2414
+ const ctx = optsRef.current.context;
2415
+ void t.run(
2416
+ { threadId, runId: uuid(), messages: [], tools: [], context: toAgUiContext(typeof ctx === "function" ? ctx() : ctx), state: {}, forwardedProps: { cancel: run } },
2417
+ () => {
2418
+ }
2419
+ ).catch(() => {
2420
+ });
2421
+ }, [commit, threadId]);
2549
2422
  const retry = useCallback2(() => {
2550
2423
  const s = ref.current;
2551
2424
  const last = lastUserTurn(s.turns);
@@ -2553,65 +2426,35 @@ function usePilot(transport, opts = {}) {
2553
2426
  commit(dropLastExchange(s));
2554
2427
  void send(last.text, last.attachments ?? []);
2555
2428
  }, [commit, send]);
2556
- const abandonAndRetry = useCallback2(() => {
2557
- const s = ref.current;
2558
- const last = lastUserTurn(s.turns);
2559
- if (!last) return;
2560
- commit({ ...dropLastExchange(s), busy: false });
2561
- void send(last.text, last.attachments ?? []);
2562
- }, [commit, send]);
2563
- const resume = useCallback2(() => {
2564
- const s = ref.current;
2565
- if (s.busy) return;
2566
- commit({ ...s, interrupted: null, effects: [] });
2567
- if (resumeMode(s.turns) === "resend") abandonAndRetry();
2568
- else void send(RESUME_TEXT, [], { resume: true, resumeTurnId: s.interrupted?.turnId });
2569
- }, [commit, send, abandonAndRetry]);
2570
- const setModel = useCallback2(
2571
- (value) => {
2572
- commit({ ...ref.current, currentModel: value, effects: [] });
2573
- void transportRef.current?.setModel?.(value);
2574
- },
2575
- [commit]
2576
- );
2429
+ const sendAnswersIfComplete = useCallback2(() => {
2430
+ const open = ref.current.permissions.filter((p) => !answers.current.has(p.id));
2431
+ if (open.length || !answers.current.size || ref.current.busy) return;
2432
+ const resume = [...answers.current.entries()].map(([interruptId, a]) => ({ interruptId, ...a }));
2433
+ answers.current.clear();
2434
+ commit(beginSend(ref.current, "", []));
2435
+ void runNow({ messages: [], resume });
2436
+ }, [commit, runNow]);
2577
2437
  const respondPermission = useCallback2(
2578
- async (id, optionId, via) => {
2579
- const t = transportRef.current;
2580
- const handler = via ?? (t?.respondPermission ? (a, b) => t.respondPermission(a, b) : void 0);
2581
- if (!handler) throw new Error("ghostykit: el transporte no sabe contestar permisos (pasa `onPermission`)");
2582
- const asked = ref.current.permissions.find((p) => p.id === id);
2583
- const r = await handler(id, optionId);
2584
- commit(resolvePermission(ref.current, id, optionId, r ? r.resolved !== false : true, r ? r.detail : void 0, asked));
2585
- return r;
2438
+ (id, approved) => {
2439
+ answers.current.set(id, { status: "resolved", payload: { approved } });
2440
+ sendAnswersIfComplete();
2586
2441
  },
2587
- [commit]
2442
+ [sendAnswersIfComplete]
2588
2443
  );
2589
2444
  const recordDecision = useCallback2(
2590
- (id, optionId, r, asked) => commit(resolvePermission(ref.current, id, optionId, r ? r.resolved !== false : true, r ? r.detail : void 0, asked)),
2445
+ (id, status, asked) => commit(resolvePermission(ref.current, id, status, asked)),
2591
2446
  [commit]
2592
2447
  );
2593
2448
  const expirePermission = useCallback2(
2594
2449
  (id) => {
2595
- if (ref.current.permissions.some((p) => p.id === id)) commit(resolvePermission(ref.current, id, "", false));
2596
- },
2597
- [commit]
2598
- );
2599
- const appendAgentMessage = useCallback2(
2600
- (text) => {
2601
- if (ref.current.busy) {
2602
- pendingWakes.current.push(text);
2603
- return;
2604
- }
2605
- commit(appendAgentMessages(ref.current, [text]));
2450
+ if (!ref.current.permissions.some((p) => p.id === id)) return;
2451
+ answers.current.set(id, { status: "cancelled" });
2452
+ commit(resolvePermission(ref.current, id, "expired"));
2453
+ sendAnswersIfComplete();
2606
2454
  },
2607
- [commit]
2455
+ [commit, sendAnswersIfComplete]
2608
2456
  );
2609
- return { state, send, stop, retry, abandonAndRetry, resume, setModel, respondPermission, recordDecision, expirePermission, appendAgentMessage };
2610
- }
2611
- function useSseTransport(base, opts) {
2612
- const optsRef = useRef4(opts);
2613
- optsRef.current = opts;
2614
- return useMemo3(() => base ? createSseTransport(base, optsRef.current) : null, [base]);
2457
+ return { state, threadId, send, stop, retry, respondPermission, recordDecision, expirePermission };
2615
2458
  }
2616
2459
 
2617
2460
  // src/react/Piloto.tsx
@@ -2623,9 +2466,9 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2623
2466
  const own = usePilot(external ? null : transport, props);
2624
2467
  const pilot = external ?? own;
2625
2468
  const { state } = pilot;
2626
- const { turns, busy, connected, phase, error, notice, interrupted, permissions } = state;
2627
- const strings = useMemo4(() => ({ ...locale === "en" ? STRINGS_EN : STRINGS_ES, ...props.strings }), [locale, props.strings]);
2628
- const config = useMemo4(
2469
+ const { turns, busy, error, notice, permissions } = state;
2470
+ const strings = useMemo3(() => ({ ...locale === "en" ? STRINGS_EN : STRINGS_ES, ...props.strings }), [locale, props.strings]);
2471
+ const config = useMemo3(
2629
2472
  () => ({
2630
2473
  strings,
2631
2474
  toolVerbs: { ...locale === "en" ? TOOL_VERBS_EN : TOOL_VERBS_ES, ...props.toolVerbs },
@@ -2684,21 +2527,19 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2684
2527
  [send, setRefs]
2685
2528
  );
2686
2529
  const [held, setHeld] = useState9({});
2530
+ const heldRef = useRef5(held);
2531
+ heldRef.current = held;
2687
2532
  const releaseHeld = (id) => setHeld(({ [id]: _, ...rest }) => rest);
2688
- const respond = async (id, optionId) => {
2533
+ const respond = (id, approved) => {
2689
2534
  const req = permissions.find((p) => p.id === id);
2690
- if (req) setHeld((h) => ({ ...h, [id]: req }));
2691
- const handler = props.onPermission ?? (transport?.respondPermission ? (a, b) => transport.respondPermission(a, b) : void 0);
2692
- if (!handler) throw new Error("ghostykit: falta `onPermission` (el transporte no contesta permisos)");
2693
- try {
2694
- return await handler(id, optionId);
2695
- } catch (e) {
2696
- releaseHeld(id);
2697
- throw e;
2535
+ if (req) {
2536
+ heldRef.current = { ...heldRef.current, [id]: req };
2537
+ setHeld((h) => ({ ...h, [id]: req }));
2698
2538
  }
2539
+ pilot.respondPermission(id, approved);
2699
2540
  };
2700
- const settle = (id, optionId, answer) => {
2701
- pilot.recordDecision(id, optionId, answer, held[id]);
2541
+ const settle = (id, status) => {
2542
+ pilot.recordDecision(id, status, heldRef.current[id]);
2702
2543
  releaseHeld(id);
2703
2544
  };
2704
2545
  const cards = [...permissions, ...Object.values(held).filter((h) => !permissions.some((p) => p.id === h.id))];
@@ -2708,7 +2549,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2708
2549
  const following = useRef5(true);
2709
2550
  const touchY = useRef5(null);
2710
2551
  const [atBottom, setAtBottom] = useState9(true);
2711
- const message2 = (n) => scroller.current?.querySelector(`[data-turn="${n}"]`) ?? null;
2552
+ const message = (n) => scroller.current?.querySelector(`[data-turn="${n}"]`) ?? null;
2712
2553
  const placed = useRef5(false);
2713
2554
  useEffect6(() => {
2714
2555
  if (placed.current || turns.length === 0) return;
@@ -2719,7 +2560,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2719
2560
  let mineIndex = -1;
2720
2561
  for (let i = turns.length - 1; i >= 0; i--) {
2721
2562
  const x = turns[i];
2722
- if (x.role === "user" && !x.steer && !x.text.startsWith("\u23F0 ")) {
2563
+ if (x.role === "user" && !x.text.startsWith("\u23F0 ")) {
2723
2564
  mineIndex = i;
2724
2565
  break;
2725
2566
  }
@@ -2738,7 +2579,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2738
2579
  const sc = scroller.current;
2739
2580
  const sp = spacer.current;
2740
2581
  if (!sc || !sp) return;
2741
- const a = anchor != null ? message2(anchor) : null;
2582
+ const a = anchor != null ? message(anchor) : null;
2742
2583
  if (!a) {
2743
2584
  sp.style.height = "0px";
2744
2585
  return;
@@ -2749,7 +2590,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2749
2590
  useIsoLayoutEffect(() => {
2750
2591
  measureSpacer();
2751
2592
  const sc = scroller.current;
2752
- const a = anchor != null ? message2(anchor) : null;
2593
+ const a = anchor != null ? message(anchor) : null;
2753
2594
  if (sc && a) sc.scrollTo({ top: a.offsetTop - gapAbove(a), behavior: "smooth" });
2754
2595
  }, [anchor, measureSpacer]);
2755
2596
  useEffect6(() => {
@@ -2779,26 +2620,10 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2779
2620
  if (el) el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
2780
2621
  };
2781
2622
  const last = turns[turns.length - 1];
2782
- const pageInProgress = last?.role === "assistant" && (Boolean(last.planning) || (last.pages ?? []).some((p) => !p.error && (!p.readyAt || p.review?.status === "running")));
2783
- const toolRunning = busy && last?.role === "assistant" && ((last.tools ?? []).some(isRunning) || pageInProgress);
2623
+ const toolRunning = busy && last?.role === "assistant" && (last.tools ?? []).some(isRunning);
2784
2624
  const empty = turns.length === 0 && !busy;
2785
- const perTurn = !external && transport?.stream === "per-turn";
2786
- const ready = connected || perTurn;
2787
- const placeholder = !ready ? t.placeholderConnecting : props.placeholder ?? (props.agentName ? t.placeholder(props.agentName) : t.placeholderDefault);
2625
+ const placeholder = props.placeholder ?? (props.agentName ? t.placeholder(props.agentName) : t.placeholderDefault);
2788
2626
  const showFiles = features.attachments ?? Boolean(props.upload);
2789
- const gateRef = useRef5(initialGate(Date.now(), ready));
2790
- const [showConnecting, setShowConnecting] = useState9(false);
2791
- useEffect6(() => {
2792
- let id;
2793
- const tick = () => {
2794
- const r = stepGate(gateRef.current, ready, Date.now());
2795
- gateRef.current = r.gate;
2796
- setShowConnecting(r.visible);
2797
- if (r.recheckAt !== null) id = setTimeout(tick, Math.max(0, r.recheckAt - Date.now()));
2798
- };
2799
- tick();
2800
- return () => clearTimeout(id);
2801
- }, [ready]);
2802
2627
  const pointedRefs = refs.filter((r) => r.prompt);
2803
2628
  const plainRefs = refs.filter((r) => !r.prompt);
2804
2629
  const removeRef = (id) => setRefs(refsRef.current.filter((r) => r.id !== id));
@@ -2811,16 +2636,12 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2811
2636
  onSubmit: send,
2812
2637
  onStop: pilot.stop,
2813
2638
  busy,
2814
- canSteer: (external ? true : transport?.steer) !== false,
2639
+ canSteer: false,
2815
2640
  placeholder,
2816
- canSeeImages: state.canSeeImages,
2817
2641
  attachments: showFiles,
2818
2642
  upload: props.upload,
2819
2643
  transcribe: props.transcribe,
2820
2644
  voiceNoteText: props.voiceNoteText,
2821
- models: features.models === false ? [] : state.models,
2822
- currentModel: state.currentModel,
2823
- onModelChange: pilot.setModel,
2824
2645
  references: plainRefs,
2825
2646
  onRemoveReference: removeRef,
2826
2647
  onActivateReference: props.onReferenceClick,
@@ -2835,9 +2656,9 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2835
2656
  // empezar. Si no cabe (panel bajo), se desplaza: `safe center` nunca empuja nada fuera.
2836
2657
  /* @__PURE__ */ jsxs8("div", { className: "gs-scroll flex min-h-0 flex-1 flex-col items-center justify-center overflow-y-auto px-4 pb-[10vh] [justify-content:safe_center]", children: [
2837
2658
  slots.headerActions && /* @__PURE__ */ jsx10("div", { className: "absolute right-4 top-3 flex gap-2", children: slots.headerActions }),
2838
- /* @__PURE__ */ jsx10(AnimatePresence3, { mode: "wait", initial: false, children: showConnecting ? /* @__PURE__ */ jsx10(ConnectingState, { phase, error, avatar: props.avatar }, "connecting") : /* @__PURE__ */ jsx10("div", { children: slots.emptyState ?? /* @__PURE__ */ jsxs8("div", { className: "mb-6 flex flex-col items-center", children: [
2659
+ slots.emptyState ?? /* @__PURE__ */ jsxs8("div", { className: "mb-6 flex flex-col items-center", children: [
2839
2660
  props.avatar && /* @__PURE__ */ jsx10("div", { className: "mb-3", children: props.avatar }),
2840
- props.welcome && // Un saludo largo (una explicación del copiloto) baja a letra de lectura.
2661
+ props.welcome && // Un saludo largo (una explicación del piloto) baja a letra de lectura.
2841
2662
  /* @__PURE__ */ jsx10(
2842
2663
  "h1",
2843
2664
  {
@@ -2845,7 +2666,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2845
2666
  children: props.welcome
2846
2667
  }
2847
2668
  )
2848
- ] }) }, "welcome") }),
2669
+ ] }),
2849
2670
  /* @__PURE__ */ jsxs8("div", { className: "w-full max-w-3xl", children: [
2850
2671
  slots.aboveComposer,
2851
2672
  composer,
@@ -2891,12 +2712,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2891
2712
  permissionCards,
2892
2713
  busy && !toolRunning && !cards.length && /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1.5", children: [
2893
2714
  /* @__PURE__ */ jsx10(TypingDots, {}),
2894
- /* @__PURE__ */ jsx10(WaitLabel, { phase, since: state.turnStartedAt, elsewhere: state.elsewhere, onLost: pilot.abandonAndRetry, showDuration: features.duration !== false })
2895
- ] }),
2896
- interrupted && !busy && /* @__PURE__ */ jsxs8("p", { className: "text-sm text-text-secondary", children: [
2897
- /me quedé a medias|i stopped halfway/i.test(last?.text ?? "") ? t.interruptedOurFault : t.interrupted(interrupted.reason),
2898
- " ",
2899
- /* @__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 })
2715
+ /* @__PURE__ */ jsx10(WaitLabel, { since: state.turnStartedAt, showDuration: features.duration !== false })
2900
2716
  ] }),
2901
2717
  notice && /* @__PURE__ */ jsx10("p", { className: "text-sm text-text-tertiary", children: notice }),
2902
2718
  error && /* @__PURE__ */ jsx10("p", { className: "text-sm text-text-danger", children: error })
@@ -2924,14 +2740,13 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2924
2740
  export {
2925
2741
  AgentBubble,
2926
2742
  AgentSteps,
2743
+ ArcadeIcon,
2927
2744
  ClawIcon,
2928
2745
  Composer,
2929
2746
  ConfirmCard,
2930
- ConnectingState,
2931
2747
  CopyUserMessage,
2932
2748
  DEFAULT_TOOL_ICONS,
2933
2749
  DecisionLines,
2934
- DeliveryChips,
2935
2750
  InlineMarkdown,
2936
2751
  LinkSafetyModal,
2937
2752
  Markdown,
@@ -2940,10 +2755,9 @@ export {
2940
2755
  PilotHttpError,
2941
2756
  Piloto,
2942
2757
  PilotoContext,
2943
- RESTART_NOTICE,
2944
- RESUME_TEXT,
2945
2758
  ReferenceChips,
2946
2759
  ReplyOptionButtons,
2760
+ RobotArmIcon,
2947
2761
  STRINGS_EN,
2948
2762
  STRINGS_ES,
2949
2763
  Suggestions,
@@ -2955,15 +2769,13 @@ export {
2955
2769
  UsageStats,
2956
2770
  UserBubble,
2957
2771
  WaitLabel,
2958
- appendAgentMessages,
2959
- applyHistory,
2960
2772
  applyReferences,
2773
+ argsDetail,
2961
2774
  beginSend,
2962
2775
  cleanReplyOption,
2963
2776
  cleanUserText,
2777
+ createAgUiTransport,
2964
2778
  createPilotState,
2965
- createPostStreamTransport,
2966
- createSseTransport,
2967
2779
  Piloto as default,
2968
2780
  dropLastExchange,
2969
2781
  expiryMs,
@@ -2973,26 +2785,24 @@ export {
2973
2785
  isNarration,
2974
2786
  isRunning,
2975
2787
  lastUserTurn,
2788
+ messagesToTurns,
2976
2789
  mimeOf,
2977
2790
  narrationToMarkdown,
2978
2791
  readSse,
2979
- reconcileTurns,
2980
- reconcileWith,
2981
- reducePilotEvent,
2792
+ reduceAgUiEvent,
2982
2793
  renderInline,
2983
2794
  resolvePermission,
2984
- resumeMode,
2985
- sendAccepted,
2986
2795
  sendFailed,
2987
- sendRejectedBusy,
2988
2796
  splitNarration,
2989
2797
  stepLabel,
2990
2798
  stopTurn,
2991
2799
  summarizeTools,
2992
2800
  systemNoteOf,
2801
+ toAgUiContext,
2802
+ toPermission,
2993
2803
  toolName,
2804
+ toolNames,
2994
2805
  usePilot,
2995
2806
  usePiloto,
2996
- useSseTransport,
2997
2807
  useTextAtPace
2998
2808
  };