ghostykit 0.4.0 → 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.
@@ -36,7 +36,7 @@ function upsertTool(s, id, patch) {
36
36
  }
37
37
  var APPROVE = { optionId: "approve", name: "Aprobar", kind: "allow_once" };
38
38
  var REJECT = { optionId: "reject", name: "Rechazar", kind: "reject_once" };
39
- function toPermission(i, turns) {
39
+ function toPermission(i, turns, clockOffset = 0) {
40
40
  const meta = i.metadata ?? {};
41
41
  const toolName2 = (typeof meta.tool === "string" ? meta.tool : void 0) ?? turns.flatMap((t) => t.tools ?? []).find((x) => x.id === i.toolCallId)?.title;
42
42
  return {
@@ -46,9 +46,14 @@ function toPermission(i, turns) {
46
46
  ...i.toolCallId ? { toolCallId: i.toolCallId } : {},
47
47
  reason: i.reason,
48
48
  options: [REJECT, APPROVE],
49
- ...i.expiresAt ? { expiresAt: i.expiresAt } : {}
49
+ ...i.expiresAt ? { expiresAt: i.expiresAt, deadline: expiryMs(i.expiresAt) + clockOffset } : {}
50
50
  };
51
51
  }
52
+ function closeOpenTools(turns, status) {
53
+ return turns.map(
54
+ (t) => t.tools?.some((x) => x.status === "in_progress") ? { ...t, tools: t.tools.map((x) => x.status === "in_progress" ? { ...x, status } : x) } : t
55
+ );
56
+ }
52
57
  function textOf(content) {
53
58
  if (typeof content === "string") return content;
54
59
  if (Array.isArray(content)) return content.map((p) => p && typeof p === "object" && p.type === "text" ? p.text ?? "" : "").join("");
@@ -175,7 +180,9 @@ function finish(s0, e, now, failed = false) {
175
180
  let s = { ...s0, busy: false };
176
181
  const outcome = e.outcome;
177
182
  const interrupted = outcome?.type === "interrupt" && Array.isArray(outcome.interrupts) && outcome.interrupts.length > 0;
178
- s.permissions = interrupted ? outcome.interrupts.map((i) => toPermission(i, s.turns)) : [];
183
+ const clockOffset = typeof e.timestamp === "number" ? now - e.timestamp : 0;
184
+ s.permissions = interrupted ? outcome.interrupts.map((i) => toPermission(i, s.turns, clockOffset)) : [];
185
+ if (outcome?.type === "cancelled" || failed) s = { ...s, turns: closeOpenTools(s.turns, failed ? "failed" : "cancelled") };
179
186
  if (interrupted) {
180
187
  const waiting = new Set(s.permissions.map((p) => p.toolCallId).filter(Boolean));
181
188
  if (waiting.size) {
@@ -211,7 +218,7 @@ function sendFailed(prev, message) {
211
218
  return { ...prev, busy: false, turnStartedAt: 0, error: message, effects: [] };
212
219
  }
213
220
  function stopTurn(prev) {
214
- return { ...prev, busy: false, turnStartedAt: 0, effects: [] };
221
+ return { ...prev, busy: false, turnStartedAt: 0, turns: closeOpenTools(prev.turns, "cancelled"), effects: [] };
215
222
  }
216
223
  function dropLastExchange(prev) {
217
224
  const turns = [...prev.turns];
@@ -311,7 +318,7 @@ function cleanReplyOption(t) {
311
318
  }
312
319
 
313
320
  // src/core/steps.ts
314
- var isRunning = (t) => t.status !== "completed" && t.status !== "failed";
321
+ var isRunning = (t) => t.status !== "completed" && t.status !== "failed" && t.status !== "cancelled";
315
322
  function toolNames(t) {
316
323
  const title = t.title ?? "";
317
324
  if (!title) return [];
@@ -65,10 +65,14 @@ function createAgUiTransport(opts) {
65
65
  throw new PilotHttpError(json?.message ?? json?.error ?? `Error ${res.status}`, res.status, json?.error, retryAfter);
66
66
  }
67
67
  if (!res.body) return;
68
+ const serverDate = Date.parse(res.headers.get("date") ?? "");
69
+ const skew = Number.isNaN(serverDate) ? null : serverDate - Date.now();
68
70
  await readSse(res.body, ({ data }) => {
69
71
  if (!data) return;
70
72
  try {
71
- onEvent(JSON.parse(data));
73
+ const event = JSON.parse(data);
74
+ if (skew !== null && typeof event.timestamp !== "number") event.timestamp = Date.now() + skew;
75
+ onEvent(event);
72
76
  } catch {
73
77
  }
74
78
  });
@@ -24,7 +24,7 @@ import {
24
24
  toPermission,
25
25
  toolName,
26
26
  toolNames
27
- } from "../chunks/chunk-HPTM2BCM.js";
27
+ } from "../chunks/chunk-JQEB37IY.js";
28
28
  import {
29
29
  applyReferences,
30
30
  cleanUserText,
@@ -8,7 +8,7 @@ type Ev = {
8
8
  /** Los argumentos de una tool en corto, para el renglón del paso: «job: purge-notifications». */
9
9
  export declare function argsDetail(json?: string): string | undefined;
10
10
  /** Un `Interrupt` de AG-UI como tarjeta: título, herramienta (por su toolCallId) y vencimiento. */
11
- export declare function toPermission(i: Interrupt, turns: Turn[]): PermissionRequest;
11
+ export declare function toPermission(i: Interrupt, turns: Turn[], clockOffset?: number): PermissionRequest;
12
12
  /**
13
13
  * El hilo guardado (mensajes AG-UI) como turnos: cada mensaje del usuario abre uno; lo que sigue del
14
14
  * asistente y de las herramientas se junta en un turno, con sus pasos y su narración.
@@ -15,7 +15,8 @@ export interface ToolEntry {
15
15
  /** El nombre de la herramienta (`toolCallName`): es la llave de los verbos de la app. */
16
16
  title?: string;
17
17
  kind?: string;
18
- /** `in_progress` mientras corre; `completed` | `failed` con su resultado; `pending` = espera aprobación. */
18
+ /** `in_progress` mientras corre; `completed` | `failed` con su resultado; `pending` = espera
19
+ * aprobación; `cancelled` = el run se detuvo antes de que terminara. */
19
20
  status?: string;
20
21
  path?: string;
21
22
  /** Los argumentos, en corto («job: purge-notifications»). */
@@ -67,8 +68,14 @@ export interface PermissionRequest {
67
68
  toolCallId?: string;
68
69
  reason?: string;
69
70
  options: PermissionOption[];
70
- /** Hasta cuándo se puede contestar (ISO). */
71
+ /** Hasta cuándo se puede contestar (ISO, reloj del SERVIDOR). */
71
72
  expiresAt?: string | number;
73
+ /**
74
+ * El mismo vencimiento en el reloj de ESTE navegador (epoch ms): `expiresAt` menos la hora del
75
+ * servidor al mandar el evento, más la hora local al recibirlo. Así un reloj local desfasado no
76
+ * adelanta ni atrasa la cuenta regresiva.
77
+ */
78
+ deadline?: number;
72
79
  }
73
80
  /** Lo que una transición pide hacer FUERA del estado (avisos a la app). */
74
81
  export type PilotEffect = {
@@ -24,12 +24,12 @@ import {
24
24
  toPermission,
25
25
  toolName,
26
26
  toolNames
27
- } from "../chunks/chunk-HPTM2BCM.js";
27
+ } from "../chunks/chunk-JQEB37IY.js";
28
28
  import {
29
29
  PilotHttpError,
30
30
  createAgUiTransport,
31
31
  readSse
32
- } from "../chunks/chunk-DXFWACUL.js";
32
+ } from "../chunks/chunk-TMEHQZWL.js";
33
33
  import {
34
34
  applyReferences,
35
35
  cleanUserText,
@@ -1009,7 +1009,7 @@ function ConfirmCard({
1009
1009
  const { strings: t } = usePiloto();
1010
1010
  const [pending, setPending] = useState(null);
1011
1011
  const [stamp, setStamp] = useState(null);
1012
- const time = useRemaining(request.expiresAt);
1012
+ const time = useRemaining(request.deadline ?? request.expiresAt);
1013
1013
  const expired = time !== null && time.left <= 0;
1014
1014
  const urgent = time !== null && !expired && time.left < 6e4;
1015
1015
  useEffect(() => {
@@ -1448,7 +1448,10 @@ function StepLine({ tool, times, live }) {
1448
1448
  const spins = isRunning(tool) && live;
1449
1449
  const label = stepLabel(tool, toolVerbs, t.step);
1450
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: [
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" ? (
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" ? (
1452
1455
  // Espera aprobación (el run se detuvo en un interrupt): ni gira ni está lista.
1453
1456
  /* @__PURE__ */ jsx5(Clock, { className: "h-4 w-4 text-text-tertiary" })
1454
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" }) }),
@@ -1470,6 +1473,7 @@ var STATUS = {
1470
1473
  pending: { Icon: Clock, className: "text-text-tertiary", key: "step" },
1471
1474
  in_progress: { Icon: LoaderCircle, className: "animate-spin text-text-info", key: "running" },
1472
1475
  completed: { Icon: CircleCheck, className: "text-text-success", key: "done" },
1476
+ cancelled: { Icon: CircleSlash, className: "text-text-tertiary", key: "step" },
1473
1477
  failed: { Icon: CircleX, className: "text-text-danger", key: "failed" }
1474
1478
  };
1475
1479
  function ToolRow({ tool }) {
@@ -2,7 +2,7 @@ import {
2
2
  PilotHttpError,
3
3
  createAgUiTransport,
4
4
  readSse
5
- } from "../chunks/chunk-DXFWACUL.js";
5
+ } from "../chunks/chunk-TMEHQZWL.js";
6
6
  export {
7
7
  PilotHttpError,
8
8
  createAgUiTransport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostykit",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Ghosty Autopilot \u2014 the autopilot chat for your React app, on AG-UI: works with LangGraph, Mastra, CrewAI or any AG-UI agent. Streaming text, agent steps, human approval cards (interrupt/resume), attachments and voice.",
5
5
  "type": "module",
6
6
  "main": "./dist/core/index.js",