ghostykit 0.6.4 → 0.7.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.
@@ -0,0 +1,144 @@
1
+ // src/core/steps.ts
2
+ var isRunning = (t) => t.status !== "completed" && t.status !== "failed" && t.status !== "cancelled";
3
+ function toolNames(t) {
4
+ const title = t.title ?? "";
5
+ if (!title) return [];
6
+ const out = [title];
7
+ const double = /^mcp__.+?__(.+)$/i.exec(title);
8
+ if (double) out.push(double[1]);
9
+ else if (/^mcp_/i.test(title)) {
10
+ const parts = title.slice(4).split("_");
11
+ for (let i = 0; i < parts.length; i++) out.push(parts.slice(i).join("_"));
12
+ }
13
+ return [...new Set(out)];
14
+ }
15
+ function toolName(t) {
16
+ const names = toolNames(t);
17
+ return /^mcp__/i.test(names[0] ?? "") ? names[1] ?? names[0] : names[0] ?? null;
18
+ }
19
+ function namedVerb(t, verbs) {
20
+ for (const name of toolNames(t)) if (verbs[name]) return { key: name, verb: verbs[name] };
21
+ return null;
22
+ }
23
+ function stepLabel(t, verbs, fallback = "Paso") {
24
+ if (t.label) return t.label;
25
+ const named = namedVerb(t, verbs);
26
+ if (named) return isRunning(t) && named.verb.running ? named.verb.running : named.verb.one;
27
+ return t.title ?? t.kind ?? fallback;
28
+ }
29
+ function summarizeTools(tools, verbs) {
30
+ const groups = /* @__PURE__ */ new Map();
31
+ for (const t of tools) {
32
+ const named = namedVerb(t, verbs);
33
+ const key = named?.key ?? t.kind ?? "other";
34
+ const g = groups.get(key) ?? { n: 0, verb: named?.verb ?? verbs[key] };
35
+ g.n++;
36
+ groups.set(key, g);
37
+ }
38
+ const parts = [];
39
+ for (const [key, { n, verb }] of groups) {
40
+ if (verb) parts.push(n === 1 ? verb.one : verb.many.replace("%n", String(n)));
41
+ else parts.push(`${n} \xD7 ${key.replace(/[_-]/g, " ")}`);
42
+ }
43
+ return parts.join(" \xB7 ");
44
+ }
45
+ function groupSteps(tools, label) {
46
+ const groups = /* @__PURE__ */ new Map();
47
+ for (const t of tools) {
48
+ const k = `${label(t)}\0${t.detail ?? ""}`;
49
+ const g = groups.get(k);
50
+ if (!g) {
51
+ groups.set(k, { tool: t, times: 1 });
52
+ continue;
53
+ }
54
+ g.times++;
55
+ const wins = isRunning(t) || t.status === "failed" || !(isRunning(g.tool) || g.tool.status === "failed");
56
+ if (wins) g.tool = { ...t, id: g.tool.id };
57
+ }
58
+ return [...groups.values()];
59
+ }
60
+ var TOOL_VERBS_ES = {
61
+ read: { one: "Le\xED un archivo", many: "Le\xED %n archivos" },
62
+ read_file: { one: "Le\xED un archivo", many: "Le\xED %n archivos" },
63
+ write: { one: "Escrib\xED un archivo", many: "Escrib\xED %n archivos" },
64
+ write_file: { one: "Escrib\xED un archivo", many: "Escrib\xED %n archivos" },
65
+ edit: { one: "Edit\xE9 un archivo", many: "Edit\xE9 %n archivos" },
66
+ edit_file: { one: "Edit\xE9 un archivo", many: "Edit\xE9 %n archivos" },
67
+ bash: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
68
+ shell: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
69
+ terminal: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
70
+ execute: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
71
+ grep: { one: "Busqu\xE9 en el c\xF3digo", many: "Busqu\xE9 en el c\xF3digo" },
72
+ glob: { one: "Busqu\xE9 archivos", many: "Busqu\xE9 archivos" },
73
+ search: { one: "Busqu\xE9", many: "Busqu\xE9 %n veces" },
74
+ tool_search: { one: "Busqu\xE9 una herramienta", many: "Busqu\xE9 %n herramientas" },
75
+ web_search: { one: "Busqu\xE9 en la web", many: "Busqu\xE9 en la web" },
76
+ web_fetch: { one: "Abr\xED una p\xE1gina", many: "Abr\xED %n p\xE1ginas" },
77
+ fetch: { one: "Abr\xED una p\xE1gina", many: "Abr\xED %n p\xE1ginas" },
78
+ site_publish: { one: "Publiqu\xE9 una p\xE1gina", many: "Publiqu\xE9 %n p\xE1ginas" },
79
+ pdf: { one: "Gener\xE9 un PDF", many: "Gener\xE9 %n PDF" },
80
+ connector: { one: "Us\xE9 tu sistema", many: "Us\xE9 tu sistema %n veces" },
81
+ mcp: { one: "Us\xE9 una herramienta", many: "Us\xE9 %n herramientas" },
82
+ browser: { one: "Us\xE9 el navegador", many: "Us\xE9 el navegador" },
83
+ todo: { one: "Anot\xE9 el plan", many: "Anot\xE9 el plan" },
84
+ delete: { one: "Borr\xE9 un archivo", many: "Borr\xE9 %n archivos" },
85
+ move: { one: "Mov\xED un archivo", many: "Mov\xED %n archivos" },
86
+ think: { one: "Lo pens\xE9", many: "Lo pens\xE9" },
87
+ image: { one: "Cre\xE9 una imagen", many: "Cre\xE9 %n im\xE1genes" },
88
+ delegate: { one: "Delegu\xE9 una tarea", many: "Delegu\xE9 %n tareas" },
89
+ collect: { one: "Recog\xED un resultado", many: "Recog\xED %n resultados" },
90
+ status: { one: "Pregunt\xE9 c\xF3mo iba", many: "Pregunt\xE9 %n veces c\xF3mo iba" },
91
+ stop: { one: "Detuve una tarea", many: "Detuve %n tareas" },
92
+ guide: { one: "Le\xED una gu\xEDa", many: "Le\xED %n gu\xEDas" },
93
+ deliver: { one: "Entregu\xE9 un archivo", many: "Entregu\xE9 %n archivos" },
94
+ sheet: { one: "Consult\xE9 una hoja", many: "Consult\xE9 %n hojas" },
95
+ view: { one: "Arm\xE9 una vista", many: "Arm\xE9 %n vistas" }
96
+ };
97
+ var TOOL_VERBS_EN = {
98
+ read: { one: "Read a file", many: "Read %n files" },
99
+ read_file: { one: "Read a file", many: "Read %n files" },
100
+ write: { one: "Wrote a file", many: "Wrote %n files" },
101
+ write_file: { one: "Wrote a file", many: "Wrote %n files" },
102
+ edit: { one: "Edited a file", many: "Edited %n files" },
103
+ edit_file: { one: "Edited a file", many: "Edited %n files" },
104
+ bash: { one: "Ran a command", many: "Ran %n commands" },
105
+ shell: { one: "Ran a command", many: "Ran %n commands" },
106
+ terminal: { one: "Ran a command", many: "Ran %n commands" },
107
+ execute: { one: "Ran a command", many: "Ran %n commands" },
108
+ grep: { one: "Searched the code", many: "Searched the code" },
109
+ glob: { one: "Searched for files", many: "Searched for files" },
110
+ search: { one: "Searched", many: "Searched %n times" },
111
+ tool_search: { one: "Looked for a tool", many: "Looked for %n tools" },
112
+ web_search: { one: "Searched the web", many: "Searched the web" },
113
+ web_fetch: { one: "Opened a page", many: "Opened %n pages" },
114
+ fetch: { one: "Opened a page", many: "Opened %n pages" },
115
+ site_publish: { one: "Published a page", many: "Published %n pages" },
116
+ pdf: { one: "Generated a PDF", many: "Generated %n PDFs" },
117
+ connector: { one: "Used your system", many: "Used your system %n times" },
118
+ mcp: { one: "Used a tool", many: "Used %n tools" },
119
+ browser: { one: "Used the browser", many: "Used the browser" },
120
+ todo: { one: "Wrote down the plan", many: "Wrote down the plan" },
121
+ delete: { one: "Deleted a file", many: "Deleted %n files" },
122
+ move: { one: "Moved a file", many: "Moved %n files" },
123
+ think: { one: "Thought it through", many: "Thought it through" },
124
+ image: { one: "Created an image", many: "Created %n images" },
125
+ delegate: { one: "Delegated a task", many: "Delegated %n tasks" },
126
+ collect: { one: "Collected a result", many: "Collected %n results" },
127
+ status: { one: "Checked how it was going", many: "Checked %n times how it was going" },
128
+ stop: { one: "Stopped a task", many: "Stopped %n tasks" },
129
+ guide: { one: "Read a guide", many: "Read %n guides" },
130
+ deliver: { one: "Delivered a file", many: "Delivered %n files" },
131
+ sheet: { one: "Checked a sheet", many: "Checked %n sheets" },
132
+ view: { one: "Built a view", many: "Built %n views" }
133
+ };
134
+
135
+ export {
136
+ isRunning,
137
+ toolNames,
138
+ toolName,
139
+ stepLabel,
140
+ summarizeTools,
141
+ groupSteps,
142
+ TOOL_VERBS_ES,
143
+ TOOL_VERBS_EN
144
+ };
@@ -1,7 +1,7 @@
1
1
  export * from "./types";
2
2
  export { argsDetail, beginSend, beginSteer, createPilotState, dropLastExchange, expiryMs, lastUserTurn, markSubagent, messagesToTurns, prependHistory, queueMessage, reduceAgUiEvent, resolvePermission, sendFailed, stopTurn, subagentsOf, takeQueued, toPermission, } from "./reducer";
3
3
  export { applyJsonPatch, type PatchOp } from "./jsonPatch";
4
- export { isNarration, narrationToMarkdown, splitNarration } from "./narration";
4
+ export { extractBlocks, isNarration, narrationToMarkdown, splitNarration, type TextBlock } from "./narration";
5
5
  export { cleanReplyOption, extractReplyOptions, type ReplyOptions } from "./options";
6
6
  export { TOOL_VERBS_EN, TOOL_VERBS_ES, groupSteps, isRunning, stepLabel, summarizeTools, toolName, toolNames, type ToolVerb, type ToolVerbs, } from "./steps";
7
7
  export { applyReferences, cleanUserText, formatDuration, systemNoteOf } from "./text";
@@ -1,6 +1,4 @@
1
1
  import {
2
- TOOL_VERBS_EN,
3
- TOOL_VERBS_ES,
4
2
  applyJsonPatch,
5
3
  argsDetail,
6
4
  beginSend,
@@ -9,10 +7,9 @@ import {
9
7
  createPilotState,
10
8
  dropLastExchange,
11
9
  expiryMs,
10
+ extractBlocks,
12
11
  extractReplyOptions,
13
- groupSteps,
14
12
  isNarration,
15
- isRunning,
16
13
  lastUserTurn,
17
14
  markSubagent,
18
15
  messagesToTurns,
@@ -23,15 +20,21 @@ import {
23
20
  resolvePermission,
24
21
  sendFailed,
25
22
  splitNarration,
26
- stepLabel,
27
23
  stopTurn,
28
24
  subagentsOf,
29
- summarizeTools,
30
25
  takeQueued,
31
- toPermission,
26
+ toPermission
27
+ } from "../chunks/chunk-7M2B7VKH.js";
28
+ import {
29
+ TOOL_VERBS_EN,
30
+ TOOL_VERBS_ES,
31
+ groupSteps,
32
+ isRunning,
33
+ stepLabel,
34
+ summarizeTools,
32
35
  toolName,
33
36
  toolNames
34
- } from "../chunks/chunk-LPPND5ME.js";
37
+ } from "../chunks/chunk-UUH37KXA.js";
35
38
  import {
36
39
  applyReferences,
37
40
  cleanUserText,
@@ -51,6 +54,7 @@ export {
51
54
  createPilotState,
52
55
  dropLastExchange,
53
56
  expiryMs,
57
+ extractBlocks,
54
58
  extractReplyOptions,
55
59
  formatDuration,
56
60
  groupSteps,
@@ -9,9 +9,26 @@
9
9
  */
10
10
  /** Un tramo es «narración» si es corto y de un solo párrafo; si no, es texto normal. */
11
11
  export declare function isNarration(chunk: string): boolean;
12
- export declare function splitNarration(text: string, cuts?: number[]): {
12
+ export declare function splitNarration(text: string, cuts?: number[], closed?: boolean): {
13
13
  steps: string[];
14
14
  rest: string;
15
15
  };
16
16
  /** La copia que se guarda: narración como `- ✓ …` y luego la respuesta. */
17
17
  export declare function narrationToMarkdown(chunks: string[], answer: string): string;
18
+ /** Un bloque con nombre del texto (```eb-file …```) que una app pinta como tarjeta. */
19
+ export interface TextBlock {
20
+ lang: string;
21
+ body: string;
22
+ /** El bloque ya cerró (en vivo, uno a medio escribir llega sin cerrar). */
23
+ complete: boolean;
24
+ }
25
+ /**
26
+ * Saca del texto los bloques ```<lang>``` cuyos nombres se piden (sólo los que abren al inicio de un
27
+ * renglón) y recorre los cortes de la narración para que sigan cayendo donde caían. Uno sin cerrar
28
+ * (todavía se escribe) se saca hasta el final.
29
+ */
30
+ export declare function extractBlocks(text: string, langs: readonly string[], cuts?: number[]): {
31
+ text: string;
32
+ cuts?: number[];
33
+ blocks: TextBlock[];
34
+ };
@@ -159,6 +159,13 @@ export interface PilotState {
159
159
  /** Cuándo empezó el turno que ESTE cliente encargó (0 = ninguno). */
160
160
  turnStartedAt: number;
161
161
  runId: string | null;
162
+ /** El último rechazo HTTP del servidor (antes del stream): para avisos propios (p. ej. 403 + `x-needs-login`). Se limpia al mandar. */
163
+ httpError?: {
164
+ status: number;
165
+ code?: string;
166
+ message: string;
167
+ headers?: Record<string, string>;
168
+ } | null;
162
169
  /** El estado compartido del agente (`STATE_SNAPSHOT`, con `STATE_DELTA` aplicado encima). */
163
170
  agentState: Record<string, unknown>;
164
171
  /** El paso con nombre en curso (`STEP_STARTED`), p. ej. `waking`. */
@@ -0,0 +1,39 @@
1
+ import { type PilotoPlugin, type PluginCtx } from "../react/plugins";
2
+ export interface ModelPickerOptions {
3
+ /** La llave de `agentState` con `{current, options: [{value, name}]}`. Default: `model`. */
4
+ stateKey?: string;
5
+ /** Cambiar de modelo es API de la app (no un run): aquí la llamas. */
6
+ onChange: (value: string, ctx: PluginCtx) => void | Promise<void>;
7
+ }
8
+ /**
9
+ * El selector de modelo en la barra del composer, con lo que el agente anuncia en
10
+ * `agentState.model = {current, options}`.
11
+ */
12
+ export declare function modelPicker({ stateKey, onChange }: ModelPickerOptions): PilotoPlugin;
13
+ /** Una página que el agente arma en vivo (`CUSTOM ghosty.page`). */
14
+ export interface LivePage {
15
+ pageId: string;
16
+ title: string;
17
+ sections: {
18
+ id: string;
19
+ title?: string;
20
+ status: "pending" | "building" | "done" | "failed";
21
+ }[];
22
+ url: string | null;
23
+ error?: string;
24
+ }
25
+ export interface LivePageOptions {
26
+ /** Ancho del panel. Default: 320 px. */
27
+ width?: number;
28
+ }
29
+ /**
30
+ * El panel de la página en vivo: escucha `ghosty.page` (plan, secciones, lista, error) y pinta el
31
+ * avance a un lado del chat, con el enlace al terminar.
32
+ */
33
+ export declare function livePage({ width }?: LivePageOptions): PilotoPlugin;
34
+ export interface DevtoolsOptions {
35
+ /** Además de la lista de plugins, cada evento con quién lo atiende. Default: no. */
36
+ events?: boolean;
37
+ }
38
+ /** Imprime en consola los plugins montados, sus puntos y lo que atienden (y, si quieres, cada evento). */
39
+ export declare function pilotDevtools({ events }?: DevtoolsOptions): PilotoPlugin;
@@ -0,0 +1,129 @@
1
+ import {
2
+ ModelPicker,
3
+ definePlugin,
4
+ pluginPoints,
5
+ pluginRequires
6
+ } from "../chunks/chunk-PNU3LKWG.js";
7
+ import "../chunks/chunk-UUH37KXA.js";
8
+ import "../chunks/chunk-6U545WJG.js";
9
+
10
+ // src/plugins/index.tsx
11
+ import { useEffect, useState, useSyncExternalStore } from "react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ function modelPicker({ stateKey = "model", onChange }) {
14
+ function Picker({ ctx }) {
15
+ const model = ctx.agentState[stateKey];
16
+ const [chosen, setChosen] = useState(null);
17
+ useEffect(() => setChosen(null), [model?.current]);
18
+ if (!model?.options?.length) return null;
19
+ return /* @__PURE__ */ jsx(
20
+ ModelPicker,
21
+ {
22
+ models: model.options,
23
+ current: chosen ?? model.current ?? null,
24
+ onChange: (value) => {
25
+ setChosen(value);
26
+ void onChange(value, ctx);
27
+ }
28
+ }
29
+ );
30
+ }
31
+ return definePlugin({ name: "modelPicker", styles: "ghostykit", requires: { state: [stateKey] }, composer: { actionsEnd: Picker } });
32
+ }
33
+ function livePage({ width = 320 } = {}) {
34
+ let page = null;
35
+ const listeners = /* @__PURE__ */ new Set();
36
+ const set = (next) => {
37
+ page = next;
38
+ for (const l of listeners) l();
39
+ };
40
+ const store = {
41
+ subscribe: (l) => {
42
+ listeners.add(l);
43
+ return () => void listeners.delete(l);
44
+ },
45
+ get: () => page
46
+ };
47
+ const status = (id, s) => page && set({ ...page, sections: page.sections.map((x) => x.id === id ? { ...x, status: s } : x) });
48
+ function onPage(e) {
49
+ const v = e.value ?? {};
50
+ switch (v.event) {
51
+ case "page-plan":
52
+ return set({ pageId: v.pageId, title: v.title, url: null, sections: (v.sections ?? []).map((x) => ({ id: x.id, title: x.title, status: "pending" })) });
53
+ case "section-building":
54
+ return status(v.id, "building");
55
+ case "section":
56
+ return status(v.id, v.ok === false ? "failed" : "done");
57
+ case "page-ready":
58
+ return page && set({ ...page, title: v.title ?? page.title, url: v.url ?? null });
59
+ case "page-error":
60
+ return page && set({ ...page, error: v.message });
61
+ case "page-drop":
62
+ return page?.pageId === v.pageId && set(null);
63
+ }
64
+ }
65
+ function Panel({ ctx }) {
66
+ const p = useSyncExternalStore(store.subscribe, store.get, store.get);
67
+ if (!p) return null;
68
+ const done = p.sections.filter((x) => x.status === "done").length;
69
+ return /* @__PURE__ */ jsxs("aside", { style: { width }, className: "hidden shrink-0 flex-col gap-3 overflow-y-auto border-l border-border-primary bg-background-secondary p-4 text-sm md:flex", children: [
70
+ /* @__PURE__ */ jsxs("div", { children: [
71
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-text-primary", children: p.title }),
72
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-text-tertiary", children: [
73
+ done,
74
+ "/",
75
+ p.sections.length,
76
+ " ",
77
+ ctx.strings.numberLocale.startsWith("en") ? "sections" : "secciones"
78
+ ] })
79
+ ] }),
80
+ /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1.5", children: p.sections.map((x) => /* @__PURE__ */ jsxs("li", { "data-status": x.status, className: "flex items-center gap-2", children: [
81
+ /* @__PURE__ */ jsx(
82
+ "span",
83
+ {
84
+ className: `h-2 w-2 shrink-0 rounded-full ${x.status === "done" ? "bg-text-success" : x.status === "failed" ? "bg-text-danger" : x.status === "building" ? "animate-pulse bg-brand-500" : "bg-border-primary"}`
85
+ }
86
+ ),
87
+ /* @__PURE__ */ jsx("span", { className: "truncate text-text-secondary", children: x.title ?? x.id })
88
+ ] }, x.id)) }),
89
+ p.error && /* @__PURE__ */ jsx("p", { className: "text-xs text-text-danger", children: p.error }),
90
+ p.url && /* @__PURE__ */ jsx("a", { href: p.url, target: "_blank", rel: "noopener noreferrer", className: "self-start rounded-full bg-brand-500 px-3 py-1 text-white hover:bg-brand-600", children: ctx.strings.linkOpen })
91
+ ] });
92
+ }
93
+ return definePlugin({ name: "livePage", styles: "ghostykit", requires: { events: ["ghosty.page"] }, events: { "ghosty.page": onPage }, sidePanel: Panel });
94
+ }
95
+ function pilotDevtools({ events = false } = {}) {
96
+ function Devtools({ ctx }) {
97
+ const names = ctx.plugins.map((p) => p.name).join(",");
98
+ useEffect(() => {
99
+ console.groupCollapsed(`[ghostykit] ${ctx.plugins.length} plugins`);
100
+ console.table(
101
+ ctx.plugins.map((p) => {
102
+ const req = pluginRequires(p);
103
+ return { plugin: p.name, points: pluginPoints(p).join(" "), events: req.events.join(" "), state: req.state.join(" ") };
104
+ })
105
+ );
106
+ console.groupEnd();
107
+ }, [names]);
108
+ return null;
109
+ }
110
+ return definePlugin({
111
+ name: "pilotDevtools",
112
+ styles: "ghostykit",
113
+ aboveThread: Devtools,
114
+ ...events ? {
115
+ events: {
116
+ "*": (e, ctx) => {
117
+ const key = e.type === "CUSTOM" ? e.name : e.type;
118
+ const who = ctx.plugins.filter((p) => p.name !== "pilotDevtools" && (p.events?.[e.type] || key && (p.events?.[key] || p.message?.custom?.[key]))).map((p) => p.name);
119
+ console.debug(`[ghostykit] ${e.type}${e.type === "CUSTOM" ? ` ${e.name}` : ""}`, who.length ? `\u2192 ${who.join(", ")}` : "", e);
120
+ }
121
+ }
122
+ } : {}
123
+ });
124
+ }
125
+ export {
126
+ livePage,
127
+ modelPicker,
128
+ pilotDevtools
129
+ };
@@ -1,4 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
+ import { type TextBlock } from "../core/narration";
2
3
  import { type ReplyOptions } from "../core/options";
3
4
  import type { Turn, Usage } from "../core/types";
4
5
  export declare function UserBubble({ turn }: {
@@ -35,5 +36,18 @@ export interface AgentBubbleProps {
35
36
  showDuration?: boolean;
36
37
  /** Lo que la app quiera pintar bajo la respuesta (archivos, tarjetas propias). */
37
38
  renderDeliveries?: (turn: Turn, live: boolean) => ReactNode;
39
+ /** Vistas propias de las herramientas, justo bajo los pasos. */
40
+ renderTools?: (turn: Turn, live: boolean) => ReactNode;
41
+ /** Antes del texto de la respuesta, bajo sus pasos. */
42
+ renderBefore?: (turn: Turn, live: boolean) => ReactNode;
43
+ /** `false`: el texto antes de cada herramienta NO se vuelve paso con palomita (todo es respuesta). */
44
+ narration?: boolean;
45
+ /** El avatar del agente a la izquierda de la respuesta. */
46
+ avatar?: ReactNode;
47
+ /** Bloques con nombre (```eb-file```) que salen del texto y se pintan como tarjetas bajo la respuesta. */
48
+ blocks?: {
49
+ langs: string[];
50
+ render: (blocks: TextBlock[], turn: Turn, live: boolean) => ReactNode;
51
+ };
38
52
  }
39
- export declare function AgentBubble({ turn, live, onRetry, onOption, showUsage, showCopy, showDuration, renderDeliveries }: AgentBubbleProps): import("react").JSX.Element;
53
+ export declare function AgentBubble(props: AgentBubbleProps): import("react").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { type ReactNode } from "react";
1
+ import { type ReactNode, type Ref } from "react";
2
2
  import type { Attachment, Reference } from "../core/types";
3
3
  /** Un modelo que la app ofrece elegir (AG-UI no lo define: la app lo manda en `forwardedProps`). */
4
4
  export interface ModelOption {
@@ -10,8 +10,14 @@ export interface UploadResult {
10
10
  url: string;
11
11
  id?: string;
12
12
  }
13
+ /** Lo que se puede hacer con la caja desde afuera (`ctx.addAttachments`). */
14
+ export interface ComposerHandle {
15
+ addAttachments: (items: (File | Attachment)[]) => void;
16
+ }
13
17
  /** El tipo del archivo, deducido del nombre cuando el navegador no lo dice (pasa más de lo que parece). */
14
18
  export declare function mimeOf(file: File): string;
19
+ /** ¿Cabe en un `accept` («image/*,.pdf,application/json»)? */
20
+ export declare function accepts(accept: string, name: string, mime: string): boolean;
15
21
  export interface ComposerProps {
16
22
  onSubmit: (text: string, attachments: Attachment[]) => void;
17
23
  onStop?: () => void;
@@ -36,12 +42,18 @@ export interface ComposerProps {
36
42
  references?: Reference[];
37
43
  onRemoveReference?: (id: string) => void;
38
44
  onActivateReference?: (ref: Reference) => void;
39
- /** Botones propios junto a adjuntar (slot `composerExtras`). */
45
+ /** Botones propios entre la imagen y el clip. */
40
46
  extras?: ReactNode;
47
+ /** Botones propios después del clip. */
48
+ extrasEnd?: ReactNode;
49
+ /** Para agregar adjuntos desde afuera. */
50
+ handleRef?: Ref<ComposerHandle>;
51
+ /** Qué se puede adjuntar (como el `accept` de un input). Default: todo. */
52
+ accept?: string;
41
53
  maxFiles?: number;
42
54
  maxFileMb?: number;
43
55
  }
44
- export declare function Composer({ onSubmit, onStop, busy, canSteer, placeholder, initialValue, autoFocus, canSeeImages, attachments: withFiles, upload, transcribe, voiceNoteText, models, currentModel, onModelChange, references, onRemoveReference, onActivateReference, extras, maxFiles, maxFileMb, }: ComposerProps): import("react").JSX.Element;
56
+ export declare function Composer({ onSubmit, onStop, busy, canSteer, placeholder, initialValue, autoFocus, canSeeImages, attachments: withFiles, upload, transcribe, voiceNoteText, models, currentModel, onModelChange, references, onRemoveReference, onActivateReference, extras, extrasEnd, handleRef, accept, maxFiles, maxFileMb, }: ComposerProps): import("react").JSX.Element;
45
57
  /**
46
58
  * El selector de modelo. PORTADO de Ghosty (`ModelPicker.tsx`): dropdown discreto hacia arriba, sin
47
59
  * Radix; cierra con clic fuera y con Escape.
@@ -1,10 +1,8 @@
1
1
  import { type CSSProperties, type ReactNode } from "react";
2
- import { type ToolVerbs } from "../core/steps";
3
- import type { Attachment, Reference, Turn } from "../core/types";
2
+ import type { Reference } from "../core/types";
4
3
  import type { PilotTransport } from "../transport";
5
- import { type UploadResult } from "./Composer";
6
- import { type MarkdownOptions, type PilotIcon, type PilotTheme } from "./context";
7
- import { type PilotStrings } from "./strings";
4
+ import { type PilotTheme } from "./context";
5
+ import { type PilotoPlugin, type PluginCtx } from "./plugins";
8
6
  import { type Pilot, type UsePilotOptions } from "./usePilot";
9
7
  export interface PilotoFeatures {
10
8
  /** Botones de adjuntar. Default: sí si hay `upload`. */
@@ -21,20 +19,11 @@ export interface PilotoFeatures {
21
19
  duration?: boolean;
22
20
  /** Mandar con el run vivo (steer). Default: lo que diga el agente en `state.caps.steer`. */
23
21
  steer?: boolean;
24
- }
25
- export interface PilotoSlots {
26
- /** Lo que la app pinte bajo cada respuesta (archivos, tarjetas propias). */
27
- renderDeliveries?: (turn: Turn, live: boolean) => ReactNode;
28
- /** Botones propios en la barra del composer. */
29
- composerExtras?: ReactNode;
30
- /** Lo que se ve con el hilo vacío (en lugar del saludo de casa). */
31
- emptyState?: ReactNode;
32
- /** Arriba a la derecha, sobre el hilo (compartir, abrir en grande…). */
33
- headerActions?: ReactNode;
34
- /** Pegado arriba del composer (un aviso, un banner, `<Subagents pilot={pilot} />`). */
35
- aboveComposer?: ReactNode | ((pilot: Pilot) => ReactNode);
36
- /** Un panel junto al chat (subagentes, vista previa), con el estado del agente y el piloto. */
37
- sidePanel?: (agentState: Record<string, unknown>, pilot: Pilot) => ReactNode;
22
+ /**
23
+ * Lo que el agente dice ANTES de cada herramienta como pasos con palomita («Reviso tus crons.»).
24
+ * Default: sí. `false`: todo el texto es respuesta (los pasos siguen en su caja).
25
+ */
26
+ narration?: boolean;
38
27
  }
39
28
  export interface PilotoProps extends UsePilotOptions {
40
29
  transport: PilotTransport | null;
@@ -42,25 +31,16 @@ export interface PilotoProps extends UsePilotOptions {
42
31
  pilot?: Pilot;
43
32
  agentName?: string;
44
33
  locale?: "es" | "en";
45
- strings?: Partial<PilotStrings>;
46
34
  theme?: PilotTheme;
47
35
  /** La piel: `ghosty` (default), `easybits`, `neutral`, o el nombre de una propia (`[data-skin]`). */
48
36
  skin?: string;
49
37
  className?: string;
50
38
  style?: CSSProperties;
51
- toolVerbs?: ToolVerbs;
52
- toolIcons?: Record<string, PilotIcon>;
53
- /** El texto del usuario como se pinta (quita la fontanería que la app pega al prompt). */
54
- formatUserText?: (text: string) => string;
55
- markdown?: MarkdownOptions;
56
39
  /** Saludo del hilo vacío. */
57
40
  welcome?: ReactNode;
58
41
  avatar?: ReactNode;
59
42
  placeholder?: string;
60
43
  suggestions?: string[];
61
- upload?: (file: File) => Promise<UploadResult>;
62
- transcribe?: (audio: Blob) => Promise<string>;
63
- voiceNoteText?: (transcript: string) => string;
64
44
  features?: PilotoFeatures;
65
45
  /**
66
46
  * Una pregunta lista en la caja (sin mandarla) y el foco en ella: al abrir el piloto desde una
@@ -75,15 +55,16 @@ export interface PilotoProps extends UsePilotOptions {
75
55
  onReferencesChange?: (refs: Reference[]) => void;
76
56
  /** Clic en un chip de referencia (resaltar la tarjeta en la app). */
77
57
  onReferenceClick?: (ref: Reference) => void;
78
- slots?: PilotoSlots;
79
- }
80
- /** Lo que la app puede hacer desde afuera (`<Piloto ref={…} />`). */
81
- export interface PilotoHandle {
82
- send: (text: string, attachments?: Attachment[]) => void;
83
- addReference: (ref: Reference) => void;
84
- /** Escribe en la caja sin mandar. */
85
- setDraft: (text: string) => void;
86
- focus: () => void;
58
+ /**
59
+ * La ÚNICA forma de personalizar: textos, pasos, eventos, entregas, composer y paneles (ver
60
+ * `PilotoPlugin`). Créalos una vez (fuera del render o con `useMemo`).
61
+ */
62
+ plugins?: readonly PilotoPlugin[];
87
63
  }
64
+ /**
65
+ * Lo que la app puede hacer desde afuera (`<Piloto ref={…} />`): lo MISMO que recibe un plugin en
66
+ * `ctx`. Con `pilot` (estado, `reconnect`, `stop`, `continueRun`…) nadie necesita `usePilot` aparte.
67
+ */
68
+ export type PilotoHandle = Omit<PluginCtx, "plugins" | "strings" | "agentState">;
88
69
  export declare const Piloto: import("react").ForwardRefExoticComponent<PilotoProps & import("react").RefAttributes<PilotoHandle>>;
89
70
  export default Piloto;
@@ -1,5 +1,6 @@
1
1
  import { type ComponentType } from "react";
2
2
  import { type ToolVerbs } from "../core/steps";
3
+ import type { Turn } from "../core/types";
3
4
  import { type PilotStrings } from "./strings";
4
5
  /** Un ícono: cualquier componente que acepte `className` (los de lucide-react sirven tal cual). */
5
6
  export type PilotIcon = ComponentType<{
@@ -9,7 +10,9 @@ export type PilotTheme = "light" | "dark" | "auto";
9
10
  /** Ajustes del markdown de las respuestas. */
10
11
  export interface MarkdownOptions {
11
12
  /** Reescribe el markdown antes de pintarlo (p. ej. `#12` → `[#12](#folio-12)`). */
12
- transform?: (md: string) => string;
13
+ transform?: (md: string, turn?: Turn) => string;
14
+ /** Cómo se pinta un elemento del markdown, por etiqueta (`a`, `code`…). */
15
+ components?: Record<string, ComponentType<any>>;
13
16
  /** Un clic en un enlace. Devuelve `true` si la app lo manejó (no se abre el aviso de salida). */
14
17
  onLinkClick?: (href: string, event: MouseEvent) => boolean;
15
18
  }
@@ -26,6 +29,8 @@ export interface PilotoConfig {
26
29
  }
27
30
  export declare const PilotoContext: import("react").Context<PilotoConfig>;
28
31
  export declare const usePiloto: () => PilotoConfig;
32
+ /** La respuesta que se está pintando (para `markdown.transform(md, turn)`). */
33
+ export declare const TurnContext: import("react").Context<Turn | undefined>;
29
34
  /** Las clases de la raíz: el alcance de los estilos y el tema. Lo usan también los portales. */
30
35
  export declare function rootProps(theme: PilotTheme, skin?: string): {
31
36
  className: string;
@@ -1,11 +1,12 @@
1
- export { Piloto, Piloto as default, type PilotoFeatures, type PilotoHandle, type PilotoProps, type PilotoSlots } from "./Piloto";
2
- export { toAgUiContext, usePilot, type Pilot, type SendOptions, type TurnDoneInfo, type UsePilotOptions } from "./usePilot";
1
+ export { Piloto, Piloto as default, type PilotoFeatures, type PilotoHandle, type PilotoProps } from "./Piloto";
2
+ export { definePlugin, pluginPoints, pluginRequires, resolvePlugins, type PilotoPlugin, type PluginCtx, type PluginEvent, type PluginView, type ResolvedPlugins, } from "./plugins";
3
+ export { toAgUiContext, usePilot, type Pilot, type SendOptions, type TurnDoneInfo, type TurnEvent, type UsePilotOptions } from "./usePilot";
3
4
  export { AgentBubble, CopyUserMessage, ReplyOptionButtons, TurnFooter, UsageStats, UserBubble, type AgentBubbleProps } from "./Bubble";
4
5
  export { AgentSteps, ArcadeIcon, ClawIcon, DEFAULT_TOOL_ICONS, Narration, RobotArmIcon, ToolRow } from "./Steps";
5
6
  export { LinkSafetyModal, Markdown } from "./Markdown";
6
7
  export { InlineMarkdown, renderInline } from "./InlineMarkdown";
7
8
  export { useTextAtPace } from "./TextAtPace";
8
- export { Composer, ModelPicker, mimeOf, type ComposerProps, type ModelOption, type UploadResult } from "./Composer";
9
+ export { Composer, ModelPicker, mimeOf, type ComposerHandle, type ComposerProps, type ModelOption, type UploadResult } from "./Composer";
9
10
  export { ConfirmCard, DecisionLines } from "./ConfirmCard";
10
11
  export { ReferenceChips, Suggestions } from "./Chips";
11
12
  export { TypingDots, WaitLabel } from "./Status";