@nac3/forge-cli 1.16.0 → 1.16.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.
@@ -33,23 +33,56 @@ export interface InFlightTurn {
33
33
  started_at: string;
34
34
  }
35
35
  /**
36
- * Tope de edad. Un turno mas viejo que esto se considera muerto y deja de
37
- * anunciarse, aunque nadie lo haya limpiado.
36
+ * Cada cuanto el turno da senal de vida. Es el periodo del latido de actividad
37
+ * del servidor (`startRelayHeartbeat`), que corre SOLO entre el arranque y el
38
+ * cierre del turno.
38
39
  *
39
- * No es decoracion: `clearInFlightTurn` corre al final del handler, y un
40
- * proceso que muere a mitad de turno (o una excepcion por un camino que no
41
- * pasa por ahi) dejaria al telefono con una burbuja "pensando" para siempre.
42
- * Mentir sobre lo que el sistema esta haciendo es peor que no decir nada.
40
+ * Vive aca y no en server.ts a proposito: es la mitad de un par acoplado, y la
41
+ * otra mitad es `SILENCIO_MAX_MS`. El bug anterior nacio de tener dos numeros
42
+ * acoplados en dos archivos distintos sin nada que los ligara.
43
43
  */
44
- export declare const IN_FLIGHT_MAX_AGE_MS: number;
45
- export declare function setInFlightTurn(t: InFlightTurn): void;
44
+ export declare const PULSO_DE_PROGRESO_MS: number;
45
+ /**
46
+ * Tope de SILENCIO. Un turno que no da senal de vida por mas que esto se
47
+ * considera muerto y deja de anunciarse, aunque nadie lo haya limpiado.
48
+ *
49
+ * ANTES ERA UN TOPE DE EDAD, y eso estaba mal (Pablo, 13-oct-2026: "hay turnos
50
+ * de mas de 3 horas en un desarrollo"). La proteccion que se queria es contra
51
+ * un turno COLGADO -- un proceso que muere a mitad de camino, o una excepcion
52
+ * por un camino que no pasa por `clearInFlightTurn` -- porque mentir sobre lo
53
+ * que el sistema esta haciendo es peor que no decir nada. Pero "colgado" no es
54
+ * "viejo": es "sin progreso". Un tope de edad confunde las dos cosas y castiga
55
+ * justo al turno largo, que es el que mas necesita anunciarse.
56
+ *
57
+ * Medido: un turno de 29 minutos en ADIP-Kikan dejaba al telefono diciendo que
58
+ * no habia nada corriendo durante sus ultimos 19 minutos. Con turnos de 3 horas
59
+ * el anuncio serviria para el 5% del turno.
60
+ *
61
+ * Diez pulsos de margen: tolera que se pierdan nueve latidos seguidos antes de
62
+ * dar por muerto un turno que esta vivo.
63
+ */
64
+ export declare const SILENCIO_MAX_MS: number;
65
+ export declare function setInFlightTurn(t: InFlightTurn, now?: number): void;
66
+ /**
67
+ * Senal de vida del turno en curso. La llama el latido de actividad del
68
+ * servidor, que corre solo mientras hay turno.
69
+ *
70
+ * No crea nada: si no hay turno registrado, no hace nada. Asi un latido
71
+ * rezagado no puede resucitar un turno ya cerrado.
72
+ */
73
+ export declare function touchInFlightTurn(now?: number): void;
46
74
  /**
47
75
  * Limpia el turno en vuelo. Con `id`, SOLO si coincide: dos turnos encadenados
48
76
  * pueden cerrar fuera de orden, y un cierre tardio del viejo no tiene que
49
77
  * borrar el anuncio del que recien arranco.
50
78
  */
51
79
  export declare function clearInFlightTurn(id?: string): void;
52
- /** El turno en vuelo, o null si no hay o si ya es demasiado viejo. */
80
+ /**
81
+ * El turno en vuelo, o null si no hay o si se quedo CALLADO.
82
+ *
83
+ * La edad del turno no entra en la decision: un turno de tres horas que sigue
84
+ * latiendo es un turno vivo y se anuncia. Lo que lo saca es el silencio.
85
+ */
53
86
  export declare function inFlightTurn(now?: number): InFlightTurn | null;
54
87
  /** Solo para tests. */
55
88
  export declare function _resetInFlightTurn(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"in_flight_turn.d.ts","sourceRoot":"","sources":["../../src/relay/in_flight_turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,QAAiB,CAAC;AAInD,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,IAAI,CAGrD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAInD;AAED,sEAAsE;AACtE,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAmB,GAAG,YAAY,GAAG,IAAI,CAQ1E;AAED,uBAAuB;AACvB,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
1
+ {"version":3,"file":"in_flight_turn.d.ts","sourceRoot":"","sources":["../../src/relay/in_flight_turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,QAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe,QAA4B,CAAC;AASzD,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,GAAE,MAAmB,GAAG,IAAI,CAQ/E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAmB,GAAG,IAAI,CAGhE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAInD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAmB,GAAG,YAAY,GAAG,IAAI,CAU1E;AAED,uBAAuB;AACvB,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
@@ -27,20 +27,57 @@
27
27
  * ASCII puro.
28
28
  */
29
29
  /**
30
- * Tope de edad. Un turno mas viejo que esto se considera muerto y deja de
31
- * anunciarse, aunque nadie lo haya limpiado.
30
+ * Cada cuanto el turno da senal de vida. Es el periodo del latido de actividad
31
+ * del servidor (`startRelayHeartbeat`), que corre SOLO entre el arranque y el
32
+ * cierre del turno.
32
33
  *
33
- * No es decoracion: `clearInFlightTurn` corre al final del handler, y un
34
- * proceso que muere a mitad de turno (o una excepcion por un camino que no
35
- * pasa por ahi) dejaria al telefono con una burbuja "pensando" para siempre.
36
- * Mentir sobre lo que el sistema esta haciendo es peor que no decir nada.
34
+ * Vive aca y no en server.ts a proposito: es la mitad de un par acoplado, y la
35
+ * otra mitad es `SILENCIO_MAX_MS`. El bug anterior nacio de tener dos numeros
36
+ * acoplados en dos archivos distintos sin nada que los ligara.
37
37
  */
38
- export const IN_FLIGHT_MAX_AGE_MS = 10 * 60 * 1000;
38
+ export const PULSO_DE_PROGRESO_MS = 60 * 1000;
39
+ /**
40
+ * Tope de SILENCIO. Un turno que no da senal de vida por mas que esto se
41
+ * considera muerto y deja de anunciarse, aunque nadie lo haya limpiado.
42
+ *
43
+ * ANTES ERA UN TOPE DE EDAD, y eso estaba mal (Pablo, 13-oct-2026: "hay turnos
44
+ * de mas de 3 horas en un desarrollo"). La proteccion que se queria es contra
45
+ * un turno COLGADO -- un proceso que muere a mitad de camino, o una excepcion
46
+ * por un camino que no pasa por `clearInFlightTurn` -- porque mentir sobre lo
47
+ * que el sistema esta haciendo es peor que no decir nada. Pero "colgado" no es
48
+ * "viejo": es "sin progreso". Un tope de edad confunde las dos cosas y castiga
49
+ * justo al turno largo, que es el que mas necesita anunciarse.
50
+ *
51
+ * Medido: un turno de 29 minutos en ADIP-Kikan dejaba al telefono diciendo que
52
+ * no habia nada corriendo durante sus ultimos 19 minutos. Con turnos de 3 horas
53
+ * el anuncio serviria para el 5% del turno.
54
+ *
55
+ * Diez pulsos de margen: tolera que se pierdan nueve latidos seguidos antes de
56
+ * dar por muerto un turno que esta vivo.
57
+ */
58
+ export const SILENCIO_MAX_MS = PULSO_DE_PROGRESO_MS * 10;
39
59
  let current = null;
40
- export function setInFlightTurn(t) {
60
+ export function setInFlightTurn(t, now = Date.now()) {
41
61
  if (!t || !t.id)
42
62
  return;
43
- current = { id: String(t.id), user: String(t.user || ''), started_at: String(t.started_at || new Date().toISOString()) };
63
+ current = {
64
+ id: String(t.id),
65
+ user: String(t.user || ''),
66
+ started_at: String(t.started_at || new Date(now).toISOString()),
67
+ visto: now,
68
+ };
69
+ }
70
+ /**
71
+ * Senal de vida del turno en curso. La llama el latido de actividad del
72
+ * servidor, que corre solo mientras hay turno.
73
+ *
74
+ * No crea nada: si no hay turno registrado, no hace nada. Asi un latido
75
+ * rezagado no puede resucitar un turno ya cerrado.
76
+ */
77
+ export function touchInFlightTurn(now = Date.now()) {
78
+ if (!current)
79
+ return;
80
+ current.visto = now;
44
81
  }
45
82
  /**
46
83
  * Limpia el turno en vuelo. Con `id`, SOLO si coincide: dos turnos encadenados
@@ -54,16 +91,23 @@ export function clearInFlightTurn(id) {
54
91
  return;
55
92
  current = null;
56
93
  }
57
- /** El turno en vuelo, o null si no hay o si ya es demasiado viejo. */
94
+ /**
95
+ * El turno en vuelo, o null si no hay o si se quedo CALLADO.
96
+ *
97
+ * La edad del turno no entra en la decision: un turno de tres horas que sigue
98
+ * latiendo es un turno vivo y se anuncia. Lo que lo saca es el silencio.
99
+ */
58
100
  export function inFlightTurn(now = Date.now()) {
59
101
  if (!current)
60
102
  return null;
61
- const started = Date.parse(current.started_at);
62
- if (Number.isFinite(started) && now - started > IN_FLIGHT_MAX_AGE_MS) {
103
+ /* Un reloj que salta hacia atras da silencio negativo: no vence. Preferible
104
+ * anunciar un turno de mas que descartar uno vivo por un salto de NTP. */
105
+ if (now - current.visto > SILENCIO_MAX_MS) {
63
106
  current = null;
64
107
  return null;
65
108
  }
66
- return current;
109
+ const { id, user, started_at } = current;
110
+ return { id, user, started_at };
67
111
  }
68
112
  /** Solo para tests. */
69
113
  export function _resetInFlightTurn() {
@@ -1 +1 @@
1
- {"version":3,"file":"in_flight_turn.js","sourceRoot":"","sources":["../../src/relay/in_flight_turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AASH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEnD,IAAI,OAAO,GAAwB,IAAI,CAAC;AAExC,MAAM,UAAU,eAAe,CAAC,CAAe;IAC7C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;QAAE,OAAO;IACxB,OAAO,GAAG,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;AAC3H,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAW;IAC3C,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,IAAI,EAAE,IAAI,OAAO,CAAC,EAAE,KAAK,EAAE;QAAE,OAAO;IACpC,OAAO,GAAG,IAAI,CAAC;AACjB,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,YAAY,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;IACnD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,GAAG,GAAG,OAAO,GAAG,oBAAoB,EAAE,CAAC;QACrE,OAAO,GAAG,IAAI,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,kBAAkB;IAChC,OAAO,GAAG,IAAI,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"in_flight_turn.js","sourceRoot":"","sources":["../../src/relay/in_flight_turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AASH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,oBAAoB,GAAG,EAAE,CAAC;AAOzD,IAAI,OAAO,GAAoB,IAAI,CAAC;AAEpC,MAAM,UAAU,eAAe,CAAC,CAAe,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IACvE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;QAAE,OAAO;IACxB,OAAO,GAAG;QACR,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC/D,KAAK,EAAE,GAAG;KACX,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;IACxD,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,OAAO,CAAC,KAAK,GAAG,GAAG,CAAC;AACtB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAW;IAC3C,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,IAAI,EAAE,IAAI,OAAO,CAAC,EAAE,KAAK,EAAE;QAAE,OAAO;IACpC,OAAO,GAAG,IAAI,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;IACnD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B;8EAC0E;IAC1E,IAAI,GAAG,GAAG,OAAO,CAAC,KAAK,GAAG,eAAe,EAAE,CAAC;QAC1C,OAAO,GAAG,IAAI,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACzC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAClC,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,kBAAkB;IAChC,OAAO,GAAG,IAAI,CAAC;AACjB,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.0";
5
+ export declare const VERSION = "1.16.1";
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.0';
5
+ export const VERSION = '1.16.1';
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.0",
3
+ "version": "1.16.1",
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>",