ghostykit 0.6.3 → 0.7.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.
- package/README.md +94 -19
- package/dist/chunks/{chunk-LFFJLMDO.js → chunk-FWBUIGTC.js} +35 -148
- package/dist/chunks/chunk-UUH37KXA.js +144 -0
- package/dist/chunks/chunk-ZIUOXG7B.js +1757 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +12 -8
- package/dist/core/narration.d.ts +18 -1
- package/dist/core/types.d.ts +2 -0
- package/dist/plugins/index.d.ts +39 -0
- package/dist/plugins/index.js +129 -0
- package/dist/react/Bubble.d.ts +11 -1
- package/dist/react/Composer.d.ts +11 -3
- package/dist/react/Piloto.d.ts +13 -37
- package/dist/react/context.d.ts +4 -1
- package/dist/react/index.d.ts +4 -3
- package/dist/react/index.js +503 -1788
- package/dist/react/plugins.d.ts +212 -0
- package/dist/react/usePilot.d.ts +15 -0
- package/dist/styles.css +572 -548
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -97,34 +97,109 @@ const ref = useRef<PilotoHandle>(null);
|
|
|
97
97
|
draft={{ text: "Why did the monitor fail?", key: row.id }} // prefilled + focused, not sent
|
|
98
98
|
threadId={conversationId} // reopen a stored thread
|
|
99
99
|
context={() => ({ screen: "crons", selected })} // AG-UI `context`, every run
|
|
100
|
-
toolVerbs={{ run_cron: { one: "Ran a cron", many: "Ran %n crons", running: "Running the cron…" } }}
|
|
101
|
-
upload={async (file) => ({ url: await myUpload(file) })}
|
|
102
|
-
transcribe={async (audio) => myWhisper(audio)}
|
|
103
100
|
onTurnDone={({ text }) => refreshMyData()}
|
|
104
|
-
|
|
101
|
+
plugins={PLUGINS} // everything else: see Customize
|
|
105
102
|
/>;
|
|
106
103
|
|
|
107
104
|
ref.current?.addReference({ id: "12", label: "#12 Rosa" }); // "@" a row into the message
|
|
108
105
|
ref.current?.send("Why did this fail?"); // ask on the user's behalf
|
|
109
106
|
```
|
|
110
107
|
|
|
111
|
-
Other
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
`
|
|
117
|
-
(`STATE_SNAPSHOT` + `STATE_DELTA`) is `pilot.state.agentState`, and each turn's `CUSTOM` events are
|
|
118
|
-
`turn.custom` (what `renderDeliveries` draws).
|
|
108
|
+
Other props: `features` (`attachments`, `retry`, `copy`, `usage`, `replyOptions`, `duration`,
|
|
109
|
+
`steer`), `references` / `onReferencesChange`, `placeholder`, `avatar`, `initialTurns`, `loadOlder`
|
|
110
|
+
(earlier pages of a long thread), `forwardedProps` (extra keys for every run, e.g.
|
|
111
|
+
`{permissions: "ask"}`), and the callbacks `onTurnStart`, `onTitle`, `onState`, `onEvent` (every raw
|
|
112
|
+
AG-UI event), `onSend`. The agent's shared state (`STATE_SNAPSHOT` + `STATE_DELTA`) is
|
|
113
|
+
`pilot.state.agentState`, and each turn's `CUSTOM` events are `turn.custom`.
|
|
119
114
|
|
|
120
115
|
Subagents (`state.subagents.tasks`) are `pilot.subagents`; `pilot.stopSubagent(id)` stops one, and
|
|
121
|
-
`<Subagents pilot={pilot} />` is a ready-made bar ("2 agents working", one row each, *Stop*)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
`<Subagents pilot={pilot} />` is a ready-made bar ("2 agents working", one row each, *Stop*). A
|
|
117
|
+
message the server can't take while a run is live (`409 run_active`) waits in `pilot.state.queued`
|
|
118
|
+
and is sent when that run ends. `pilot.refresh()` asks for the thread again (when your own event bus
|
|
119
|
+
says something changed). To read the state outside the chat, call `usePilot(transport)` yourself and
|
|
120
|
+
pass the result as `pilot`. Every piece is exported (`AgentBubble`, `AgentSteps`, `Composer`,
|
|
121
|
+
`ConfirmCard`, `Markdown`, …) if you want to build your own layout.
|
|
122
|
+
|
|
123
|
+
## Customize
|
|
124
|
+
|
|
125
|
+
There is **one** way to customize `<Piloto>`: **plugins**. A plugin is an object with a `name` and
|
|
126
|
+
named extension points. Everything a plugin draws is a React **component**: it is mounted as
|
|
127
|
+
`<View ctx={…} />`, so it can use hooks. Branding is the skin plus `--piloto-*` tokens.
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { Piloto, definePlugin } from "ghostykit";
|
|
131
|
+
import { livePage, modelPicker, pilotDevtools } from "ghostykit/plugins";
|
|
132
|
+
|
|
133
|
+
const crm = definePlugin({
|
|
134
|
+
name: "crm",
|
|
135
|
+
requires: { events: ["crm.card"] }, // what it handles, visible at a glance
|
|
136
|
+
tools: { verbs: { find_lead: { one: "Found the lead", many: "Found %n leads", running: "Looking…" } } },
|
|
137
|
+
message: { custom: { "crm.card": ({ entry, ctx }) => <LeadCard lead={entry.value} onOpen={() => ctx.send("Open it")} /> } },
|
|
138
|
+
composer: { actions: ({ ctx }) => <PointButton onPick={(row) => ctx.addReference(row)} /> },
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Create them ONCE (module scope or useMemo): some plugins keep state.
|
|
142
|
+
const PLUGINS = [crm, modelPicker({ onChange: (model) => api.setModel(model) }), livePage(), pilotDevtools()];
|
|
143
|
+
|
|
144
|
+
<Piloto transport={transport} skin="ghosty" plugins={PLUGINS} />;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| Point | What it does | Example |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `name` | Unique. Marks everything it draws with `data-plugin="<name>"`. | `"crm"` |
|
|
150
|
+
| `styles` | Whose styles its views use. `"host"` (default): your app's; ghostykit's utilities stay out (`data-piloto-isolate`), so they never override your classes. `"ghostykit"`: for views built from ghostykit components (`<Subagents>`, `ModelPicker`). | `"ghostykit"` |
|
|
151
|
+
| `requires` | `{events, state}` it handles. Derived from `events`/`state`/`message.custom` if missing; in development a mismatch is a warning. | `{ events: ["ghosty.page"] }` |
|
|
152
|
+
| `strings` | Texts over the locale's. | `{ stop: "Para" }` |
|
|
153
|
+
| `markdown.transform` | Rewrites the answer's markdown, with the turn at hand (chained). | `(md, turn) => md.replace(/#(\d+)/g, "[#$1](#folio-$1)")` |
|
|
154
|
+
| `markdown.onLinkClick` | A click on a link; `true` if handled (asked in order). | open in-app |
|
|
155
|
+
| `markdown.blocks` | Fenced blocks by name (```` ```eb-file ````) taken out of the text and drawn as cards under the answer; one still being written comes with `complete: false`. | `{ "eb-file": FileCard }` |
|
|
156
|
+
| `vars`, `className` | Branding on the root: `--piloto-*` tokens and classes. | `{ "--piloto-radius-bubble": "6px" }` |
|
|
157
|
+
| `tools.verbs` | How each tool is named in the steps. | `{ run_cron: { one, many, running } }` |
|
|
158
|
+
| `tools.icons` | Icon by tool name or `kind`. | `{ run_cron: Clock }` |
|
|
159
|
+
| `tools.render` | A tool's own view, under the steps, by tool name or `kind`. | `{ create_image: ImagePreview }` |
|
|
160
|
+
| `events` | Handlers by AG-UI type, by `CUSTOM` name, or `*`. | `{ "ghosty.ui": (e, ctx) => board.open(e.value) }` |
|
|
161
|
+
| `state` | Called when that key of `agentState` changes. | `{ model: (v, ctx) => … }` |
|
|
162
|
+
| `message.custom` | Draws a `turn.custom` entry by its name (deliveries, CRM cards). | `{ "ghosty.delivery": DeliveryCard }` |
|
|
163
|
+
| `message.assistantBefore` | Before each answer's text, under its steps. | live page progress, "Creating image…" |
|
|
164
|
+
| `message.user` / `message.assistant` | Extras under each user message / answer. | `({ turn, live }) => …` |
|
|
165
|
+
| `message.formatUserText` | How the user's text is shown (chained). | strip app plumbing |
|
|
166
|
+
| `composer.actions` | Buttons in the composer bar, between the image button and the clip. | "My files", "Point" |
|
|
167
|
+
| `composer.actionsEnd` | Buttons after the clip. | model picker |
|
|
168
|
+
| `composer.above` | Above the message box. | `<Subagents pilot={ctx.pilot} />` |
|
|
169
|
+
| `composer.below` | Under the message box. | suggestion chips |
|
|
170
|
+
| `composer.attachments` | `upload`, `transcribe`, `voiceNoteText`. | `{ upload: async (f) => ({ url }) }` |
|
|
171
|
+
| `header` | Top right, over the thread. | share button |
|
|
172
|
+
| `sidePanel` | Next to the chat. | live page, subagents |
|
|
173
|
+
| `aboveThread` | Above the messages. | a banner |
|
|
174
|
+
| `empty` | The empty thread (instead of the greeting). | onboarding |
|
|
175
|
+
| `waiting` | While the agent hasn't answered (instead of the waiting phrase). Gets `step`, `since`. | "Creating your image…" |
|
|
176
|
+
| `wrap` | A provider around the whole chat (`{ctx, children}`); the first plugin is outermost. | artifacts panel |
|
|
177
|
+
| `onTurnStart` / `onTurnDone` | This client started / finished a turn. | open the last delivery |
|
|
178
|
+
|
|
179
|
+
Every view gets `ctx`: `{pilot, agentState, strings, send, addReference, addAttachments, setDraft, focus, plugins}`.
|
|
180
|
+
`addAttachments` takes `File`s (they go through `upload`) or ready `Attachment`s (e.g. from "My files"),
|
|
181
|
+
and they can be sent without text. The `ref` of `<Piloto>` is the same handle (without `plugins`,
|
|
182
|
+
`strings`, `agentState`), so the app never needs a separate `usePilot`: `ref.current.addReference(…)`,
|
|
183
|
+
`ref.current.pilot.reconnect()` (when your own event bus says the thread changed),
|
|
184
|
+
`ref.current.pilot.stop()`. `initialPrompt` sends a message as soon as the thread is open (a question
|
|
185
|
+
in the URL).
|
|
186
|
+
|
|
187
|
+
**Order and precedence**, in the order of the `plugins` list:
|
|
188
|
+
|
|
189
|
+
- **Maps** (`strings`, `tools.verbs`, `tools.icons`, `vars`) are merged, and later plugins win. `markdown` and `formatUserText` are chained.
|
|
190
|
+
- **`events` and `state`** call every handler, in order.
|
|
191
|
+
- **Keyed or single views** (`tools.render`, `message.custom`, `markdown.blocks`, `empty`, `waiting`, `composer.attachments`): the first plugin that declares one wins. A clash is a development warning.
|
|
192
|
+
- **Lists** (`header`, `sidePanel`, `aboveThread`, `composer.actions`/`actionsEnd`/`above`/`below`, `message.user`/`assistant`/`assistantBefore`, `wrap`, `onTurnStart`/`onTurnDone`) all apply, in order.
|
|
193
|
+
|
|
194
|
+
In development, ghostykit also warns once about a `CUSTOM` event that nothing handles.
|
|
195
|
+
|
|
196
|
+
**Ready-made plugins** (`ghostykit/plugins`), usable as they are and as templates:
|
|
197
|
+
|
|
198
|
+
| Plugin | What it does |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `modelPicker({ onChange, stateKey? })` | The model picker in the composer bar, from `agentState.model = {current, options}`. Switching is your API (`onChange`). |
|
|
201
|
+
| `livePage({ width? })` | A side panel that follows `CUSTOM ghosty.page` (plan, sections, ready, error) with the link at the end. |
|
|
202
|
+
| `pilotDevtools({ events? })` | Prints the mounted plugins, their points and what they handle; with `events: true`, every event and who handles it. |
|
|
128
203
|
|
|
129
204
|
### Skins and theming
|
|
130
205
|
|
|
@@ -193,7 +193,7 @@ function toolLabel(t, v) {
|
|
|
193
193
|
if (str(x.title)) patch.label = str(x.title);
|
|
194
194
|
if (str(x.kind)) patch.kind = str(x.kind);
|
|
195
195
|
if (str(x.path)) patch.path = str(x.path);
|
|
196
|
-
if (str(x.detail)) patch
|
|
196
|
+
if (str(x.detail)) Object.assign(patch, { detail: str(x.detail), detailFromServer: true });
|
|
197
197
|
t.tools = (t.tools ?? []).map((y) => y.id === x.toolCallId ? { ...y, ...patch } : y);
|
|
198
198
|
}
|
|
199
199
|
function applyStoredCustom(t, m) {
|
|
@@ -259,7 +259,8 @@ function applySnapshot(s, messages) {
|
|
|
259
259
|
const tools = t.tools?.map((x) => {
|
|
260
260
|
const prev = o?.role === t.role ? o.tools?.find((y) => y.id === x.id) : void 0;
|
|
261
261
|
const status = x.status === "pending" && (prev?.status ?? (live ? "in_progress" : void 0));
|
|
262
|
-
|
|
262
|
+
const keepDetail = prev?.detailFromServer && !x.detailFromServer ? { detail: prev.detail, detailFromServer: true } : {};
|
|
263
|
+
return { ...prev, ...x, ...status ? { status } : {}, ...keepDetail };
|
|
263
264
|
});
|
|
264
265
|
const base = tools ? { ...t, tools } : t;
|
|
265
266
|
if (!o || o.role !== t.role) return base;
|
|
@@ -325,6 +326,7 @@ function reduceAgUiEvent(prev, e, now = Date.now()) {
|
|
|
325
326
|
}
|
|
326
327
|
case "TOOL_CALL_END": {
|
|
327
328
|
const known = s.turns[s.turns.length - 1]?.tools?.find((x) => x.id === e.toolCallId);
|
|
329
|
+
if (known?.detailFromServer) return s;
|
|
328
330
|
return upsertTool(s, e.toolCallId, { detail: argsDetail(known?.args) });
|
|
329
331
|
}
|
|
330
332
|
case "TOOL_CALL_RESULT": {
|
|
@@ -369,7 +371,7 @@ function reduceAgUiEvent(prev, e, now = Date.now()) {
|
|
|
369
371
|
if (str(v.title)) patch.label = str(v.title);
|
|
370
372
|
if (str(v.kind)) patch.kind = str(v.kind);
|
|
371
373
|
if (str(v.path)) patch.path = str(v.path);
|
|
372
|
-
if (str(v.detail)) patch
|
|
374
|
+
if (str(v.detail)) Object.assign(patch, { detail: str(v.detail), detailFromServer: true });
|
|
373
375
|
return upsertTool(s, v.toolCallId, patch);
|
|
374
376
|
}
|
|
375
377
|
if (e.name === "ghosty.replace") {
|
|
@@ -538,7 +540,7 @@ function isNarration(chunk) {
|
|
|
538
540
|
const t = chunk.trim();
|
|
539
541
|
return t.length > 0 && t.length <= 300 && !/\n\s*\n/.test(t) && !t.includes("```");
|
|
540
542
|
}
|
|
541
|
-
function splitNarration(text, cuts) {
|
|
543
|
+
function splitNarration(text, cuts, closed = false) {
|
|
542
544
|
if (cuts?.length) {
|
|
543
545
|
const valid = cuts.filter((c) => c > 0 && c <= text.length);
|
|
544
546
|
if (valid.length) {
|
|
@@ -550,7 +552,9 @@ function splitNarration(text, cuts) {
|
|
|
550
552
|
from = c;
|
|
551
553
|
}
|
|
552
554
|
if (chunks.every(isNarration)) {
|
|
553
|
-
|
|
555
|
+
const rest = text.slice(from).replace(/^\s+/, "");
|
|
556
|
+
if (closed && !rest.trim()) return { steps: chunks.slice(0, -1).map((t) => t.replace(/\s*\n\s*/g, " ")), rest: chunks[chunks.length - 1] };
|
|
557
|
+
return { steps: chunks.map((t) => t.replace(/\s*\n\s*/g, " ")), rest };
|
|
554
558
|
}
|
|
555
559
|
}
|
|
556
560
|
return { steps: [], rest: text };
|
|
@@ -574,6 +578,30 @@ function narrationToMarkdown(chunks, answer) {
|
|
|
574
578
|
|
|
575
579
|
${answer.trim()}` : list;
|
|
576
580
|
}
|
|
581
|
+
function extractBlocks(text, langs, cuts) {
|
|
582
|
+
if (!langs.length || !text.includes("```")) return { text, cuts, blocks: [] };
|
|
583
|
+
const want = new Set(langs);
|
|
584
|
+
const blocks = [];
|
|
585
|
+
const removed = [];
|
|
586
|
+
const re = /(^|\n)```([\w.-]+)[^\n]*\n([\s\S]*?)(\n```[ \t]*(?=\n|$)|$)/g;
|
|
587
|
+
for (let m = re.exec(text); m; m = re.exec(text)) {
|
|
588
|
+
if (!want.has(m[2])) continue;
|
|
589
|
+
const start = m.index + m[1].length;
|
|
590
|
+
const end = m.index + m[0].length;
|
|
591
|
+
blocks.push({ lang: m[2], body: m[3], complete: Boolean(m[4]) });
|
|
592
|
+
removed.push([start, end]);
|
|
593
|
+
}
|
|
594
|
+
if (!removed.length) return { text, cuts, blocks };
|
|
595
|
+
let out = "";
|
|
596
|
+
let from = 0;
|
|
597
|
+
for (const [a, b] of removed) {
|
|
598
|
+
out += text.slice(from, a);
|
|
599
|
+
from = b;
|
|
600
|
+
}
|
|
601
|
+
out += text.slice(from);
|
|
602
|
+
const shift = (c) => c - removed.reduce((n, [a, b]) => n + (c >= b ? b - a : c > a ? c - a : 0), 0);
|
|
603
|
+
return { text: out, ...cuts ? { cuts: cuts.map(shift) } : {}, blocks };
|
|
604
|
+
}
|
|
577
605
|
|
|
578
606
|
// src/core/options.ts
|
|
579
607
|
var BULLET = /^\s*(?:[-*•+]|(\d{1,2})[.)])\s+(.+?)\s*$/;
|
|
@@ -604,140 +632,6 @@ function cleanReplyOption(t) {
|
|
|
604
632
|
return s.charAt(0).toLocaleUpperCase() + s.slice(1);
|
|
605
633
|
}
|
|
606
634
|
|
|
607
|
-
// src/core/steps.ts
|
|
608
|
-
var isRunning = (t) => t.status !== "completed" && t.status !== "failed" && t.status !== "cancelled";
|
|
609
|
-
function toolNames(t) {
|
|
610
|
-
const title = t.title ?? "";
|
|
611
|
-
if (!title) return [];
|
|
612
|
-
const out = [title];
|
|
613
|
-
const double = /^mcp__.+?__(.+)$/i.exec(title);
|
|
614
|
-
if (double) out.push(double[1]);
|
|
615
|
-
else if (/^mcp_/i.test(title)) {
|
|
616
|
-
const parts = title.slice(4).split("_");
|
|
617
|
-
for (let i = 0; i < parts.length; i++) out.push(parts.slice(i).join("_"));
|
|
618
|
-
}
|
|
619
|
-
return [...new Set(out)];
|
|
620
|
-
}
|
|
621
|
-
function toolName(t) {
|
|
622
|
-
const names = toolNames(t);
|
|
623
|
-
return /^mcp__/i.test(names[0] ?? "") ? names[1] ?? names[0] : names[0] ?? null;
|
|
624
|
-
}
|
|
625
|
-
function namedVerb(t, verbs) {
|
|
626
|
-
for (const name of toolNames(t)) if (verbs[name]) return { key: name, verb: verbs[name] };
|
|
627
|
-
return null;
|
|
628
|
-
}
|
|
629
|
-
function stepLabel(t, verbs, fallback = "Paso") {
|
|
630
|
-
if (t.label) return t.label;
|
|
631
|
-
const named = namedVerb(t, verbs);
|
|
632
|
-
if (named) return isRunning(t) && named.verb.running ? named.verb.running : named.verb.one;
|
|
633
|
-
return t.title ?? t.kind ?? fallback;
|
|
634
|
-
}
|
|
635
|
-
function summarizeTools(tools, verbs) {
|
|
636
|
-
const groups = /* @__PURE__ */ new Map();
|
|
637
|
-
for (const t of tools) {
|
|
638
|
-
const named = namedVerb(t, verbs);
|
|
639
|
-
const key = named?.key ?? t.kind ?? "other";
|
|
640
|
-
const g = groups.get(key) ?? { n: 0, verb: named?.verb ?? verbs[key] };
|
|
641
|
-
g.n++;
|
|
642
|
-
groups.set(key, g);
|
|
643
|
-
}
|
|
644
|
-
const parts = [];
|
|
645
|
-
for (const [key, { n, verb }] of groups) {
|
|
646
|
-
if (verb) parts.push(n === 1 ? verb.one : verb.many.replace("%n", String(n)));
|
|
647
|
-
else parts.push(`${n} \xD7 ${key.replace(/[_-]/g, " ")}`);
|
|
648
|
-
}
|
|
649
|
-
return parts.join(" \xB7 ");
|
|
650
|
-
}
|
|
651
|
-
function groupSteps(tools, label) {
|
|
652
|
-
const groups = /* @__PURE__ */ new Map();
|
|
653
|
-
for (const t of tools) {
|
|
654
|
-
const k = `${label(t)}\0${t.detail ?? ""}`;
|
|
655
|
-
const g = groups.get(k);
|
|
656
|
-
if (!g) {
|
|
657
|
-
groups.set(k, { tool: t, times: 1 });
|
|
658
|
-
continue;
|
|
659
|
-
}
|
|
660
|
-
g.times++;
|
|
661
|
-
const wins = isRunning(t) || t.status === "failed" || !(isRunning(g.tool) || g.tool.status === "failed");
|
|
662
|
-
if (wins) g.tool = { ...t, id: g.tool.id };
|
|
663
|
-
}
|
|
664
|
-
return [...groups.values()];
|
|
665
|
-
}
|
|
666
|
-
var TOOL_VERBS_ES = {
|
|
667
|
-
read: { one: "Le\xED un archivo", many: "Le\xED %n archivos" },
|
|
668
|
-
read_file: { one: "Le\xED un archivo", many: "Le\xED %n archivos" },
|
|
669
|
-
write: { one: "Escrib\xED un archivo", many: "Escrib\xED %n archivos" },
|
|
670
|
-
write_file: { one: "Escrib\xED un archivo", many: "Escrib\xED %n archivos" },
|
|
671
|
-
edit: { one: "Edit\xE9 un archivo", many: "Edit\xE9 %n archivos" },
|
|
672
|
-
edit_file: { one: "Edit\xE9 un archivo", many: "Edit\xE9 %n archivos" },
|
|
673
|
-
bash: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
|
|
674
|
-
shell: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
|
|
675
|
-
terminal: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
|
|
676
|
-
execute: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
|
|
677
|
-
grep: { one: "Busqu\xE9 en el c\xF3digo", many: "Busqu\xE9 en el c\xF3digo" },
|
|
678
|
-
glob: { one: "Busqu\xE9 archivos", many: "Busqu\xE9 archivos" },
|
|
679
|
-
search: { one: "Busqu\xE9", many: "Busqu\xE9 %n veces" },
|
|
680
|
-
tool_search: { one: "Busqu\xE9 una herramienta", many: "Busqu\xE9 %n herramientas" },
|
|
681
|
-
web_search: { one: "Busqu\xE9 en la web", many: "Busqu\xE9 en la web" },
|
|
682
|
-
web_fetch: { one: "Abr\xED una p\xE1gina", many: "Abr\xED %n p\xE1ginas" },
|
|
683
|
-
fetch: { one: "Abr\xED una p\xE1gina", many: "Abr\xED %n p\xE1ginas" },
|
|
684
|
-
site_publish: { one: "Publiqu\xE9 una p\xE1gina", many: "Publiqu\xE9 %n p\xE1ginas" },
|
|
685
|
-
pdf: { one: "Gener\xE9 un PDF", many: "Gener\xE9 %n PDF" },
|
|
686
|
-
connector: { one: "Us\xE9 tu sistema", many: "Us\xE9 tu sistema %n veces" },
|
|
687
|
-
mcp: { one: "Us\xE9 una herramienta", many: "Us\xE9 %n herramientas" },
|
|
688
|
-
browser: { one: "Us\xE9 el navegador", many: "Us\xE9 el navegador" },
|
|
689
|
-
todo: { one: "Anot\xE9 el plan", many: "Anot\xE9 el plan" },
|
|
690
|
-
delete: { one: "Borr\xE9 un archivo", many: "Borr\xE9 %n archivos" },
|
|
691
|
-
move: { one: "Mov\xED un archivo", many: "Mov\xED %n archivos" },
|
|
692
|
-
think: { one: "Lo pens\xE9", many: "Lo pens\xE9" },
|
|
693
|
-
image: { one: "Cre\xE9 una imagen", many: "Cre\xE9 %n im\xE1genes" },
|
|
694
|
-
delegate: { one: "Delegu\xE9 una tarea", many: "Delegu\xE9 %n tareas" },
|
|
695
|
-
collect: { one: "Recog\xED un resultado", many: "Recog\xED %n resultados" },
|
|
696
|
-
status: { one: "Pregunt\xE9 c\xF3mo iba", many: "Pregunt\xE9 %n veces c\xF3mo iba" },
|
|
697
|
-
stop: { one: "Detuve una tarea", many: "Detuve %n tareas" },
|
|
698
|
-
guide: { one: "Le\xED una gu\xEDa", many: "Le\xED %n gu\xEDas" },
|
|
699
|
-
deliver: { one: "Entregu\xE9 un archivo", many: "Entregu\xE9 %n archivos" },
|
|
700
|
-
sheet: { one: "Consult\xE9 una hoja", many: "Consult\xE9 %n hojas" },
|
|
701
|
-
view: { one: "Arm\xE9 una vista", many: "Arm\xE9 %n vistas" }
|
|
702
|
-
};
|
|
703
|
-
var TOOL_VERBS_EN = {
|
|
704
|
-
read: { one: "Read a file", many: "Read %n files" },
|
|
705
|
-
read_file: { one: "Read a file", many: "Read %n files" },
|
|
706
|
-
write: { one: "Wrote a file", many: "Wrote %n files" },
|
|
707
|
-
write_file: { one: "Wrote a file", many: "Wrote %n files" },
|
|
708
|
-
edit: { one: "Edited a file", many: "Edited %n files" },
|
|
709
|
-
edit_file: { one: "Edited a file", many: "Edited %n files" },
|
|
710
|
-
bash: { one: "Ran a command", many: "Ran %n commands" },
|
|
711
|
-
shell: { one: "Ran a command", many: "Ran %n commands" },
|
|
712
|
-
terminal: { one: "Ran a command", many: "Ran %n commands" },
|
|
713
|
-
execute: { one: "Ran a command", many: "Ran %n commands" },
|
|
714
|
-
grep: { one: "Searched the code", many: "Searched the code" },
|
|
715
|
-
glob: { one: "Searched for files", many: "Searched for files" },
|
|
716
|
-
search: { one: "Searched", many: "Searched %n times" },
|
|
717
|
-
tool_search: { one: "Looked for a tool", many: "Looked for %n tools" },
|
|
718
|
-
web_search: { one: "Searched the web", many: "Searched the web" },
|
|
719
|
-
web_fetch: { one: "Opened a page", many: "Opened %n pages" },
|
|
720
|
-
fetch: { one: "Opened a page", many: "Opened %n pages" },
|
|
721
|
-
site_publish: { one: "Published a page", many: "Published %n pages" },
|
|
722
|
-
pdf: { one: "Generated a PDF", many: "Generated %n PDFs" },
|
|
723
|
-
connector: { one: "Used your system", many: "Used your system %n times" },
|
|
724
|
-
mcp: { one: "Used a tool", many: "Used %n tools" },
|
|
725
|
-
browser: { one: "Used the browser", many: "Used the browser" },
|
|
726
|
-
todo: { one: "Wrote down the plan", many: "Wrote down the plan" },
|
|
727
|
-
delete: { one: "Deleted a file", many: "Deleted %n files" },
|
|
728
|
-
move: { one: "Moved a file", many: "Moved %n files" },
|
|
729
|
-
think: { one: "Thought it through", many: "Thought it through" },
|
|
730
|
-
image: { one: "Created an image", many: "Created %n images" },
|
|
731
|
-
delegate: { one: "Delegated a task", many: "Delegated %n tasks" },
|
|
732
|
-
collect: { one: "Collected a result", many: "Collected %n results" },
|
|
733
|
-
status: { one: "Checked how it was going", many: "Checked %n times how it was going" },
|
|
734
|
-
stop: { one: "Stopped a task", many: "Stopped %n tasks" },
|
|
735
|
-
guide: { one: "Read a guide", many: "Read %n guides" },
|
|
736
|
-
deliver: { one: "Delivered a file", many: "Delivered %n files" },
|
|
737
|
-
sheet: { one: "Checked a sheet", many: "Checked %n sheets" },
|
|
738
|
-
view: { one: "Built a view", many: "Built %n views" }
|
|
739
|
-
};
|
|
740
|
-
|
|
741
635
|
export {
|
|
742
636
|
applyJsonPatch,
|
|
743
637
|
createPilotState,
|
|
@@ -761,14 +655,7 @@ export {
|
|
|
761
655
|
isNarration,
|
|
762
656
|
splitNarration,
|
|
763
657
|
narrationToMarkdown,
|
|
658
|
+
extractBlocks,
|
|
764
659
|
extractReplyOptions,
|
|
765
|
-
cleanReplyOption
|
|
766
|
-
isRunning,
|
|
767
|
-
toolNames,
|
|
768
|
-
toolName,
|
|
769
|
-
stepLabel,
|
|
770
|
-
summarizeTools,
|
|
771
|
-
groupSteps,
|
|
772
|
-
TOOL_VERBS_ES,
|
|
773
|
-
TOOL_VERBS_EN
|
|
660
|
+
cleanReplyOption
|
|
774
661
|
};
|
|
@@ -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
|
+
};
|