@nac3/forge-cli 1.16.1 → 1.17.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.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Reatar el panel a la sesion viva.
3
+ *
4
+ * POR QUE EXISTE (Pablo, 13-oct-2026): se le cerro por error la ventana de
5
+ * Chrome del panel con un turno de E7.5 corriendo. El turno NO se perdio -- el
6
+ * servidor es un proceso aparte y siguio hasta el final -- pero al reabrir el
7
+ * panel estaba VACIO, y la pestana Coding tampoco volvio.
8
+ *
9
+ * Las dos perdidas tienen la misma causa: el panel guardaba en el navegador
10
+ * solo `yf-lang` y `yf_coding_mode`, el transcript renderizado era estado en
11
+ * memoria de ESA ventana, y no habia ningun endpoint que lo reconstruyera
12
+ * (medido: ninguno de los 25 que el panel consume sirve historial). Y el
13
+ * lienzo Coding se abre cuando LLEGAN eventos de actividad, asi que una
14
+ * ventana nueva, que no esta enganchada al stream SSE del turno en vuelo, no
15
+ * tiene de donde enterarse de que hay algo corriendo.
16
+ *
17
+ * O sea que no era "el historial no persiste": era "la ventana nueva no se
18
+ * reata a la sesion". Un solo endpoint arregla las dos cosas, porque las dos
19
+ * necesitan el mismo dato: que dijo la conversacion, y si hay un turno en
20
+ * vuelo ahora.
21
+ *
22
+ * Lo que este modulo NO hace: inventar durabilidad. El transcript ya se
23
+ * persiste en `.yujin-forge/cache/chat/` desde la primera version (SPEC 5) y
24
+ * el turno en vuelo ya es un estado consultable (`in_flight_turn.ts`). Lo que
25
+ * faltaba era servirlos.
26
+ *
27
+ * ASCII-only.
28
+ */
29
+ import type { ChatMessage } from './claude.js';
30
+ import type { InFlightTurn } from '../relay/in_flight_turn.js';
31
+ /**
32
+ * Cuantos mensajes se devuelven, desde el final.
33
+ *
34
+ * Por que un tope y no la sesion entera: una sesion de un dia entero son
35
+ * megabytes, y el panel ya recorta lo que PINTA. Devolver todo haria del
36
+ * arranque del panel la peticion mas cara del producto para mostrar lo mismo.
37
+ * 60 es el mismo numero que usa el espejo del telefono, por consistencia.
38
+ */
39
+ export declare const TOPE_MENSAJES = 60;
40
+ /**
41
+ * Tope de bytes del payload. El tope de mensajes no alcanza: un solo turno con
42
+ * un volcado de consola pegado adentro puede ser de megabytes (hoy mismo hubo
43
+ * uno de 11.870 caracteres). Se recorta desde el final -- lo mas nuevo es lo
44
+ * que el usuario quiere ver.
45
+ */
46
+ export declare const TOPE_BYTES: number;
47
+ export interface MensajeRestaurado {
48
+ role: 'user' | 'assistant';
49
+ content: string;
50
+ }
51
+ export interface TurnoEnVuelo {
52
+ id: string;
53
+ user: string;
54
+ started_at: string;
55
+ /** Segundos que lleva corriendo, para que el panel pueda decir "3h en curso"
56
+ * sin tener que calcularlo contra un reloj que puede no coincidir. */
57
+ elapsed_s: number;
58
+ }
59
+ export interface PayloadRestore {
60
+ ok: true;
61
+ /** Los mensajes, del mas viejo al mas nuevo, ya recortados. */
62
+ messages: MensajeRestaurado[];
63
+ /** True cuando se dejaron mensajes afuera por alguno de los dos topes. */
64
+ truncated: boolean;
65
+ /** Cuantos mensajes tiene la sesion en total. */
66
+ total: number;
67
+ /** El turno en vuelo, o null. Es lo que hace que el panel reabierto muestre
68
+ * la burbuja pendiente Y vuelva a abrir el lienzo Coding. */
69
+ in_flight: TurnoEnVuelo | null;
70
+ project_root: string;
71
+ session_started_at: string | null;
72
+ }
73
+ /**
74
+ * Arma el payload. Pura: toda la entrada es argumento, y por eso se puede
75
+ * probar el recorte sin levantar un servidor.
76
+ *
77
+ * `redactar` entra como funcion y no se importa adentro para que el test pueda
78
+ * afirmar QUE SE LLAMO. El transcript en disco ya se escribe redactado, pero
79
+ * los mensajes en MEMORIA no pasaron por ahi todavia, y este endpoint los
80
+ * sirve: redactar aca no es ceremonia, es el unico lugar donde pasa.
81
+ */
82
+ export declare function construirRestore(args: {
83
+ messages: readonly ChatMessage[];
84
+ inFlight: InFlightTurn | null;
85
+ projectRoot: string;
86
+ sessionStartedAt?: string | null;
87
+ now?: number;
88
+ redactar?: (s: string) => string;
89
+ }): PayloadRestore;
90
+ //# sourceMappingURL=session_restore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session_restore.d.ts","sourceRoot":"","sources":["../../src/chat/session_restore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC;;;;;GAKG;AACH,eAAO,MAAM,UAAU,QAAa,CAAC;AAErC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB;2EACuE;IACvE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,IAAI,CAAC;IACT,+DAA+D;IAC/D,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd;kEAC8D;IAC9D,SAAS,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAOD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CAClC,GAAG,cAAc,CA8CjB"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Cuantos mensajes se devuelven, desde el final.
3
+ *
4
+ * Por que un tope y no la sesion entera: una sesion de un dia entero son
5
+ * megabytes, y el panel ya recorta lo que PINTA. Devolver todo haria del
6
+ * arranque del panel la peticion mas cara del producto para mostrar lo mismo.
7
+ * 60 es el mismo numero que usa el espejo del telefono, por consistencia.
8
+ */
9
+ export const TOPE_MENSAJES = 60;
10
+ /**
11
+ * Tope de bytes del payload. El tope de mensajes no alcanza: un solo turno con
12
+ * un volcado de consola pegado adentro puede ser de megabytes (hoy mismo hubo
13
+ * uno de 11.870 caracteres). Se recorta desde el final -- lo mas nuevo es lo
14
+ * que el usuario quiere ver.
15
+ */
16
+ export const TOPE_BYTES = 512 * 1024;
17
+ /** Cuantos bytes ocupa en el payload (UTF-8, mas el envoltorio JSON). */
18
+ function pesa(m) {
19
+ return Buffer.byteLength(m.content, 'utf8') + Buffer.byteLength(m.role, 'utf8') + 24;
20
+ }
21
+ /**
22
+ * Arma el payload. Pura: toda la entrada es argumento, y por eso se puede
23
+ * probar el recorte sin levantar un servidor.
24
+ *
25
+ * `redactar` entra como funcion y no se importa adentro para que el test pueda
26
+ * afirmar QUE SE LLAMO. El transcript en disco ya se escribe redactado, pero
27
+ * los mensajes en MEMORIA no pasaron por ahi todavia, y este endpoint los
28
+ * sirve: redactar aca no es ceremonia, es el unico lugar donde pasa.
29
+ */
30
+ export function construirRestore(args) {
31
+ const { messages, inFlight, projectRoot } = args;
32
+ const now = args.now ?? Date.now();
33
+ const redactar = args.redactar ?? ((s) => s);
34
+ /* Solo lo que el panel pinta como conversacion. Un mensaje sin contenido no
35
+ * aporta una burbuja: aporta una vacia. */
36
+ const utiles = messages.filter((m) => (m.role === 'user' || m.role === 'assistant') && typeof m.content === 'string' && m.content !== '');
37
+ const porTope = utiles.slice(-TOPE_MENSAJES);
38
+ /* El recorte por bytes va desde el final hacia atras. */
39
+ const elegidos = [];
40
+ let bytes = 0;
41
+ for (let i = porTope.length - 1; i >= 0; i--) {
42
+ const m = porTope[i];
43
+ const item = { role: m.role, content: redactar(m.content) };
44
+ const b = pesa(item);
45
+ if (bytes + b > TOPE_BYTES && elegidos.length > 0)
46
+ break;
47
+ elegidos.unshift(item);
48
+ bytes += b;
49
+ }
50
+ let enVuelo = null;
51
+ if (inFlight && inFlight.id) {
52
+ const desde = Date.parse(inFlight.started_at);
53
+ const elapsed = Number.isFinite(desde) ? Math.max(0, Math.round((now - desde) / 1000)) : 0;
54
+ enVuelo = {
55
+ id: inFlight.id,
56
+ user: redactar(inFlight.user),
57
+ started_at: inFlight.started_at,
58
+ elapsed_s: elapsed,
59
+ };
60
+ }
61
+ return {
62
+ ok: true,
63
+ messages: elegidos,
64
+ truncated: elegidos.length < utiles.length,
65
+ total: utiles.length,
66
+ in_flight: enVuelo,
67
+ project_root: projectRoot,
68
+ session_started_at: args.sessionStartedAt ?? null,
69
+ };
70
+ }
71
+ //# sourceMappingURL=session_restore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session_restore.js","sourceRoot":"","sources":["../../src/chat/session_restore.ts"],"names":[],"mappings":"AA+BA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,GAAG,IAAI,CAAC;AA+BrC,yEAAyE;AACzE,SAAS,IAAI,CAAC,CAAoB;IAChC,OAAO,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;AACvF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAOhC;IACC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAErD;+CAC2C;IAC3C,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAC5B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,KAAK,EAAE,CAC1G,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,CAAC;IAE7C,yDAAyD;IACzD,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QACtB,MAAM,IAAI,GAAsB,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/E,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,IAAI,KAAK,GAAG,CAAC,GAAG,UAAU,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM;QACzD,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvB,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,IAAI,OAAO,GAAwB,IAAI,CAAC;IACxC,IAAI,QAAQ,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3F,OAAO,GAAG;YACR,EAAE,EAAE,QAAQ,CAAC,EAAE;YACf,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC7B,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,SAAS,EAAE,OAAO;SACnB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,QAAQ,EAAE,QAAQ;QAClB,SAAS,EAAE,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM;QAC1C,KAAK,EAAE,MAAM,CAAC,MAAM;QACpB,SAAS,EAAE,OAAO;QAClB,YAAY,EAAE,WAAW;QACzB,kBAAkB,EAAE,IAAI,CAAC,gBAAgB,IAAI,IAAI;KAClD,CAAC;AACJ,CAAC"}
package/dist/version.d.ts CHANGED
@@ -2,5 +2,5 @@
2
2
  * Yujin Forge CLI version. Kept in sync with package.json by the npm
3
3
  * "version" lifecycle (scripts/sync_version.mjs) so the two never drift.
4
4
  */
5
- export declare const VERSION = "1.16.1";
5
+ export declare const VERSION = "1.17.0";
6
6
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  * Yujin Forge CLI version. Kept in sync with package.json by the npm
3
3
  * "version" lifecycle (scripts/sync_version.mjs) so the two never drift.
4
4
  */
5
- export const VERSION = '1.16.1';
5
+ export const VERSION = '1.17.0';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nac3/forge-cli",
3
- "version": "1.16.1",
3
+ "version": "1.17.0",
4
4
  "description": "Yujin Forge -- voice-first NAC-3 React development framework. CLI + chat panel + spec ingest + 10-format document reader + voice loop.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Pablo Kuschnirof <pablo@rpaforce.com>",