ghostykit 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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-HPTM2BCM.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-DXFWACUL.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,7 +1008,6 @@ 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
1012
  const time = useRemaining(request.expiresAt);
1059
1013
  const expired = time !== null && time.left <= 0;
@@ -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,10 @@ 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 === "pending" ? (
1452
+ // Espera aprobación (el run se detuvo en un interrupt): ni gira ni está lista.
1453
+ /* @__PURE__ */ jsx5(Clock, { className: "h-4 w-4 text-text-tertiary" })
1454
+ ) : 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
1455
  /* @__PURE__ */ jsx5(KindIcon, { className: "h-[22px] w-[22px] shrink-0 text-text-secondary" }),
1506
1456
  /* @__PURE__ */ jsxs3("span", { className: "min-w-0 truncate", children: [
1507
1457
  /* @__PURE__ */ jsx5("span", { className: spins ? "text-text-primary" : void 0, children: label }),
@@ -1572,17 +1522,13 @@ function UserBubble({ turn }) {
1572
1522
  }
1573
1523
  const visible = formatUserText(turn.text);
1574
1524
  const atts = turn.attachments ?? [];
1575
- const inBubble = atts.filter((a) => a.mimeType.startsWith("image/") && (a.data || a.uri) || a.mimeType.startsWith("audio/") && a.uri);
1525
+ const inBubble = atts.filter((a) => a.mimeType.startsWith("image/") && (a.data || a.url) || a.mimeType.startsWith("audio/") && a.url);
1576
1526
  const docs = atts.filter((a) => !inBubble.includes(a));
1577
1527
  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
1528
  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
1529
  "a",
1584
1530
  {
1585
- href: a.uri,
1531
+ href: a.url,
1586
1532
  target: "_blank",
1587
1533
  rel: "noopener noreferrer",
1588
1534
  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 +1541,7 @@ function UserBubble({ turn }) {
1595
1541
  )) }),
1596
1542
  (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
1543
  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)
1544
+ (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
1545
  ) }),
1600
1546
  visible
1601
1547
  ] }),
@@ -1692,14 +1638,6 @@ function TurnFooter({ text, seconds, onRetry, showCopy = true, showDuration = tr
1692
1638
  ] })
1693
1639
  ] });
1694
1640
  }
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
1641
  function AgentBubble({ turn, live, onRetry, onOption, showUsage = true, showCopy = true, showDuration = true, renderDeliveries }) {
1704
1642
  const { strings: t } = usePiloto();
1705
1643
  const narrated = splitNarration(turn.text ?? "", turn.cuts);
@@ -1717,9 +1655,9 @@ function AgentBubble({ turn, live, onRetry, onOption, showUsage = true, showCopy
1717
1655
  /* @__PURE__ */ jsx6(ReplyOptionButtons, { options, onPick: onOption })
1718
1656
  ] }) : (
1719
1657
  // 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}`)
1658
+ visible && /* @__PURE__ */ jsx6(AnswerText, { text: visible, live }, `r${narrated.steps.length}`)
1721
1659
  ),
1722
- renderDeliveries ? renderDeliveries(turn, live) : turn.deliveries?.length ? /* @__PURE__ */ jsx6(DeliveryChips, { deliveries: turn.deliveries }) : null,
1660
+ renderDeliveries?.(turn, live),
1723
1661
  turn.decisions?.length ? /* @__PURE__ */ jsx6(DecisionLines, { decisions: turn.decisions }) : null,
1724
1662
  showUsage && turn.usage && /* @__PURE__ */ jsx6(UsageStats, { ...turn.usage }),
1725
1663
  turn.text && !live && /* @__PURE__ */ jsx6(TurnFooter, { text: turn.text, seconds: turn.seconds, onRetry, showCopy, showDuration })
@@ -1976,7 +1914,7 @@ function Composer({
1976
1914
  const mime = mimeOf(a.file);
1977
1915
  if (mime.startsWith("image/") || isText(mime)) return readInline(a.file);
1978
1916
  const up = await upload(a.file);
1979
- return { name: a.file.name, mimeType: mime, data: "", uri: up.url, ...up.id ? { fileId: up.id } : {} };
1917
+ return { name: a.file.name, mimeType: mime, url: up.url };
1980
1918
  })
1981
1919
  );
1982
1920
  } catch (e) {
@@ -2059,7 +1997,7 @@ function Composer({
2059
1997
  const [said, up] = await Promise.all([transcribe(blob), upload ? upload(new File([blob], name, { type })) : Promise.resolve(null)]);
2060
1998
  const text = said.trim();
2061
1999
  if (!text) throw new Error(t.voiceEmpty);
2062
- const atts = up ? [{ name, mimeType: type, data: "", uri: up.url, ...up.id ? { fileId: up.id } : {} }] : [];
2000
+ const atts = up ? [{ name, mimeType: type, url: up.url }] : [];
2063
2001
  onSubmit(voiceNoteText ? voiceNoteText(text) : text, atts);
2064
2002
  setNotice(null);
2065
2003
  } catch (e) {
@@ -2299,36 +2237,16 @@ function ModelPicker({ models, current, onChange }) {
2299
2237
  ] });
2300
2238
  }
2301
2239
 
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
2240
  // src/react/Status.tsx
2319
2241
  import { AnimatePresence as AnimatePresence2, motion as motion2 } from "motion/react";
2320
2242
  import { useEffect as useEffect4, useState as useState7 } from "react";
2321
2243
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2322
- var WAKING_GIVEUP_S = 45;
2323
2244
  var STILL_WORKING_S = 90;
2324
2245
  function TypingDots() {
2325
2246
  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
2247
  }
2327
2248
  function WaitLabel({
2328
- phase,
2329
2249
  since,
2330
- elsewhere,
2331
- onLost,
2332
2250
  showDuration = true
2333
2251
  }) {
2334
2252
  const { strings: t } = usePiloto();
@@ -2340,111 +2258,53 @@ function WaitLabel({
2340
2258
  }, []);
2341
2259
  const seconds = Math.max(0, Math.floor((now - (since || mounted)) / 1e3));
2342
2260
  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
- ] })
2261
+ const what = phrases?.length ? seconds >= STILL_WORKING_S ? t.stillWorking : phrases[Math.floor(seconds / 4) % phrases.length] : t.howItGoes(seconds);
2262
+ return /* @__PURE__ */ jsx9("div", { className: "flex flex-col gap-0.5 text-sm text-text-secondary", children: /* @__PURE__ */ jsxs7("p", { className: "flex items-center", children: [
2263
+ /* @__PURE__ */ jsx9(AnimatePresence2, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx9(
2264
+ motion2.span,
2265
+ {
2266
+ className: "gc-wait-text",
2267
+ initial: { opacity: 0 },
2268
+ animate: { opacity: 1 },
2269
+ exit: { opacity: 0 },
2270
+ transition: { duration: 0.3, ease: "easeInOut" },
2271
+ children: what
2272
+ },
2273
+ what
2274
+ ) }),
2275
+ showDuration && /* @__PURE__ */ jsxs7("span", { className: "tabular-nums text-text-tertiary", children: [
2276
+ "\xA0\xB7 ",
2277
+ formatDuration(seconds)
2368
2278
  ] })
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
- );
2279
+ ] }) });
2443
2280
  }
2444
2281
 
2445
2282
  // 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;
2283
+ import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef4, useState as useState8 } from "react";
2284
+ var uuid = () => {
2285
+ const c = globalThis.crypto;
2286
+ if (c?.randomUUID) return c.randomUUID();
2287
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
2288
+ const r = Math.random() * 16 | 0;
2289
+ return (ch === "x" ? r : r & 3 | 8).toString(16);
2290
+ });
2291
+ };
2292
+ function toAgUiContext(ctx) {
2293
+ if (ctx === void 0 || ctx === null) return [];
2294
+ if (Array.isArray(ctx) && ctx.every((c) => c && typeof c === "object" && "description" in c && "value" in c)) return ctx;
2295
+ const str = (v) => typeof v === "string" ? v : JSON.stringify(v);
2296
+ if (typeof ctx === "object" && !Array.isArray(ctx)) return Object.entries(ctx).filter(([, v]) => v !== void 0).map(([k, v]) => ({ description: k, value: str(v) }));
2297
+ return [{ description: "context", value: str(ctx) }];
2298
+ }
2299
+ function userMessage(text, attachments) {
2300
+ if (!attachments.length) return { id: uuid(), role: "user", content: text };
2301
+ const parts = text ? [{ type: "text", text }] : [];
2302
+ for (const a of attachments) {
2303
+ const type = a.mimeType.startsWith("image/") ? "image" : a.mimeType.startsWith("audio/") ? "audio" : "document";
2304
+ 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 } });
2305
+ }
2306
+ return { id: uuid(), role: "user", content: parts };
2307
+ }
2448
2308
  function usePilot(transport, opts = {}) {
2449
2309
  const [state, setState] = useState8(() => createPilotState(opts.initialTurns ?? []));
2450
2310
  const ref = useRef4(state);
@@ -2452,100 +2312,109 @@ function usePilot(transport, opts = {}) {
2452
2312
  optsRef.current = opts;
2453
2313
  const transportRef = useRef4(transport);
2454
2314
  transportRef.current = transport;
2455
- const pendingWakes = useRef4([]);
2315
+ const [threadId] = useState8(() => opts.threadId ?? uuid());
2316
+ const ctrl = useRef4(null);
2317
+ const liveRun = useRef4(null);
2318
+ const answers = useRef4(/* @__PURE__ */ new Map());
2456
2319
  const commit = useCallback2((next) => {
2457
- const prev = ref.current;
2458
- if (next === prev) return;
2320
+ if (next === ref.current) return;
2459
2321
  ref.current = next;
2460
2322
  setState(next);
2461
2323
  const o = optsRef.current;
2462
2324
  for (const e of next.effects) {
2463
2325
  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?.();
2326
+ else if (e.type === "turn-done") o.onTurnDone?.({ text: e.text, runId: e.runId, seconds: e.seconds, error: e.error, interrupted: e.interrupted });
2469
2327
  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
2328
  }
2478
2329
  }, []);
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 = {}) => {
2330
+ const runNow = useCallback2(
2331
+ async (partial, reopening = false) => {
2516
2332
  const t = transportRef.current;
2517
2333
  if (!t) return;
2518
- const s0 = ref.current;
2519
- const inFlight = s0.busy;
2520
2334
  const ctx = optsRef.current.context;
2521
2335
  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 } : {}
2336
+ threadId,
2337
+ runId: uuid(),
2338
+ tools: [],
2339
+ context: toAgUiContext(typeof ctx === "function" ? ctx() : ctx),
2340
+ state: {},
2341
+ forwardedProps: {},
2342
+ ...partial
2529
2343
  };
2530
- commit(beginSend(s0, text, attachments));
2344
+ ctrl.current?.abort();
2345
+ const c = new AbortController();
2346
+ ctrl.current = c;
2347
+ if (input.messages.length || input.resume?.length) liveRun.current = input.runId;
2531
2348
  optsRef.current.onSend?.(input);
2349
+ let ended = false;
2532
2350
  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;
2351
+ await t.run(
2352
+ input,
2353
+ (e) => {
2354
+ if (c.signal.aborted) return;
2355
+ optsRef.current.onEvent?.(e);
2356
+ if (e.type === "RUN_FINISHED" || e.type === "RUN_ERROR") {
2357
+ ended = true;
2358
+ liveRun.current = null;
2359
+ }
2360
+ commit(reduceAgUiEvent(ref.current, e));
2361
+ },
2362
+ c.signal
2363
+ );
2364
+ } catch (err) {
2365
+ if (c.signal.aborted) return;
2366
+ const code = err instanceof PilotHttpError ? err.code : void 0;
2367
+ if ((code === "run_active" || code === "interrupt_pending") && !reopening) {
2368
+ commit({ ...ref.current, notice: err instanceof Error ? err.message : null, effects: [] });
2369
+ return runNow({ messages: [] }, true);
2539
2370
  }
2540
- commit(sendFailed(ref.current, message(e, "No se pudo mandar el mensaje"), Boolean(e?.needsLogin)));
2371
+ commit(sendFailed(ref.current, err instanceof Error && err.message ? err.message : "No se pudo hablar con el agente."));
2372
+ return;
2541
2373
  }
2374
+ if (ended || c.signal.aborted) return;
2375
+ if (!reopening) return runNow({ messages: [] }, true);
2376
+ commit(sendFailed(ref.current, "Se cort\xF3 la conexi\xF3n con el agente."));
2542
2377
  },
2543
- [commit]
2378
+ [commit, threadId]
2379
+ );
2380
+ useEffect5(() => {
2381
+ if (!transport || !opts.threadId) return;
2382
+ void runNow({ messages: [] }, true);
2383
+ return () => ctrl.current?.abort();
2384
+ }, [transport]);
2385
+ const pendingResume = () => [
2386
+ ...ref.current.permissions.filter((p) => !answers.current.has(p.id)).map((p) => ({ interruptId: p.id, status: "cancelled" })),
2387
+ ...[...answers.current.entries()].map(([interruptId, a]) => ({ interruptId, ...a }))
2388
+ ];
2389
+ const send = useCallback2(
2390
+ async (text, attachments = [], _extra = {}) => {
2391
+ if (!transportRef.current || ref.current.busy) return;
2392
+ const resume = pendingResume();
2393
+ answers.current.clear();
2394
+ let s = ref.current;
2395
+ for (const p of s.permissions) s = resolvePermission(s, p.id, "cancelled");
2396
+ commit(beginSend(s, text, attachments));
2397
+ await runNow({ messages: [userMessage(text, attachments)], ...resume.length ? { resume } : {} });
2398
+ },
2399
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2400
+ [commit, runNow]
2544
2401
  );
2545
2402
  const stop = useCallback2(() => {
2403
+ const run = liveRun.current ?? (ref.current.busy ? ref.current.runId : null);
2404
+ liveRun.current = null;
2405
+ ctrl.current?.abort();
2406
+ ctrl.current = null;
2546
2407
  commit(stopTurn(ref.current));
2547
- void transportRef.current?.cancel();
2548
- }, [commit]);
2408
+ const t = transportRef.current;
2409
+ if (!t || !run) return;
2410
+ const ctx = optsRef.current.context;
2411
+ void t.run(
2412
+ { threadId, runId: uuid(), messages: [], tools: [], context: toAgUiContext(typeof ctx === "function" ? ctx() : ctx), state: {}, forwardedProps: { cancel: run } },
2413
+ () => {
2414
+ }
2415
+ ).catch(() => {
2416
+ });
2417
+ }, [commit, threadId]);
2549
2418
  const retry = useCallback2(() => {
2550
2419
  const s = ref.current;
2551
2420
  const last = lastUserTurn(s.turns);
@@ -2553,65 +2422,35 @@ function usePilot(transport, opts = {}) {
2553
2422
  commit(dropLastExchange(s));
2554
2423
  void send(last.text, last.attachments ?? []);
2555
2424
  }, [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
- );
2425
+ const sendAnswersIfComplete = useCallback2(() => {
2426
+ const open = ref.current.permissions.filter((p) => !answers.current.has(p.id));
2427
+ if (open.length || !answers.current.size || ref.current.busy) return;
2428
+ const resume = [...answers.current.entries()].map(([interruptId, a]) => ({ interruptId, ...a }));
2429
+ answers.current.clear();
2430
+ commit(beginSend(ref.current, "", []));
2431
+ void runNow({ messages: [], resume });
2432
+ }, [commit, runNow]);
2577
2433
  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;
2434
+ (id, approved) => {
2435
+ answers.current.set(id, { status: "resolved", payload: { approved } });
2436
+ sendAnswersIfComplete();
2586
2437
  },
2587
- [commit]
2438
+ [sendAnswersIfComplete]
2588
2439
  );
2589
2440
  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)),
2441
+ (id, status, asked) => commit(resolvePermission(ref.current, id, status, asked)),
2591
2442
  [commit]
2592
2443
  );
2593
2444
  const expirePermission = useCallback2(
2594
2445
  (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]));
2446
+ if (!ref.current.permissions.some((p) => p.id === id)) return;
2447
+ answers.current.set(id, { status: "cancelled" });
2448
+ commit(resolvePermission(ref.current, id, "expired"));
2449
+ sendAnswersIfComplete();
2606
2450
  },
2607
- [commit]
2451
+ [commit, sendAnswersIfComplete]
2608
2452
  );
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]);
2453
+ return { state, threadId, send, stop, retry, respondPermission, recordDecision, expirePermission };
2615
2454
  }
2616
2455
 
2617
2456
  // src/react/Piloto.tsx
@@ -2623,9 +2462,9 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2623
2462
  const own = usePilot(external ? null : transport, props);
2624
2463
  const pilot = external ?? own;
2625
2464
  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(
2465
+ const { turns, busy, error, notice, permissions } = state;
2466
+ const strings = useMemo3(() => ({ ...locale === "en" ? STRINGS_EN : STRINGS_ES, ...props.strings }), [locale, props.strings]);
2467
+ const config = useMemo3(
2629
2468
  () => ({
2630
2469
  strings,
2631
2470
  toolVerbs: { ...locale === "en" ? TOOL_VERBS_EN : TOOL_VERBS_ES, ...props.toolVerbs },
@@ -2684,21 +2523,19 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2684
2523
  [send, setRefs]
2685
2524
  );
2686
2525
  const [held, setHeld] = useState9({});
2526
+ const heldRef = useRef5(held);
2527
+ heldRef.current = held;
2687
2528
  const releaseHeld = (id) => setHeld(({ [id]: _, ...rest }) => rest);
2688
- const respond = async (id, optionId) => {
2529
+ const respond = (id, approved) => {
2689
2530
  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;
2531
+ if (req) {
2532
+ heldRef.current = { ...heldRef.current, [id]: req };
2533
+ setHeld((h) => ({ ...h, [id]: req }));
2698
2534
  }
2535
+ pilot.respondPermission(id, approved);
2699
2536
  };
2700
- const settle = (id, optionId, answer) => {
2701
- pilot.recordDecision(id, optionId, answer, held[id]);
2537
+ const settle = (id, status) => {
2538
+ pilot.recordDecision(id, status, heldRef.current[id]);
2702
2539
  releaseHeld(id);
2703
2540
  };
2704
2541
  const cards = [...permissions, ...Object.values(held).filter((h) => !permissions.some((p) => p.id === h.id))];
@@ -2708,7 +2545,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2708
2545
  const following = useRef5(true);
2709
2546
  const touchY = useRef5(null);
2710
2547
  const [atBottom, setAtBottom] = useState9(true);
2711
- const message2 = (n) => scroller.current?.querySelector(`[data-turn="${n}"]`) ?? null;
2548
+ const message = (n) => scroller.current?.querySelector(`[data-turn="${n}"]`) ?? null;
2712
2549
  const placed = useRef5(false);
2713
2550
  useEffect6(() => {
2714
2551
  if (placed.current || turns.length === 0) return;
@@ -2719,7 +2556,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2719
2556
  let mineIndex = -1;
2720
2557
  for (let i = turns.length - 1; i >= 0; i--) {
2721
2558
  const x = turns[i];
2722
- if (x.role === "user" && !x.steer && !x.text.startsWith("\u23F0 ")) {
2559
+ if (x.role === "user" && !x.text.startsWith("\u23F0 ")) {
2723
2560
  mineIndex = i;
2724
2561
  break;
2725
2562
  }
@@ -2738,7 +2575,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2738
2575
  const sc = scroller.current;
2739
2576
  const sp = spacer.current;
2740
2577
  if (!sc || !sp) return;
2741
- const a = anchor != null ? message2(anchor) : null;
2578
+ const a = anchor != null ? message(anchor) : null;
2742
2579
  if (!a) {
2743
2580
  sp.style.height = "0px";
2744
2581
  return;
@@ -2749,7 +2586,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2749
2586
  useIsoLayoutEffect(() => {
2750
2587
  measureSpacer();
2751
2588
  const sc = scroller.current;
2752
- const a = anchor != null ? message2(anchor) : null;
2589
+ const a = anchor != null ? message(anchor) : null;
2753
2590
  if (sc && a) sc.scrollTo({ top: a.offsetTop - gapAbove(a), behavior: "smooth" });
2754
2591
  }, [anchor, measureSpacer]);
2755
2592
  useEffect6(() => {
@@ -2779,26 +2616,10 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2779
2616
  if (el) el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
2780
2617
  };
2781
2618
  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);
2619
+ const toolRunning = busy && last?.role === "assistant" && (last.tools ?? []).some(isRunning);
2784
2620
  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);
2621
+ const placeholder = props.placeholder ?? (props.agentName ? t.placeholder(props.agentName) : t.placeholderDefault);
2788
2622
  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
2623
  const pointedRefs = refs.filter((r) => r.prompt);
2803
2624
  const plainRefs = refs.filter((r) => !r.prompt);
2804
2625
  const removeRef = (id) => setRefs(refsRef.current.filter((r) => r.id !== id));
@@ -2811,16 +2632,12 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2811
2632
  onSubmit: send,
2812
2633
  onStop: pilot.stop,
2813
2634
  busy,
2814
- canSteer: (external ? true : transport?.steer) !== false,
2635
+ canSteer: false,
2815
2636
  placeholder,
2816
- canSeeImages: state.canSeeImages,
2817
2637
  attachments: showFiles,
2818
2638
  upload: props.upload,
2819
2639
  transcribe: props.transcribe,
2820
2640
  voiceNoteText: props.voiceNoteText,
2821
- models: features.models === false ? [] : state.models,
2822
- currentModel: state.currentModel,
2823
- onModelChange: pilot.setModel,
2824
2641
  references: plainRefs,
2825
2642
  onRemoveReference: removeRef,
2826
2643
  onActivateReference: props.onReferenceClick,
@@ -2835,9 +2652,9 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2835
2652
  // empezar. Si no cabe (panel bajo), se desplaza: `safe center` nunca empuja nada fuera.
2836
2653
  /* @__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
2654
  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: [
2655
+ slots.emptyState ?? /* @__PURE__ */ jsxs8("div", { className: "mb-6 flex flex-col items-center", children: [
2839
2656
  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.
2657
+ props.welcome && // Un saludo largo (una explicación del piloto) baja a letra de lectura.
2841
2658
  /* @__PURE__ */ jsx10(
2842
2659
  "h1",
2843
2660
  {
@@ -2845,7 +2662,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2845
2662
  children: props.welcome
2846
2663
  }
2847
2664
  )
2848
- ] }) }, "welcome") }),
2665
+ ] }),
2849
2666
  /* @__PURE__ */ jsxs8("div", { className: "w-full max-w-3xl", children: [
2850
2667
  slots.aboveComposer,
2851
2668
  composer,
@@ -2891,12 +2708,7 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2891
2708
  permissionCards,
2892
2709
  busy && !toolRunning && !cards.length && /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1.5", children: [
2893
2710
  /* @__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 })
2711
+ /* @__PURE__ */ jsx10(WaitLabel, { since: state.turnStartedAt, showDuration: features.duration !== false })
2900
2712
  ] }),
2901
2713
  notice && /* @__PURE__ */ jsx10("p", { className: "text-sm text-text-tertiary", children: notice }),
2902
2714
  error && /* @__PURE__ */ jsx10("p", { className: "text-sm text-text-danger", children: error })
@@ -2924,14 +2736,13 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2924
2736
  export {
2925
2737
  AgentBubble,
2926
2738
  AgentSteps,
2739
+ ArcadeIcon,
2927
2740
  ClawIcon,
2928
2741
  Composer,
2929
2742
  ConfirmCard,
2930
- ConnectingState,
2931
2743
  CopyUserMessage,
2932
2744
  DEFAULT_TOOL_ICONS,
2933
2745
  DecisionLines,
2934
- DeliveryChips,
2935
2746
  InlineMarkdown,
2936
2747
  LinkSafetyModal,
2937
2748
  Markdown,
@@ -2940,10 +2751,9 @@ export {
2940
2751
  PilotHttpError,
2941
2752
  Piloto,
2942
2753
  PilotoContext,
2943
- RESTART_NOTICE,
2944
- RESUME_TEXT,
2945
2754
  ReferenceChips,
2946
2755
  ReplyOptionButtons,
2756
+ RobotArmIcon,
2947
2757
  STRINGS_EN,
2948
2758
  STRINGS_ES,
2949
2759
  Suggestions,
@@ -2955,15 +2765,13 @@ export {
2955
2765
  UsageStats,
2956
2766
  UserBubble,
2957
2767
  WaitLabel,
2958
- appendAgentMessages,
2959
- applyHistory,
2960
2768
  applyReferences,
2769
+ argsDetail,
2961
2770
  beginSend,
2962
2771
  cleanReplyOption,
2963
2772
  cleanUserText,
2773
+ createAgUiTransport,
2964
2774
  createPilotState,
2965
- createPostStreamTransport,
2966
- createSseTransport,
2967
2775
  Piloto as default,
2968
2776
  dropLastExchange,
2969
2777
  expiryMs,
@@ -2973,26 +2781,24 @@ export {
2973
2781
  isNarration,
2974
2782
  isRunning,
2975
2783
  lastUserTurn,
2784
+ messagesToTurns,
2976
2785
  mimeOf,
2977
2786
  narrationToMarkdown,
2978
2787
  readSse,
2979
- reconcileTurns,
2980
- reconcileWith,
2981
- reducePilotEvent,
2788
+ reduceAgUiEvent,
2982
2789
  renderInline,
2983
2790
  resolvePermission,
2984
- resumeMode,
2985
- sendAccepted,
2986
2791
  sendFailed,
2987
- sendRejectedBusy,
2988
2792
  splitNarration,
2989
2793
  stepLabel,
2990
2794
  stopTurn,
2991
2795
  summarizeTools,
2992
2796
  systemNoteOf,
2797
+ toAgUiContext,
2798
+ toPermission,
2993
2799
  toolName,
2800
+ toolNames,
2994
2801
  usePilot,
2995
2802
  usePiloto,
2996
- useSseTransport,
2997
2803
  useTextAtPace
2998
2804
  };