ghostykit 0.6.2 → 0.6.3

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.
@@ -116,7 +116,8 @@ function createPilotState(turns = []) {
116
116
  effects: []
117
117
  };
118
118
  }
119
- var NOT_IN_TURN = /* @__PURE__ */ new Set(["ghosty.title", "ghosty.injected", "ghosty.history", "ghosty.tool", "ghosty.replace", "ghosty.page"]);
119
+ var NOT_IN_TURN = /* @__PURE__ */ new Set(["ghosty.title", "ghosty.injected", "ghosty.history", "ghosty.tool", "ghosty.replace", "ghosty.page", "ghosty.stalled"]);
120
+ var PROGRESS = /* @__PURE__ */ new Set(["TEXT_MESSAGE_START", "TEXT_MESSAGE_CONTENT", "TEXT_MESSAGE_CHUNK", "TOOL_CALL_START", "TOOL_CALL_ARGS", "TOOL_CALL_CHUNK", "TOOL_CALL_RESULT", "REASONING_MESSAGE_CONTENT", "REASONING_MESSAGE_CHUNK", "RUN_STARTED"]);
120
121
  function patchAssistant(s, patch) {
121
122
  const turns = [...s.turns];
122
123
  const last = turns[turns.length - 1];
@@ -283,7 +284,8 @@ function applySnapshot(s, messages) {
283
284
  };
284
285
  }
285
286
  function reduceAgUiEvent(prev, e, now = Date.now()) {
286
- const s = prev.effects.length ? { ...prev, effects: [] } : prev;
287
+ let s = prev.effects.length ? { ...prev, effects: [] } : prev;
288
+ if (s.stalled && PROGRESS.has(e.type)) s = { ...s, stalled: null };
287
289
  switch (e.type) {
288
290
  case "RUN_STARTED":
289
291
  return { ...s, busy: true, error: null, step: null, runId: e.runId ?? s.runId };
@@ -355,6 +357,10 @@ function reduceAgUiEvent(prev, e, now = Date.now()) {
355
357
  case "CUSTOM":
356
358
  if (e.name === "ghosty.title") return { ...s, effects: [{ type: "title", title: e.value?.title }] };
357
359
  if (e.name === "ghosty.history") return { ...s, historyNext: toHistory(e.value) };
360
+ if (e.name === "ghosty.stalled") {
361
+ const message = e.value?.message;
362
+ return { ...s, stalled: typeof message === "string" && message ? { message } : {} };
363
+ }
358
364
  if (e.name === "ghosty.tool") {
359
365
  const v = e.value ?? {};
360
366
  if (typeof v.toolCallId !== "string") return s;
@@ -390,7 +396,7 @@ function toHistory(v) {
390
396
  return { hasMoreBefore: h.hasMoreBefore === true, ...before !== void 0 ? { before } : {} };
391
397
  }
392
398
  function finish(s0, e, now, failed = false) {
393
- let s = { ...s0, busy: false, step: null, openText: null };
399
+ let s = { ...s0, busy: false, step: null, openText: null, stalled: null };
394
400
  const outcome = e.outcome;
395
401
  const interrupted = outcome?.type === "interrupt" && Array.isArray(outcome.interrupts) && outcome.interrupts.length > 0;
396
402
  const clockOffset = typeof e.timestamp === "number" ? now - e.timestamp : 0;
@@ -414,7 +420,9 @@ function finish(s0, e, now, failed = false) {
414
420
  const seconds = t0 ? Math.max(1, Math.round((now - t0) / 1e3)) : void 0;
415
421
  const turns = [...s.turns];
416
422
  const last = turns[turns.length - 1];
417
- if (last?.role === "assistant") turns[turns.length - 1] = { ...last, ...seconds !== void 0 ? { seconds } : {}, ...usage ? { usage } : {} };
423
+ const tr = e.metadata?.truncated;
424
+ const truncated = tr ? { ...typeof tr.reason === "string" ? { reason: tr.reason } : {} } : void 0;
425
+ if (last?.role === "assistant") turns[turns.length - 1] = { ...last, ...seconds !== void 0 ? { seconds } : {}, ...usage ? { usage } : {}, ...truncated ? { truncated } : {} };
418
426
  s = { ...s, turns, turnStartedAt: 0, ...usage ? { usage } : {} };
419
427
  if (!t0) return s;
420
428
  const effect = {
@@ -31,7 +31,7 @@ import {
31
31
  toPermission,
32
32
  toolName,
33
33
  toolNames
34
- } from "../chunks/chunk-ZJ34IR2Z.js";
34
+ } from "../chunks/chunk-LFFJLMDO.js";
35
35
  import {
36
36
  applyReferences,
37
37
  cleanUserText,
@@ -95,6 +95,10 @@ export interface Turn {
95
95
  usage?: Usage;
96
96
  /** Lo que el usuario decidió en las tarjetas de este turno (sólo en vivo). */
97
97
  decisions?: Decision[];
98
+ /** El servidor recortó la respuesta (`RUN_FINISHED.metadata.truncated`); `reason` si la dio. */
99
+ truncated?: {
100
+ reason?: string;
101
+ };
98
102
  /** Los `CUSTOM` del turno (p. ej. `ghosty.delivery`), para `renderDeliveries`. Del snapshot salen
99
103
  * del campo `custom` de cada mensaje del asistente. */
100
104
  custom?: CustomEntry[];
@@ -171,6 +175,10 @@ export interface PilotState {
171
175
  runId: string;
172
176
  reason?: string;
173
177
  } | null;
178
+ /** El agente se atoró y el servidor sigue intentando (`CUSTOM ghosty.stalled`); se quita con lo siguiente que llegue. */
179
+ stalled?: {
180
+ message?: string;
181
+ } | null;
174
182
  /** Mensajes en espera de que termine el run vivo (steer rechazado). */
175
183
  queued: QueuedMessage[];
176
184
  /** Lo que estaba en espera cuando la persona detuvo: regresa a la caja (`n` cambia en cada vez). */
@@ -31,7 +31,7 @@ import {
31
31
  toPermission,
32
32
  toolName,
33
33
  toolNames
34
- } from "../chunks/chunk-ZJ34IR2Z.js";
34
+ } from "../chunks/chunk-LFFJLMDO.js";
35
35
  import {
36
36
  PilotHttpError,
37
37
  createAgUiTransport,
@@ -896,6 +896,9 @@ var STRINGS_ES = {
896
896
  loadingOlder: "Cargando\u2026",
897
897
  continueRun: "Continuar",
898
898
  runCut: "La respuesta se cort\xF3 antes de terminar.",
899
+ stalled: "Se ator\xF3, sigo intentando\u2026",
900
+ truncated: "La respuesta qued\xF3 incompleta.",
901
+ attachmentFailed: (name, reason) => reason ? `No pude leer ${name}: ${reason}` : `No pude leer ${name}`,
899
902
  queued: "Se manda cuando termine",
900
903
  subagentsWorking: (n) => n === 1 ? "1 agente trabajando" : `${n} agentes trabajando`,
901
904
  subagentsDone: (n) => n === 1 ? "1 agente termin\xF3" : `${n} agentes terminaron`,
@@ -971,6 +974,9 @@ var STRINGS_EN = {
971
974
  loadingOlder: "Loading\u2026",
972
975
  continueRun: "Continue",
973
976
  runCut: "The answer was cut off before it finished.",
977
+ stalled: "Got stuck, still trying\u2026",
978
+ truncated: "The answer is incomplete.",
979
+ attachmentFailed: (name, reason) => reason ? `Couldn't read ${name}: ${reason}` : `Couldn't read ${name}`,
974
980
  queued: "Sends when this finishes",
975
981
  subagentsWorking: (n) => n === 1 ? "1 agent working" : `${n} agents working`,
976
982
  subagentsDone: (n) => n === 1 ? "1 agent finished" : `${n} agents finished`,
@@ -2388,7 +2394,7 @@ function usePilot(transport, opts = {}) {
2388
2394
  ctrl.current?.abort();
2389
2395
  const c = new AbortController();
2390
2396
  ctrl.current = c;
2391
- const steering = typeof input.forwardedProps?.steer === "string";
2397
+ const steering = input.forwardedProps?.steer !== void 0 && input.forwardedProps?.steer !== false;
2392
2398
  const userMsg = input.messages[0];
2393
2399
  if (!steering && (input.messages.length || input.resume?.length || input.forwardedProps?.continueRun)) liveRun.current = input.runId;
2394
2400
  optsRef.current.onSend?.(input);
@@ -2410,6 +2416,18 @@ function usePilot(transport, opts = {}) {
2410
2416
  } catch (err) {
2411
2417
  if (c.signal.aborted) return;
2412
2418
  const code = err instanceof PilotHttpError ? err.code : void 0;
2419
+ if (code === "run_not_live" && steering && userMsg) {
2420
+ const { text } = fromUserMessage(userMsg.content);
2421
+ let s = ref.current;
2422
+ for (let i = s.turns.length - 1; i >= 0; i--) {
2423
+ if (s.turns[i].role === "user" && s.turns[i].text === text) {
2424
+ s = { ...s, turns: [...s.turns.slice(0, i), ...s.turns.slice(i + 1)] };
2425
+ break;
2426
+ }
2427
+ }
2428
+ commit({ ...s, busy: false, turnStartedAt: 0, effects: [] });
2429
+ return sendNew(text, []);
2430
+ }
2413
2431
  if (code === "nothing_to_continue") {
2414
2432
  liveRun.current = null;
2415
2433
  commit({ ...ref.current, busy: false, turnStartedAt: 0, cutRun: null, effects: [] });
@@ -2461,8 +2479,8 @@ function usePilot(transport, opts = {}) {
2461
2479
  async (text, attachments = [], _extra = {}) => {
2462
2480
  if (!transportRef.current) return;
2463
2481
  if (ref.current.busy) {
2464
- const live = liveRun.current ?? ref.current.runId;
2465
- if (!steerOn() || !live || !text.trim() || attachments.length) return;
2482
+ const live = liveRun.current ?? ref.current.runId ?? true;
2483
+ if (!steerOn() || !text.trim() || attachments.length) return;
2466
2484
  commit(beginSteer(ref.current, text));
2467
2485
  await runNow({ messages: [userMessage(text, [])], forwardedProps: { steer: live } });
2468
2486
  return;
@@ -2864,20 +2882,24 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2864
2882
  turns.map((turn, i) => {
2865
2883
  const isLast = i === turns.length - 1;
2866
2884
  const live = busy && isLast;
2867
- return /* @__PURE__ */ jsx10("div", { "data-turn": i - olderCount, children: turn.role === "user" ? /* @__PURE__ */ jsx10(UserBubble, { turn }) : /* @__PURE__ */ jsx10(
2868
- AgentBubble,
2869
- {
2870
- turn,
2871
- live,
2872
- onRetry: isLast && features.retry !== false ? pilot.retry : void 0,
2873
- onOption: isLast && !busy && features.replyOptions !== false ? (text) => send(text) : void 0,
2874
- showUsage: features.usage !== false,
2875
- showCopy: features.copy !== false,
2876
- showDuration: features.duration !== false,
2877
- renderDeliveries: slots.renderDeliveries
2878
- }
2879
- ) }, i - olderCount);
2885
+ return /* @__PURE__ */ jsxs8("div", { "data-turn": i - olderCount, children: [
2886
+ turn.role === "user" ? /* @__PURE__ */ jsx10(UserBubble, { turn }) : /* @__PURE__ */ jsx10(
2887
+ AgentBubble,
2888
+ {
2889
+ turn,
2890
+ live,
2891
+ onRetry: isLast && features.retry !== false ? pilot.retry : void 0,
2892
+ onOption: isLast && !busy && features.replyOptions !== false ? (text) => send(text) : void 0,
2893
+ showUsage: features.usage !== false,
2894
+ showCopy: features.copy !== false,
2895
+ showDuration: features.duration !== false,
2896
+ renderDeliveries: slots.renderDeliveries
2897
+ }
2898
+ ),
2899
+ turn.role === "assistant" && /* @__PURE__ */ jsx10(TurnNotes, { turn })
2900
+ ] }, i - olderCount);
2880
2901
  }),
2902
+ busy && state.stalled && /* @__PURE__ */ jsx10("p", { className: "text-sm text-text-tertiary", children: state.stalled.message ?? t.stalled }),
2881
2903
  state.queued.map((q, i) => (
2882
2904
  // Esperan a que termine el run vivo (el servidor no los dejó entrar): salen solos.
2883
2905
  /* @__PURE__ */ jsxs8("div", { "data-queued": true, className: "flex flex-col items-end gap-1 opacity-60", children: [
@@ -2926,6 +2948,15 @@ var Piloto = forwardRef3(function Piloto2(props, handle) {
2926
2948
  ] })
2927
2949
  ] })) }) });
2928
2950
  });
2951
+ function TurnNotes({ turn }) {
2952
+ const { strings: t } = usePiloto();
2953
+ const failed = (turn.custom ?? []).filter((c) => c.name === "ghosty.attachments_failed").flatMap((c) => (c.value?.files ?? []).filter((f) => f?.name));
2954
+ if (!failed.length && !turn.truncated) return null;
2955
+ return /* @__PURE__ */ jsxs8("div", { "data-turn-notes": true, className: "mt-1 flex flex-col gap-0.5 text-xs text-text-tertiary", children: [
2956
+ failed.map((f, i) => /* @__PURE__ */ jsx10("p", { children: t.attachmentFailed(f.name, f.reason) }, i)),
2957
+ turn.truncated && /* @__PURE__ */ jsx10("p", { children: turn.truncated.reason ?? t.truncated })
2958
+ ] });
2959
+ }
2929
2960
 
2930
2961
  // src/react/Subagents.tsx
2931
2962
  import { useState as useState10 } from "react";
@@ -62,6 +62,12 @@ export interface PilotStrings {
62
62
  continueRun: string;
63
63
  /** La franja, si el servidor no dio razón. */
64
64
  runCut: string;
65
+ /** `ghosty.stalled` sin mensaje propio. */
66
+ stalled: string;
67
+ /** Bajo una respuesta recortada por el servidor. */
68
+ truncated: string;
69
+ /** Un adjunto que el agente no pudo leer: nombre y razón. */
70
+ attachmentFailed: (name: string, reason?: string) => string;
65
71
  /** Bajo un mensaje que espera a que termine el run vivo. */
66
72
  queued: string;
67
73
  /** «2 agentes trabajando». */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostykit",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
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",