ghostykit 0.3.2 → 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.
@@ -1,670 +0,0 @@
1
- // src/core/reconcile.ts
2
- var same = (a, b) => a.role === b.role && a.text.trim() === b.text.trim();
3
- function reconcileTurns(current, fresh) {
4
- const users = (ts) => ts.filter((t) => t.role === "user").length;
5
- if (users(fresh) <= users(current)) return current;
6
- let j = 0;
7
- return fresh.map((f) => {
8
- for (let k = j; k < current.length; k++) {
9
- if (same(current[k], f)) {
10
- j = k + 1;
11
- return { ...f, ...current[k], deliveries: current[k].deliveries ?? f.deliveries };
12
- }
13
- }
14
- return f;
15
- });
16
- }
17
-
18
- // src/core/reducer.ts
19
- var RESTART_NOTICE = "El servidor se reinici\xF3 a media respuesta. Vuelve a enviar el mensaje.";
20
- var RESUME_TEXT = "Contin\xFAa donde te quedaste.";
21
- function createPilotState(turns = []) {
22
- return {
23
- turns,
24
- busy: false,
25
- connected: false,
26
- error: null,
27
- notice: null,
28
- phase: "waking",
29
- usage: null,
30
- models: [],
31
- currentModel: null,
32
- canSeeImages: true,
33
- interrupted: null,
34
- elsewhere: false,
35
- permissions: [],
36
- turnStartedAt: 0,
37
- streaming: false,
38
- currentTurnId: null,
39
- connections: 0,
40
- needAccount: false,
41
- heartbeatMs: null,
42
- effects: []
43
- };
44
- }
45
- var isObj = (d) => typeof d === "object" && d !== null;
46
- function patchCurrent(s, patch) {
47
- const inProgress = s.streaming;
48
- const turns = [...s.turns];
49
- const last = turns[turns.length - 1];
50
- if (inProgress && last?.role === "assistant") turns[turns.length - 1] = patch(last);
51
- else turns.push(patch({ role: "assistant", text: "" }));
52
- return { ...s, streaming: true, turns };
53
- }
54
- function stillAlive(s) {
55
- return { ...s, notice: s.notice === RESTART_NOTICE ? null : s.notice, interrupted: null };
56
- }
57
- function upsertTool(s, entry) {
58
- return patchCurrent(s, (t) => {
59
- const tools = [...t.tools ?? []];
60
- const i = tools.findIndex((x) => x.id === entry.id);
61
- let cuts = t.cuts;
62
- if (i === -1) {
63
- tools.push(entry);
64
- const len = t.text.length;
65
- if (t.text.trim() && cuts?.[cuts.length - 1] !== len) cuts = [...cuts ?? [], len];
66
- } else tools[i] = { ...tools[i], ...entry };
67
- return { ...t, tools, cuts };
68
- });
69
- }
70
- var patchPage = (s, pageId, fn) => patchCurrent(s, (t) => ({ ...t, pages: (t.pages ?? []).map((p) => p.pageId === pageId ? fn(p) : p) }));
71
- var patchSection = (s, pageId, id, fn) => patchPage(s, pageId, (p) => ({ ...p, sections: p.sections.map((x) => x.id === id ? fn(x) : x) }));
72
- function toToolEntry(d) {
73
- const e = { id: String(d.id) };
74
- if (d.title !== void 0) e.title = d.title;
75
- if (d.kind !== void 0) e.kind = d.kind;
76
- if (d.status !== void 0) e.status = d.status;
77
- if (d.path !== void 0) e.path = d.path;
78
- const detail = d.detail ?? d.detalle;
79
- if (detail !== void 0) e.detail = detail;
80
- return e;
81
- }
82
- function toDelivery(p) {
83
- return {
84
- kind: p.tipo === "artefacto" ? "artifact" : "file",
85
- name: p.nombre ?? p.titulo ?? "archivo",
86
- fileId: p.fileId,
87
- docId: p.docId,
88
- mime: p.mime,
89
- size: p.size ?? (typeof p.contenidoBase64 === "string" ? Math.floor(p.contenidoBase64.length * 3 / 4) : void 0),
90
- ...p.captura ? { capture: true } : {},
91
- raw: p
92
- };
93
- }
94
- function reducePilotEvent(prev, name, data, now = Date.now()) {
95
- const s = prev.effects.length ? { ...prev, effects: [] } : prev;
96
- const d = isObj(data) ? data : {};
97
- switch (name) {
98
- case "started": {
99
- const connections = s.connections + 1;
100
- const heartbeatMs = typeof d.latidoMs === "number" && d.latidoMs > 0 ? d.latidoMs : null;
101
- let next = { ...s, connections, connected: true, heartbeatMs };
102
- if (typeof d.turnId === "string" && s.turnStartedAt && !s.currentTurnId) next.currentTurnId = d.turnId;
103
- if (connections > 1 && s.turnStartedAt && s.streaming) {
104
- const last = next.turns[next.turns.length - 1];
105
- next = { ...next, streaming: false, turns: last?.role === "assistant" ? next.turns.slice(0, -1) : next.turns };
106
- }
107
- return next;
108
- }
109
- case "caps": {
110
- if (!isObj(data)) return s;
111
- if (d.needAccount) return { ...s, needAccount: true };
112
- return { ...s, canSeeImages: d.image !== false };
113
- }
114
- case "page-plan": {
115
- if (!isObj(data)) return s;
116
- const live = stillAlive(s);
117
- return patchCurrent(live, (t) => {
118
- const page = {
119
- pageId: d.pageId,
120
- title: d.title,
121
- theme: d.theme,
122
- sections: (d.sections ?? []).map((x) => ({ ...x, status: "pending" })),
123
- startedAt: t.planningAt ?? now,
124
- mode: "build"
125
- };
126
- return { ...t, planning: false, pages: [...(t.pages ?? []).filter((p) => p.pageId !== d.pageId), page] };
127
- });
128
- }
129
- case "section-building":
130
- if (!isObj(data)) return s;
131
- return patchSection(s, d.pageId, d.id, (x) => x.status === "done" ? x : { ...x, status: "building" });
132
- case "section-update":
133
- if (!isObj(data)) return s;
134
- return patchSection(s, d.pageId, d.id, (x) => ({ ...x, html: d.html, status: x.status === "pending" ? "building" : x.status }));
135
- case "section":
136
- if (!isObj(data)) return s;
137
- return patchSection(s, d.pageId, d.id, (x) => ({ ...x, html: d.html, status: d.ok ? "done" : "failed" }));
138
- case "page-ready":
139
- if (!isObj(data)) return s;
140
- return patchPage(s, d.pageId, (p) => ({ ...p, readyAt: now, url: d.url, n: d.n }));
141
- case "page-planning":
142
- return patchCurrent(stillAlive(s), (t) => ({ ...t, planning: true, planningAt: t.planningAt ?? now }));
143
- case "page-drop":
144
- return patchCurrent(s, (t) => ({ ...t, planning: false, pages: (t.pages ?? []).filter((p) => p.readyAt) }));
145
- case "page-edit": {
146
- if (!isObj(data)) return s;
147
- const live = stillAlive(s);
148
- const page = {
149
- pageId: d.pageId,
150
- title: d.title,
151
- theme: d.theme,
152
- sections: (d.sections ?? []).map((x) => ({ ...x, status: x.id === d.sectionId ? "building" : "done" })),
153
- startedAt: now,
154
- mode: "edit",
155
- focus: d.sectionId && d.nodeId ? { sectionId: d.sectionId, nodeId: d.nodeId } : void 0,
156
- editLabel: d.label
157
- };
158
- return patchCurrent(live, (t) => ({ ...t, pages: [...(t.pages ?? []).filter((p) => p.pageId !== d.pageId), page] }));
159
- }
160
- case "review":
161
- if (!isObj(data)) return s;
162
- return patchPage(s, d.pageId, (p) => ({
163
- ...p,
164
- theme: d.theme ?? p.theme,
165
- review: { status: d.status, fixes: d.fixes ?? [], skipped: d.skipped ?? [], thumb: d.thumb ?? p.review?.thumb, seconds: d.seconds }
166
- }));
167
- case "page-version":
168
- if (!isObj(data)) return s;
169
- return patchPage(s, d.pageId, (p) => ({
170
- ...p,
171
- n: d.n,
172
- url: d.url ?? p.url,
173
- readyAt: p.readyAt ?? now,
174
- ...d.summary ? { editSummary: d.summary } : {},
175
- ...d.focusId && p.focus ? { focus: { ...p.focus, nodeId: d.focusId } } : {}
176
- }));
177
- case "page-error":
178
- if (!isObj(data)) return s;
179
- return patchCurrent(s, (t) => ({
180
- ...t,
181
- planning: false,
182
- pages: (t.pages ?? []).map((p) => p.pageId === d.pageId || !d.pageId && !p.readyAt ? { ...p, error: d.message } : p)
183
- }));
184
- case "models":
185
- if (!isObj(data)) return s;
186
- return { ...s, models: d.options ?? [], currentModel: d.current ?? null };
187
- case "status": {
188
- if (!isObj(data)) return s;
189
- return { ...stillAlive(s), phase: d.phase, elsewhere: Boolean(d.otraCaja) };
190
- }
191
- case "reemplazo":
192
- if (!s.turnStartedAt || !isObj(data)) return s;
193
- return patchCurrent(s, (t) => ({ ...t, text: d.text }));
194
- case "chunk":
195
- if (!isObj(data)) return s;
196
- return patchCurrent(stillAlive(s), (t) => ({ ...t, text: t.text + d.text }));
197
- case "thought":
198
- if (!isObj(data)) return s;
199
- return patchCurrent(s, (t) => ({ ...t, thought: (t.thought ?? "") + d.text }));
200
- case "tool":
201
- if (!isObj(data)) return s;
202
- return upsertTool(stillAlive(s), toToolEntry(d.id == null ? { ...d, id: syntheticToolId(s, d) } : d));
203
- case "snapshot":
204
- if (!isObj(data)) return s;
205
- return reduceSnapshot(s, d, now);
206
- case "idle": {
207
- if (!s.turnStartedAt) return s;
208
- return closeCut(s, "se cort\xF3 la conexi\xF3n", void 0, void 0, now);
209
- }
210
- case "interrupted": {
211
- if (!isObj(data)) return s;
212
- const turnId = typeof d.turnId === "string" ? d.turnId : void 0;
213
- return closeCut(s, typeof d.reason === "string" ? d.reason : "se interrumpi\xF3", turnId, d.canContinue === true, now);
214
- }
215
- case "capacity": {
216
- if (!isObj(data)) return s;
217
- const message = typeof d.message === "string" && d.message ? d.message : "No hay capacidad ahora mismo. Intenta en un momento.";
218
- return { ...reduceDone(s, { turnId: d.turnId }, now), notice: message };
219
- }
220
- case "artifact":
221
- if (!isObj(data)) return s;
222
- return patchCurrent(s, (t) => ({ ...t, deliveries: [...t.deliveries ?? [], toDelivery(d)] }));
223
- case "usage": {
224
- if (!isObj(data)) return s;
225
- const u = d;
226
- const turns = [...s.turns];
227
- const last = turns[turns.length - 1];
228
- if (last?.role === "assistant") turns[turns.length - 1] = { ...last, usage: u };
229
- return { ...s, usage: u, turns };
230
- }
231
- case "title":
232
- return { ...s, effects: [{ type: "title", title: typeof d.title === "string" ? d.title : void 0 }] };
233
- case "permission": {
234
- if (!isObj(data) || typeof d.id !== "string") return s;
235
- const req = { id: d.id, title: String(d.title ?? ""), tool: d.tool, options: Array.isArray(d.options) ? d.options : [], ...d.expiresAt != null ? { expiresAt: d.expiresAt } : {} };
236
- const i = s.permissions.findIndex((p) => p.id === req.id);
237
- const permissions = i === -1 ? [...s.permissions, req] : s.permissions.map((p, k) => k === i ? req : p);
238
- return { ...s, permissions };
239
- }
240
- case "permission-resolved":
241
- if (!isObj(data)) return s;
242
- return { ...s, permissions: s.permissions.filter((p) => p.id !== d.id) };
243
- case "done": {
244
- let next = s;
245
- const last = s.turns[s.turns.length - 1];
246
- if (typeof d.text === "string" && d.text && s.turnStartedAt && !(s.streaming && last?.role === "assistant" && last.text)) {
247
- next = patchCurrent(s, (t) => ({ ...t, text: d.text }));
248
- }
249
- return reduceDone(next, d, now);
250
- }
251
- case "warning":
252
- if (!isObj(data) || typeof d.message !== "string") return s;
253
- return { ...s, notice: d.message };
254
- case "error":
255
- if (!isObj(data) || typeof d.message !== "string") return s;
256
- return { ...s, error: d.message };
257
- case "closed":
258
- return { ...s, connected: false, effects: [{ type: "disconnected" }] };
259
- default:
260
- return s;
261
- }
262
- }
263
- function keepIndependent(perms, now) {
264
- return perms.filter((p) => p.expiresAt !== void 0 && expiryMs(p.expiresAt) > now);
265
- }
266
- function expiryMs(v) {
267
- return typeof v === "number" ? v : Date.parse(v);
268
- }
269
- function closeCut(s, reason, turnId, canContinue, now = Date.now()) {
270
- return {
271
- ...s,
272
- elsewhere: false,
273
- streaming: false,
274
- busy: false,
275
- turnStartedAt: 0,
276
- error: null,
277
- notice: null,
278
- permissions: keepIndependent(s.permissions, now),
279
- interrupted: { reason, turnId, ...canContinue ? { canContinue } : {} },
280
- effects: [{ type: "turn-interrupted", reason, turnId }]
281
- };
282
- }
283
- function syntheticToolId(s, d) {
284
- const last = s.streaming ? s.turns[s.turns.length - 1] : void 0;
285
- const tools = last?.role === "assistant" ? last.tools ?? [] : [];
286
- if (d.status !== "in_progress" && d.status !== "pending") {
287
- for (let i = tools.length - 1; i >= 0; i--) {
288
- const t = tools[i];
289
- if (t.title === d.title && t.status !== "completed" && t.status !== "failed") return t.id;
290
- }
291
- }
292
- return `tool-${tools.length + 1}`;
293
- }
294
- function reduceSnapshot(s0, d, now) {
295
- const names = Array.isArray(d.tools) ? d.tools.filter((x) => typeof x === "string") : [];
296
- const tools = names.map((title, i) => ({ id: `tool-${i + 1}`, title, status: i === names.length - 1 ? "in_progress" : "completed" }));
297
- const cuts = Array.isArray(d.cortes) ? d.cortes.filter((x) => typeof x === "number") : void 0;
298
- let s = {
299
- ...stillAlive(s0),
300
- busy: true,
301
- phase: "session",
302
- turnStartedAt: s0.turnStartedAt || now,
303
- currentTurnId: typeof d.turnId === "string" ? d.turnId : s0.currentTurnId
304
- };
305
- s = patchCurrent(s, (t) => ({ ...t, text: typeof d.text === "string" ? d.text : t.text, tools, ...cuts?.length ? { cuts } : {} }));
306
- return s;
307
- }
308
- function reduceDone(s0, d, now) {
309
- let s = { ...s0, elsewhere: false };
310
- const waiting = Boolean(s.turnStartedAt);
311
- const isMine = (id) => id != null && id === s.currentTurnId;
312
- const cut = (reason, turnId) => closeCut(s, reason, turnId, void 0, now);
313
- const interrupted = isObj(d.interrumpido) ? d.interrumpido : null;
314
- if (!waiting) {
315
- if (interrupted) return cut(interrupted.motivo, interrupted.turnId);
316
- if (d.reposo) return s;
317
- } else if (interrupted) {
318
- if (isMine(interrupted.turnId) || s.connections > 1) return cut(interrupted.motivo, interrupted.turnId);
319
- return s;
320
- } else if (d.reposo) {
321
- const last2 = isObj(d.ultimo) ? d.ultimo : null;
322
- if (!(last2?.estado === "done" && isMine(last2.turnId))) {
323
- if (s.connections > 1) return cut("se cort\xF3 la conexi\xF3n");
324
- return s;
325
- }
326
- const text = typeof last2.text === "string" ? last2.text : "";
327
- if (text) s = patchCurrent(s, (t) => ({ ...t, text }));
328
- }
329
- const effects = [];
330
- s = { ...s, streaming: false, busy: false, permissions: keepIndependent(s.permissions, now) };
331
- if (s.needAccount) {
332
- s = { ...s, needAccount: false };
333
- effects.push({ type: "needs-login" });
334
- }
335
- const t0 = s.turnStartedAt;
336
- s = { ...s, turnStartedAt: 0 };
337
- let seconds;
338
- if (t0) {
339
- seconds = Math.round((now - t0) / 1e3);
340
- const turns = [...s.turns];
341
- const last2 = turns[turns.length - 1];
342
- if (last2?.role === "assistant") turns[turns.length - 1] = { ...last2, seconds };
343
- s = { ...s, turns };
344
- }
345
- const last = s.turns[s.turns.length - 1];
346
- effects.push({
347
- type: "turn-done",
348
- text: last?.role === "assistant" ? last.text : "",
349
- turnId: typeof d.turnId === "string" ? d.turnId : void 0,
350
- ...seconds !== void 0 ? { seconds } : {}
351
- });
352
- return { ...s, effects };
353
- }
354
- function beginSend(prev, text, attachments, now = Date.now()) {
355
- const inFlight = prev.busy;
356
- let s = {
357
- ...prev,
358
- effects: [],
359
- interrupted: null,
360
- notice: prev.notice === RESTART_NOTICE ? null : prev.notice,
361
- turns: [...prev.turns, { role: "user", text, attachments, steer: inFlight }]
362
- };
363
- if (!inFlight) {
364
- s = { ...s, busy: true, streaming: false, turnStartedAt: now, currentTurnId: null, effects: [{ type: "turn-start" }] };
365
- }
366
- return s;
367
- }
368
- function sendAccepted(prev, r, inFlight, now = Date.now()) {
369
- let s = { ...prev, effects: [] };
370
- if (r.turnId && !r.injected) s.currentTurnId = r.turnId;
371
- if (r.state) s.phase = r.state === "queued" ? "waking" : "session";
372
- if (inFlight && !r.injected) {
373
- s = {
374
- ...s,
375
- turns: s.turns.map((t, i) => i === s.turns.length - 1 ? { ...t, steer: false } : t),
376
- busy: true,
377
- streaming: false,
378
- turnStartedAt: now
379
- };
380
- }
381
- return s;
382
- }
383
- function sendRejectedBusy(prev, message) {
384
- const turns = [...prev.turns];
385
- if (turns[turns.length - 1]?.role === "user") turns.pop();
386
- return { ...prev, turns, busy: false, streaming: false, turnStartedAt: 0, notice: message, effects: [] };
387
- }
388
- function sendFailed(prev, message, needsLogin = false) {
389
- return { ...prev, busy: false, error: message, effects: needsLogin ? [{ type: "needs-login" }] : [] };
390
- }
391
- function stopTurn(prev) {
392
- return { ...prev, busy: false, streaming: false, turnStartedAt: 0, effects: [] };
393
- }
394
- function dropLastExchange(prev) {
395
- const turns = [...prev.turns];
396
- while (turns.length && turns[turns.length - 1].role === "assistant") turns.pop();
397
- return { ...prev, turns: turns.slice(0, -1), streaming: false, effects: [] };
398
- }
399
- function resolvePermission(prev, id, optionId, resolved, detail, asked) {
400
- const req = prev.permissions.find((p) => p.id === id) ?? asked;
401
- let s = { ...prev, effects: [], permissions: prev.permissions.filter((p) => p.id !== id) };
402
- if (!req) return s;
403
- const option = req.options.find((o) => o.optionId === optionId);
404
- const rejected = /^reject|deny|cancel/i.test(option?.kind ?? optionId);
405
- const decision = { id, title: req.title, status: !resolved ? "expired" : rejected ? "rejected" : "approved", ...detail ? { detail } : {} };
406
- const turns = [...s.turns];
407
- let i = turns.length - 1;
408
- while (i >= 0 && turns[i].role !== "assistant") i--;
409
- if (i >= 0) turns[i] = { ...turns[i], decisions: [...turns[i].decisions ?? [], decision] };
410
- else turns.push({ role: "assistant", text: "", decisions: [decision] });
411
- s = { ...s, turns };
412
- return s;
413
- }
414
- function applyHistory(prev, turns, permissions = []) {
415
- let s = { ...prev, effects: [] };
416
- if (turns.length && !prev.turns.length) s.turns = turns;
417
- const fresh = permissions.filter((p) => !prev.permissions.some((q) => q.id === p.id));
418
- if (fresh.length) s.permissions = [...prev.permissions, ...fresh];
419
- return s.turns === prev.turns && !fresh.length ? prev : s;
420
- }
421
- function appendAgentMessages(prev, texts) {
422
- if (!texts.length) return prev;
423
- return { ...prev, effects: [], turns: [...prev.turns, ...texts.map((text) => ({ role: "assistant", text, arrived: true }))] };
424
- }
425
- function reconcileWith(prev, fresh) {
426
- if (prev.streaming || prev.turnStartedAt) return prev;
427
- const turns = reconcileTurns(prev.turns, fresh);
428
- return turns === prev.turns ? prev : { ...prev, turns, effects: [] };
429
- }
430
- function lastUserTurn(turns) {
431
- for (let i = turns.length - 1; i >= 0; i--) if (turns[i].role === "user") return turns[i];
432
- return null;
433
- }
434
- function resumeMode(turns) {
435
- const i = turns.map((t) => t.role).lastIndexOf("user");
436
- const last = i >= 0 ? turns[i] : null;
437
- const worked = turns.slice(i + 1).some((t) => t.role === "assistant" && (t.text.trim() || t.tools?.length));
438
- if (last && !worked && last.text !== RESUME_TEXT) return "resend";
439
- return "continue";
440
- }
441
-
442
- // src/core/narration.ts
443
- var MARK = "\u2713";
444
- var LINE = /^- ✓ (.+)$/;
445
- function isNarration(chunk) {
446
- const t = chunk.trim();
447
- return t.length > 0 && t.length <= 300 && !/\n\s*\n/.test(t) && !t.includes("```");
448
- }
449
- function splitNarration(text, cuts) {
450
- if (cuts?.length) {
451
- const valid = cuts.filter((c) => c > 0 && c <= text.length);
452
- if (valid.length) {
453
- const chunks = [];
454
- let from = 0;
455
- for (const c of valid) {
456
- const t = text.slice(from, c).trim();
457
- if (t) chunks.push(t);
458
- from = c;
459
- }
460
- if (chunks.every(isNarration)) {
461
- return { steps: chunks.map((t) => t.replace(/\s*\n\s*/g, " ")), rest: text.slice(from).replace(/^\s+/, "") };
462
- }
463
- }
464
- return { steps: [], rest: text };
465
- }
466
- const lines = text.split("\n");
467
- const steps = [];
468
- let i = 0;
469
- for (; i < lines.length; i++) {
470
- const m = LINE.exec(lines[i]);
471
- if (!m) break;
472
- steps.push(m[1].trim());
473
- }
474
- if (!steps.length) return { steps, rest: text };
475
- return { steps, rest: lines.slice(i).join("\n").replace(/^\s+/, "") };
476
- }
477
- function narrationToMarkdown(chunks, answer) {
478
- const steps = chunks.map((t) => t.trim()).filter(Boolean);
479
- if (!steps.length || !steps.every(isNarration)) return answer;
480
- const list = steps.map((p) => `- ${MARK} ${p.replace(/\s*\n\s*/g, " ")}`).join("\n");
481
- return answer.trim() ? `${list}
482
-
483
- ${answer.trim()}` : list;
484
- }
485
-
486
- // src/core/options.ts
487
- var BULLET = /^\s*(?:[-*•+]|(\d{1,2})[.)])\s+(.+?)\s*$/;
488
- var INVITES = /(puedo|quieres|elige|eliges|prefieres|opciones|caminos|can i|would you|want me|choose|prefer|options)/i;
489
- function extractReplyOptions(text) {
490
- const lines = text.replace(/\s+$/, "").split("\n");
491
- const items = [];
492
- let numbered = false;
493
- let i = lines.length - 1;
494
- for (; i >= 0; i--) {
495
- const m = BULLET.exec(lines[i]);
496
- if (!m) break;
497
- if (m[1]) numbered = true;
498
- items.unshift(m[2]);
499
- }
500
- if (items.length < 2 || items.length > 5) return null;
501
- if (items.some((t) => t.length > 140 || /```|`|https?:\/\/|\]\(/.test(t))) return null;
502
- let j = i;
503
- while (j >= 0 && !lines[j].trim()) j--;
504
- if (j < 0) return null;
505
- const invitation = lines[j].trim();
506
- if (!invitation.endsWith(":") && !INVITES.test(invitation)) return null;
507
- return { before: lines.slice(0, i + 1).join("\n").trimEnd(), items, numbered };
508
- }
509
- function cleanReplyOption(t) {
510
- let s = t.replace(/\*\*|__/g, "").trim();
511
- s = s.replace(/^(?:o|y|u|or|and)\s+/i, "").replace(/[\s,.;]+$/, "").trim();
512
- return s.charAt(0).toLocaleUpperCase() + s.slice(1);
513
- }
514
-
515
- // src/core/steps.ts
516
- var isRunning = (t) => t.status !== "completed" && t.status !== "failed";
517
- function toolName(t) {
518
- const title = t.title ?? "";
519
- const m = /^mcp__[^_]+(?:_[^_]+)*?__([a-z0-9_]+)$/i.exec(title);
520
- if (m) return m[1];
521
- return title || null;
522
- }
523
- function namedVerb(t, verbs) {
524
- const name = toolName(t);
525
- if (name && verbs[name]) return { key: name, verb: verbs[name] };
526
- return null;
527
- }
528
- function stepLabel(t, verbs, fallback = "Paso") {
529
- const named = namedVerb(t, verbs);
530
- if (named) return isRunning(t) && named.verb.running ? named.verb.running : named.verb.one;
531
- return t.title ?? t.kind ?? fallback;
532
- }
533
- function summarizeTools(tools, verbs) {
534
- const groups = /* @__PURE__ */ new Map();
535
- for (const t of tools) {
536
- const named = namedVerb(t, verbs);
537
- const key = named?.key ?? t.kind ?? "other";
538
- const g = groups.get(key) ?? { n: 0, verb: named?.verb ?? verbs[key] };
539
- g.n++;
540
- groups.set(key, g);
541
- }
542
- const parts = [];
543
- for (const [key, { n, verb }] of groups) {
544
- if (verb) parts.push(n === 1 ? verb.one : verb.many.replace("%n", String(n)));
545
- else parts.push(`${n} \xD7 ${key.replace(/[_-]/g, " ")}`);
546
- }
547
- return parts.join(" \xB7 ");
548
- }
549
- function groupSteps(tools, label) {
550
- const groups = /* @__PURE__ */ new Map();
551
- for (const t of tools) {
552
- const k = `${label(t)}\0${t.detail ?? ""}`;
553
- const g = groups.get(k);
554
- if (!g) {
555
- groups.set(k, { tool: t, times: 1 });
556
- continue;
557
- }
558
- g.times++;
559
- const wins = isRunning(t) || t.status === "failed" || !(isRunning(g.tool) || g.tool.status === "failed");
560
- if (wins) g.tool = { ...t, id: g.tool.id };
561
- }
562
- return [...groups.values()];
563
- }
564
- var TOOL_VERBS_ES = {
565
- read: { one: "Le\xED un archivo", many: "Le\xED %n archivos" },
566
- read_file: { one: "Le\xED un archivo", many: "Le\xED %n archivos" },
567
- write: { one: "Escrib\xED un archivo", many: "Escrib\xED %n archivos" },
568
- write_file: { one: "Escrib\xED un archivo", many: "Escrib\xED %n archivos" },
569
- edit: { one: "Edit\xE9 un archivo", many: "Edit\xE9 %n archivos" },
570
- edit_file: { one: "Edit\xE9 un archivo", many: "Edit\xE9 %n archivos" },
571
- bash: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
572
- shell: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
573
- terminal: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
574
- execute: { one: "Corr\xED un comando", many: "Corr\xED %n comandos" },
575
- grep: { one: "Busqu\xE9 en el c\xF3digo", many: "Busqu\xE9 en el c\xF3digo" },
576
- glob: { one: "Busqu\xE9 archivos", many: "Busqu\xE9 archivos" },
577
- search: { one: "Busqu\xE9", many: "Busqu\xE9 %n veces" },
578
- tool_search: { one: "Busqu\xE9 una herramienta", many: "Busqu\xE9 %n herramientas" },
579
- web_search: { one: "Busqu\xE9 en la web", many: "Busqu\xE9 en la web" },
580
- web_fetch: { one: "Abr\xED una p\xE1gina", many: "Abr\xED %n p\xE1ginas" },
581
- fetch: { one: "Abr\xED una p\xE1gina", many: "Abr\xED %n p\xE1ginas" },
582
- site_publish: { one: "Publiqu\xE9 una p\xE1gina", many: "Publiqu\xE9 %n p\xE1ginas" },
583
- pdf: { one: "Gener\xE9 un PDF", many: "Gener\xE9 %n PDF" },
584
- connector: { one: "Us\xE9 tu sistema", many: "Us\xE9 tu sistema %n veces" },
585
- mcp: { one: "Us\xE9 una herramienta", many: "Us\xE9 %n herramientas" },
586
- browser: { one: "Us\xE9 el navegador", many: "Us\xE9 el navegador" },
587
- todo: { one: "Anot\xE9 el plan", many: "Anot\xE9 el plan" },
588
- delete: { one: "Borr\xE9 un archivo", many: "Borr\xE9 %n archivos" },
589
- move: { one: "Mov\xED un archivo", many: "Mov\xED %n archivos" },
590
- think: { one: "Lo pens\xE9", many: "Lo pens\xE9" },
591
- image: { one: "Cre\xE9 una imagen", many: "Cre\xE9 %n im\xE1genes" },
592
- delegate: { one: "Delegu\xE9 una tarea", many: "Delegu\xE9 %n tareas" },
593
- collect: { one: "Recog\xED un resultado", many: "Recog\xED %n resultados" },
594
- status: { one: "Pregunt\xE9 c\xF3mo iba", many: "Pregunt\xE9 %n veces c\xF3mo iba" },
595
- stop: { one: "Detuve una tarea", many: "Detuve %n tareas" },
596
- guide: { one: "Le\xED una gu\xEDa", many: "Le\xED %n gu\xEDas" },
597
- deliver: { one: "Entregu\xE9 un archivo", many: "Entregu\xE9 %n archivos" },
598
- sheet: { one: "Consult\xE9 una hoja", many: "Consult\xE9 %n hojas" },
599
- view: { one: "Arm\xE9 una vista", many: "Arm\xE9 %n vistas" }
600
- };
601
- var TOOL_VERBS_EN = {
602
- read: { one: "Read a file", many: "Read %n files" },
603
- read_file: { one: "Read a file", many: "Read %n files" },
604
- write: { one: "Wrote a file", many: "Wrote %n files" },
605
- write_file: { one: "Wrote a file", many: "Wrote %n files" },
606
- edit: { one: "Edited a file", many: "Edited %n files" },
607
- edit_file: { one: "Edited a file", many: "Edited %n files" },
608
- bash: { one: "Ran a command", many: "Ran %n commands" },
609
- shell: { one: "Ran a command", many: "Ran %n commands" },
610
- terminal: { one: "Ran a command", many: "Ran %n commands" },
611
- execute: { one: "Ran a command", many: "Ran %n commands" },
612
- grep: { one: "Searched the code", many: "Searched the code" },
613
- glob: { one: "Searched for files", many: "Searched for files" },
614
- search: { one: "Searched", many: "Searched %n times" },
615
- tool_search: { one: "Looked for a tool", many: "Looked for %n tools" },
616
- web_search: { one: "Searched the web", many: "Searched the web" },
617
- web_fetch: { one: "Opened a page", many: "Opened %n pages" },
618
- fetch: { one: "Opened a page", many: "Opened %n pages" },
619
- site_publish: { one: "Published a page", many: "Published %n pages" },
620
- pdf: { one: "Generated a PDF", many: "Generated %n PDFs" },
621
- connector: { one: "Used your system", many: "Used your system %n times" },
622
- mcp: { one: "Used a tool", many: "Used %n tools" },
623
- browser: { one: "Used the browser", many: "Used the browser" },
624
- todo: { one: "Wrote down the plan", many: "Wrote down the plan" },
625
- delete: { one: "Deleted a file", many: "Deleted %n files" },
626
- move: { one: "Moved a file", many: "Moved %n files" },
627
- think: { one: "Thought it through", many: "Thought it through" },
628
- image: { one: "Created an image", many: "Created %n images" },
629
- delegate: { one: "Delegated a task", many: "Delegated %n tasks" },
630
- collect: { one: "Collected a result", many: "Collected %n results" },
631
- status: { one: "Checked how it was going", many: "Checked %n times how it was going" },
632
- stop: { one: "Stopped a task", many: "Stopped %n tasks" },
633
- guide: { one: "Read a guide", many: "Read %n guides" },
634
- deliver: { one: "Delivered a file", many: "Delivered %n files" },
635
- sheet: { one: "Checked a sheet", many: "Checked %n sheets" },
636
- view: { one: "Built a view", many: "Built %n views" }
637
- };
638
-
639
- export {
640
- reconcileTurns,
641
- RESTART_NOTICE,
642
- RESUME_TEXT,
643
- createPilotState,
644
- reducePilotEvent,
645
- expiryMs,
646
- beginSend,
647
- sendAccepted,
648
- sendRejectedBusy,
649
- sendFailed,
650
- stopTurn,
651
- dropLastExchange,
652
- resolvePermission,
653
- applyHistory,
654
- appendAgentMessages,
655
- reconcileWith,
656
- lastUserTurn,
657
- resumeMode,
658
- isNarration,
659
- splitNarration,
660
- narrationToMarkdown,
661
- extractReplyOptions,
662
- cleanReplyOption,
663
- isRunning,
664
- toolName,
665
- stepLabel,
666
- summarizeTools,
667
- groupSteps,
668
- TOOL_VERBS_ES,
669
- TOOL_VERBS_EN
670
- };