@nac3/forge-cli 1.23.3 → 1.23.5

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.
Files changed (69) hide show
  1. package/dist/bin/yf.js +0 -0
  2. package/dist/chat/doctrine_digest.d.ts.map +1 -1
  3. package/dist/chat/doctrine_digest.js +4 -1
  4. package/dist/chat/doctrine_digest.js.map +1 -1
  5. package/dist/chat/server.d.ts.map +1 -1
  6. package/dist/chat/server.js +72 -19
  7. package/dist/chat/server.js.map +1 -1
  8. package/dist/chat/tools/evidence.d.ts +34 -2
  9. package/dist/chat/tools/evidence.d.ts.map +1 -1
  10. package/dist/chat/tools/evidence.js +90 -12
  11. package/dist/chat/tools/evidence.js.map +1 -1
  12. package/dist/commands/urazuke.d.ts.map +1 -1
  13. package/dist/commands/urazuke.js +27 -0
  14. package/dist/commands/urazuke.js.map +1 -1
  15. package/dist/core/error_catalog.d.ts +4 -0
  16. package/dist/core/error_catalog.d.ts.map +1 -1
  17. package/dist/core/error_catalog.js +16 -0
  18. package/dist/core/error_catalog.js.map +1 -1
  19. package/dist/core/loopback_post.d.ts +101 -0
  20. package/dist/core/loopback_post.d.ts.map +1 -0
  21. package/dist/core/loopback_post.js +156 -0
  22. package/dist/core/loopback_post.js.map +1 -0
  23. package/dist/docs/doctrine/urazuke.md +50 -38
  24. package/dist/mcp/forge_panel_server.d.ts +1 -26
  25. package/dist/mcp/forge_panel_server.d.ts.map +1 -1
  26. package/dist/mcp/forge_panel_server.js +5 -26
  27. package/dist/mcp/forge_panel_server.js.map +1 -1
  28. package/dist/mcp/forge_verbs_server.d.ts +1 -11
  29. package/dist/mcp/forge_verbs_server.d.ts.map +1 -1
  30. package/dist/mcp/forge_verbs_server.js +7 -23
  31. package/dist/mcp/forge_verbs_server.js.map +1 -1
  32. package/dist/mcp/panel_invoke.d.ts +51 -0
  33. package/dist/mcp/panel_invoke.d.ts.map +1 -0
  34. package/dist/mcp/panel_invoke.js +102 -0
  35. package/dist/mcp/panel_invoke.js.map +1 -0
  36. package/dist/nac3/internal_manifest.d.ts +2 -0
  37. package/dist/nac3/internal_manifest.d.ts.map +1 -1
  38. package/dist/nac3/internal_manifest.js +18 -3
  39. package/dist/nac3/internal_manifest.js.map +1 -1
  40. package/dist/nac3/verb_budget.d.ts +110 -0
  41. package/dist/nac3/verb_budget.d.ts.map +1 -0
  42. package/dist/nac3/verb_budget.js +142 -0
  43. package/dist/nac3/verb_budget.js.map +1 -0
  44. package/dist/relay/in_flight_turn.d.ts +52 -11
  45. package/dist/relay/in_flight_turn.d.ts.map +1 -1
  46. package/dist/relay/in_flight_turn.js +98 -30
  47. package/dist/relay/in_flight_turn.js.map +1 -1
  48. package/dist/relay/loopback_turn.d.ts +15 -42
  49. package/dist/relay/loopback_turn.d.ts.map +1 -1
  50. package/dist/relay/loopback_turn.js +27 -115
  51. package/dist/relay/loopback_turn.js.map +1 -1
  52. package/dist/urazuke/evidence.d.ts +22 -0
  53. package/dist/urazuke/evidence.d.ts.map +1 -1
  54. package/dist/urazuke/evidence.js +42 -3
  55. package/dist/urazuke/evidence.js.map +1 -1
  56. package/dist/version.d.ts +1 -1
  57. package/dist/version.js +1 -1
  58. package/docs/doctrine/urazuke.md +50 -38
  59. package/package.json +20 -18
  60. package/src/i18n/catalogs/ar.json +3 -0
  61. package/src/i18n/catalogs/de.json +3 -0
  62. package/src/i18n/catalogs/en.json +3 -0
  63. package/src/i18n/catalogs/es.json +3 -0
  64. package/src/i18n/catalogs/fr.json +3 -0
  65. package/src/i18n/catalogs/hi.json +3 -0
  66. package/src/i18n/catalogs/it.json +3 -0
  67. package/src/i18n/catalogs/ja.json +3 -0
  68. package/src/i18n/catalogs/pt.json +3 -0
  69. package/src/i18n/catalogs/zh.json +3 -0
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Cuanto puede durar la invocacion de un verbo, y quien lo decide.
3
+ *
4
+ * EL DEFECTO (PND-235). Los dos servers MCP reenvian cada invocacion al
5
+ * servidor de chat por loopback y esperan la respuesta. Con `fetch` esa espera
6
+ * heredaba el techo de undici -- 305.677 ms MEDIDOS en Node 22.16 -- y moria
7
+ * ahi. El techo no era una decision de nadie: era el default de una libreria
8
+ * aplicado a un canal para el que no estaba pensado. Y era un techo SECRETO:
9
+ * `yujin.evidence.run` declara en su schema que acepta hasta 30 minutos, el
10
+ * servidor los honra, y la invocacion se caia a los cinco. El verbo que
11
+ * acredita una bateria de siete minutos era justo el que no podia dejar
12
+ * recibo.
13
+ *
14
+ * LA REGLA. El presupuesto del transporte sale de lo que el VERBO declara, no
15
+ * de lo que la libreria trae puesto. Nadie impone por debajo un techo mas bajo
16
+ * que el que el verbo promete.
17
+ *
18
+ * PERO EL PRESUPUESTO NO ES INFINITO, Y EL LIMITE NO ES ARBITRARIO. En modo
19
+ * Plan el verbo corre adentro de un turno de `claude -p`, y ese turno tiene su
20
+ * propia vigilia de SILENCIO (limits.claude_cli_silence_ms, 15 min por
21
+ * default): mientras una tool call esta pendiente el subproceso no emite un
22
+ * solo evento, asi que una invocacion que bloquea mas que esa ventana no falla
23
+ * sola -- se lleva el TURNO ENTERO por colgado. Por eso lo que un verbo puede
24
+ * BLOQUEAR tiene que quedar por debajo de esa ventana, con margen.
25
+ *
26
+ * Y de ahi sale la otra mitad del arreglo: un verbo que legitimamente dura mas
27
+ * que la ventana de bloqueo no se recorta en silencio, se corre EN SEGUNDO
28
+ * PLANO y devuelve un handle (ver chat/tools/evidence.ts). El techo deja de ser
29
+ * secreto: o entra en la ventana, o el verbo te dice que lo lanzo aparte.
30
+ *
31
+ * ASCII puro.
32
+ */
33
+ import { manifest } from './internal_manifest.js';
34
+ /**
35
+ * Lo maximo que una invocacion puede BLOQUEAR el turno.
36
+ *
37
+ * Diez minutos. El numero sale de la vigilia de silencio del subproceso
38
+ * (claude_cli_silence_ms, 15 min por default): bloquear mas que eso no da un
39
+ * error del verbo, mata el turno. Diez mas el margen de abajo deja cuatro
40
+ * minutos de aire. La simetria con ese limite esta cuidada por un test: si
41
+ * alguien baja la vigilia, el test lo avisa antes de que se vuelva un turno
42
+ * muerto en produccion.
43
+ */
44
+ export const VERB_BLOCKING_MAX_MS = 10 * 60_000;
45
+ /**
46
+ * Margen del despachador por encima del tope del propio verbo.
47
+ *
48
+ * El verbo mide SU trabajo; el presupuesto del transporte tiene que cubrir
49
+ * ademas lo que pasa alrededor (resolver el schema, la cola del panel, armar y
50
+ * serializar la respuesta). Sin margen, el transporte se cae un instante antes
51
+ * de que la respuesta salga -- el peor de los dos mundos: el trabajo hecho y
52
+ * el resultado perdido.
53
+ */
54
+ export const VERB_BUDGET_OVERHEAD_MS = 60_000;
55
+ /** Piso: ni la invocacion mas trivial se corta antes de esto. */
56
+ export const VERB_BUDGET_MIN_MS = 30_000;
57
+ /**
58
+ * El presupuesto de un verbo que NO declara cuanto puede durar.
59
+ *
60
+ * Cinco minutos, y si -- es casi el mismo numero que heredaba de undici. Eso
61
+ * es deliberado: para un verbo que no declara nada (leer un archivo, un git
62
+ * status, el grafo) cinco minutos nunca fue el problema. El problema era
63
+ * aplicarle ese mismo techo al verbo que declara TREINTA minutos y despues
64
+ * tratar el corte como un fracaso del trabajo.
65
+ *
66
+ * Elegirlo igual tiene una segunda virtud: el radio de este cambio queda
67
+ * acotado a los verbos que SI declaran un tope. Para todos los demas el
68
+ * comportamiento es el de antes, y un servidor colgado se reporta en cinco
69
+ * minutos y no en once -- que, con la vigilia de silencio en quince, es la
70
+ * diferencia entre un error legible y un turno muerto sin explicacion.
71
+ */
72
+ export const VERB_BUDGET_DEFAULT_MS = 5 * 60_000;
73
+ /** El techo absoluto que puede salir de esta funcion. */
74
+ export const VERB_BUDGET_MAX_MS = VERB_BLOCKING_MAX_MS + VERB_BUDGET_OVERHEAD_MS;
75
+ /**
76
+ * El tope que el VERBO declara para si mismo, leido de su schema.
77
+ *
78
+ * `undefined` cuando el verbo no acepta timeout_ms: no es un verbo pensado
79
+ * para durar, y su presupuesto sale del default.
80
+ */
81
+ export function verbTimeoutCeilingMs(verb) {
82
+ const props = verb?.schema?.properties;
83
+ const t = props?.['timeout_ms'];
84
+ if (!t)
85
+ return undefined;
86
+ const max = typeof t.maximum === 'number' && Number.isFinite(t.maximum) && t.maximum > 0
87
+ ? t.maximum : undefined;
88
+ /* Declara timeout_ms pero sin maximo en el schema: se le cree que puede
89
+ * durar, y el techo lo pone la ventana de bloqueo. */
90
+ return max ?? VERB_BLOCKING_MAX_MS;
91
+ }
92
+ function positivo(v) {
93
+ const n = typeof v === 'number' ? v : typeof v === 'string' ? Number(v) : NaN;
94
+ return Number.isFinite(n) && n > 0 ? n : undefined;
95
+ }
96
+ /**
97
+ * El presupuesto de transporte para UNA invocacion.
98
+ *
99
+ * Orden de preferencia: lo que el llamador pidio en los args, si no el tope
100
+ * que el verbo declara, si no el default. Siempre con el margen del
101
+ * despachador encima y acotado por la ventana de bloqueo.
102
+ */
103
+ export function invokeBudgetMs(verbId, args = {}, verbs = manifest().verbs) {
104
+ const verb = verbs.find((v) => v.id === verbId);
105
+ const ceiling = verbTimeoutCeilingMs(verb);
106
+ /* El verbo no declara cuanto puede durar: techo plano, sin margen que
107
+ * cubrir, y el de siempre. */
108
+ if (ceiling === undefined)
109
+ return VERB_BUDGET_DEFAULT_MS;
110
+ /* El tope del verbo manda sobre lo que pida el llamador: el servidor lo va a
111
+ * recortar igual, asi que esperar mas seria esperar de gusto. */
112
+ const propio = Math.min(positivo(args['timeout_ms']) ?? ceiling, ceiling);
113
+ const conMargen = Math.min(propio, VERB_BLOCKING_MAX_MS) + VERB_BUDGET_OVERHEAD_MS;
114
+ return Math.min(Math.max(conMargen, VERB_BUDGET_MIN_MS), VERB_BUDGET_MAX_MS);
115
+ }
116
+ /**
117
+ * Este trabajo entra en la ventana de bloqueo, o hay que lanzarlo aparte.
118
+ *
119
+ * El llamador que SI conoce la ventana real (el servidor, que lee limits.json)
120
+ * puede pasarla; sin eso se usa el default, que es el caso del subproceso.
121
+ */
122
+ export function fitsInBlockingWindow(timeoutMs, windowMs = VERB_BLOCKING_MAX_MS) {
123
+ return timeoutMs <= windowMs;
124
+ }
125
+ /** Margen que se le deja a la vigilia de silencio: el turno tiene que poder
126
+ * recibir el resultado y contarlo antes de que el vigilante lo mate. */
127
+ export const BLOCKING_WINDOW_MARGIN_MS = 2 * 60_000;
128
+ /**
129
+ * La ventana de bloqueo REAL, para quien puede leer la configuracion viva.
130
+ *
131
+ * El default es una cota superior pensada para la vigilia de silencio por
132
+ * default (15 min). Si el dueno la baja, la ventana baja con ella: si no,
133
+ * un verbo que "entra" por el default se llevaria el turno puesto. Y nunca
134
+ * baja de un minuto, porque una ventana de cero mandaria al fondo hasta la
135
+ * corrida mas corta.
136
+ */
137
+ export function blockingWindowMs(silenceMs) {
138
+ if (!Number.isFinite(silenceMs) || silenceMs <= 0)
139
+ return VERB_BLOCKING_MAX_MS;
140
+ return Math.max(60_000, Math.min(VERB_BLOCKING_MAX_MS, silenceMs - BLOCKING_WINDOW_MARGIN_MS));
141
+ }
142
+ //# sourceMappingURL=verb_budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verb_budget.js","sourceRoot":"","sources":["../../src/nac3/verb_budget.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,32 @@
24
24
  * refresco a mano -- cae sobre un latido reciente y reconstruye que esta
25
25
  * corriendo, incluso si el telefono no estaba conectado cuando arranco.
26
26
  *
27
+ * POR QUE ES UN REGISTRO Y NO UNA VARIABLE (PND-237, 30-oct-2026).
28
+ *
29
+ * Esto era UN solo slot (`let current`), y un panel corre turnos SOLAPADOS: el
30
+ * dueno manda un pedido mientras el anterior sigue, o entra uno por el puente.
31
+ * Con un solo slot el segundo pisaba al primero, y `clearInFlightTurn(id)` --
32
+ * que NO limpia si el id no coincide, y eso esta bien, porque un cierre tardio
33
+ * no tiene que borrar el anuncio del que recien arranco -- dejaba al pisado
34
+ * SIN NINGUN camino de salida. Peor: `touchInFlightTurn()` refrescaba lo que
35
+ * hubiera en el slot sin mirar el id, asi que el latido de cualquier turno
36
+ * POSTERIOR mantenia vivo al huerfano y el tope de silencio no vencia nunca.
37
+ *
38
+ * Medido en produccion (D1, 30-oct-2026 13:25 UTC): el panel de adip-kikan
39
+ * venia anunciando `dt-mvuyvonj` -- arrancado 12:47:13 -- durante 152 latidos
40
+ * y 38 minutos, mientras el MISMO panel ya habia publicado los remote_turn
41
+ * completos de dos turnos que arrancaron a su lado (12:46:39 y 12:47:40: los
42
+ * tres se solapan en 61 segundos). El telefono mostraba la burbuja "trabajando"
43
+ * con el texto viejo, y como su guarda es por id, el turno REAL no recibia
44
+ * burbuja: el dueno veia una sesion trabajando sin su pedido a la vista.
45
+ *
46
+ * Las dos reglas que lo cierran, y que son el contrato de este modulo:
47
+ * - un turno es una ENTRADA propia, identificada por su id. Nadie pisa a
48
+ * nadie, y cada uno sale por su propio cierre o por su propio silencio.
49
+ * - la SENAL DE VIDA esta ligada al id que la produce (touchInFlightTurn(id)).
50
+ * Un latido solo puede hablar por SU turno. Eso es lo que le devuelve el
51
+ * poder al tope de silencio: un huerfano deja de ser tocado y vence.
52
+ *
27
53
  * ASCII puro.
28
54
  */
29
55
  export interface InFlightTurn {
@@ -60,30 +86,45 @@ export declare const PULSO_DE_PROGRESO_MS: number;
60
86
  *
61
87
  * Diez pulsos de margen: tolera que se pierdan nueve latidos seguidos antes de
62
88
  * dar por muerto un turno que esta vivo.
89
+ *
90
+ * PND-237: esta garantia solo existe de verdad desde que la senal de vida va
91
+ * por id. Mientras `touch()` refrescaba el slot entero, el latido de un turno
92
+ * nuevo reseteaba el reloj del huerfano y este tope era letra muerta.
63
93
  */
64
94
  export declare const SILENCIO_MAX_MS: number;
65
95
  export declare function setInFlightTurn(t: InFlightTurn, now?: number): void;
66
96
  /**
67
- * Senal de vida del turno en curso. La llama el latido de actividad del
68
- * servidor, que corre solo mientras hay turno.
97
+ * Senal de vida de UN turno. La llama el latido de actividad de ese turno,
98
+ * que corre solo mientras el turno vive.
69
99
  *
70
- * No crea nada: si no hay turno registrado, no hace nada. Asi un latido
71
- * rezagado no puede resucitar un turno ya cerrado.
100
+ * No crea nada: si ese id no esta registrado, no hace nada. Asi un latido
101
+ * rezagado no puede resucitar un turno ya cerrado -- y, desde PND-237, no
102
+ * puede tampoco mantener vivo el anuncio de OTRO turno, que era el agujero
103
+ * por el que un huerfano se anunciaba para siempre.
72
104
  */
73
- export declare function touchInFlightTurn(now?: number): void;
105
+ export declare function touchInFlightTurn(id: string, now?: number): void;
74
106
  /**
75
- * Limpia el turno en vuelo. Con `id`, SOLO si coincide: dos turnos encadenados
76
- * pueden cerrar fuera de orden, y un cierre tardio del viejo no tiene que
77
- * borrar el anuncio del que recien arranco.
107
+ * Limpia el turno en vuelo. Con `id`, SOLO ese: dos turnos encadenados pueden
108
+ * cerrar fuera de orden, y un cierre tardio del viejo no tiene que borrar el
109
+ * anuncio del que recien arranco. Sin `id`, TODOS (apagado del servidor).
78
110
  */
79
111
  export declare function clearInFlightTurn(id?: string): void;
80
112
  /**
81
- * El turno en vuelo, o null si no hay o si se quedo CALLADO.
113
+ * El turno que se ANUNCIA, o null si no hay ninguno vivo.
114
+ *
115
+ * El latido de presencia lleva UN turno (contrato del relay), asi que entre
116
+ * varios solapados hay que elegir, y se elige el que arranco ULTIMO: es el
117
+ * pedido que el dueno acaba de hacer y que esta esperando ver. Era justo el
118
+ * que se perdia -- con el slot unico, el que quedaba anunciado podia ser
119
+ * cualquiera, y si era un huerfano la guarda por id del telefono dejaba al
120
+ * pedido nuevo sin burbuja (PND-237, sintoma 6).
82
121
  *
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.
122
+ * La edad del turno no entra en la decision de VIDA: un turno de tres horas
123
+ * que sigue latiendo es un turno vivo. Lo que lo saca es el silencio.
85
124
  */
86
125
  export declare function inFlightTurn(now?: number): InFlightTurn | null;
126
+ /** Cuantos turnos vivos declara este panel (diagnostico / tests). */
127
+ export declare function inFlightCount(now?: number): number;
87
128
  /** Solo para tests. */
88
129
  export declare function _resetInFlightTurn(): void;
89
130
  //# sourceMappingURL=in_flight_turn.d.ts.map
@@ -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,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"}
1
+ {"version":3,"file":"in_flight_turn.d.ts","sourceRoot":"","sources":["../../src/relay/in_flight_turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;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;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,eAAe,QAA4B,CAAC;AAUzD,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,GAAE,MAAmB,GAAG,IAAI,CAS/E;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,IAAI,CAK5E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAGnD;AAWD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAmB,GAAG,YAAY,GAAG,IAAI,CAiB1E;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAAmB,GAAG,MAAM,CAG9D;AAED,uBAAuB;AACvB,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
@@ -24,6 +24,32 @@
24
24
  * refresco a mano -- cae sobre un latido reciente y reconstruye que esta
25
25
  * corriendo, incluso si el telefono no estaba conectado cuando arranco.
26
26
  *
27
+ * POR QUE ES UN REGISTRO Y NO UNA VARIABLE (PND-237, 30-oct-2026).
28
+ *
29
+ * Esto era UN solo slot (`let current`), y un panel corre turnos SOLAPADOS: el
30
+ * dueno manda un pedido mientras el anterior sigue, o entra uno por el puente.
31
+ * Con un solo slot el segundo pisaba al primero, y `clearInFlightTurn(id)` --
32
+ * que NO limpia si el id no coincide, y eso esta bien, porque un cierre tardio
33
+ * no tiene que borrar el anuncio del que recien arranco -- dejaba al pisado
34
+ * SIN NINGUN camino de salida. Peor: `touchInFlightTurn()` refrescaba lo que
35
+ * hubiera en el slot sin mirar el id, asi que el latido de cualquier turno
36
+ * POSTERIOR mantenia vivo al huerfano y el tope de silencio no vencia nunca.
37
+ *
38
+ * Medido en produccion (D1, 30-oct-2026 13:25 UTC): el panel de adip-kikan
39
+ * venia anunciando `dt-mvuyvonj` -- arrancado 12:47:13 -- durante 152 latidos
40
+ * y 38 minutos, mientras el MISMO panel ya habia publicado los remote_turn
41
+ * completos de dos turnos que arrancaron a su lado (12:46:39 y 12:47:40: los
42
+ * tres se solapan en 61 segundos). El telefono mostraba la burbuja "trabajando"
43
+ * con el texto viejo, y como su guarda es por id, el turno REAL no recibia
44
+ * burbuja: el dueno veia una sesion trabajando sin su pedido a la vista.
45
+ *
46
+ * Las dos reglas que lo cierran, y que son el contrato de este modulo:
47
+ * - un turno es una ENTRADA propia, identificada por su id. Nadie pisa a
48
+ * nadie, y cada uno sale por su propio cierre o por su propio silencio.
49
+ * - la SENAL DE VIDA esta ligada al id que la produce (touchInFlightTurn(id)).
50
+ * Un latido solo puede hablar por SU turno. Eso es lo que le devuelve el
51
+ * poder al tope de silencio: un huerfano deja de ser tocado y vence.
52
+ *
27
53
  * ASCII puro.
28
54
  */
29
55
  /**
@@ -54,63 +80,105 @@ export const PULSO_DE_PROGRESO_MS = 60 * 1000;
54
80
  *
55
81
  * Diez pulsos de margen: tolera que se pierdan nueve latidos seguidos antes de
56
82
  * dar por muerto un turno que esta vivo.
83
+ *
84
+ * PND-237: esta garantia solo existe de verdad desde que la senal de vida va
85
+ * por id. Mientras `touch()` refrescaba el slot entero, el latido de un turno
86
+ * nuevo reseteaba el reloj del huerfano y este tope era letra muerta.
57
87
  */
58
88
  export const SILENCIO_MAX_MS = PULSO_DE_PROGRESO_MS * 10;
59
- let current = null;
89
+ /** Los turnos vivos de ESTE panel, por id. Varios a la vez es lo normal. */
90
+ const vivos = new Map();
60
91
  export function setInFlightTurn(t, now = Date.now()) {
61
92
  if (!t || !t.id)
62
93
  return;
63
- current = {
64
- id: String(t.id),
94
+ const id = String(t.id);
95
+ vivos.set(id, {
96
+ id,
65
97
  user: String(t.user || ''),
66
98
  started_at: String(t.started_at || new Date(now).toISOString()),
67
99
  visto: now,
68
- };
100
+ });
69
101
  }
70
102
  /**
71
- * Senal de vida del turno en curso. La llama el latido de actividad del
72
- * servidor, que corre solo mientras hay turno.
103
+ * Senal de vida de UN turno. La llama el latido de actividad de ese turno,
104
+ * que corre solo mientras el turno vive.
73
105
  *
74
- * No crea nada: si no hay turno registrado, no hace nada. Asi un latido
75
- * rezagado no puede resucitar un turno ya cerrado.
106
+ * No crea nada: si ese id no esta registrado, no hace nada. Asi un latido
107
+ * rezagado no puede resucitar un turno ya cerrado -- y, desde PND-237, no
108
+ * puede tampoco mantener vivo el anuncio de OTRO turno, que era el agujero
109
+ * por el que un huerfano se anunciaba para siempre.
76
110
  */
77
- export function touchInFlightTurn(now = Date.now()) {
78
- if (!current)
111
+ export function touchInFlightTurn(id, now = Date.now()) {
112
+ if (!id)
113
+ return;
114
+ const r = vivos.get(String(id));
115
+ if (!r)
79
116
  return;
80
- current.visto = now;
117
+ r.visto = now;
81
118
  }
82
119
  /**
83
- * Limpia el turno en vuelo. Con `id`, SOLO si coincide: dos turnos encadenados
84
- * pueden cerrar fuera de orden, y un cierre tardio del viejo no tiene que
85
- * borrar el anuncio del que recien arranco.
120
+ * Limpia el turno en vuelo. Con `id`, SOLO ese: dos turnos encadenados pueden
121
+ * cerrar fuera de orden, y un cierre tardio del viejo no tiene que borrar el
122
+ * anuncio del que recien arranco. Sin `id`, TODOS (apagado del servidor).
86
123
  */
87
124
  export function clearInFlightTurn(id) {
88
- if (!current)
125
+ if (!id) {
126
+ vivos.clear();
89
127
  return;
90
- if (id && current.id !== id)
91
- return;
92
- current = null;
128
+ }
129
+ vivos.delete(String(id));
130
+ }
131
+ /** Poda las entradas que se quedaron CALLADAS. */
132
+ function podar(now) {
133
+ for (const [id, r] of vivos) {
134
+ /* Un reloj que salta hacia atras da silencio negativo: no vence. Preferible
135
+ * anunciar un turno de mas que descartar uno vivo por un salto de NTP. */
136
+ if (now - r.visto > SILENCIO_MAX_MS)
137
+ vivos.delete(id);
138
+ }
93
139
  }
94
140
  /**
95
- * El turno en vuelo, o null si no hay o si se quedo CALLADO.
141
+ * El turno que se ANUNCIA, o null si no hay ninguno vivo.
96
142
  *
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.
143
+ * El latido de presencia lleva UN turno (contrato del relay), asi que entre
144
+ * varios solapados hay que elegir, y se elige el que arranco ULTIMO: es el
145
+ * pedido que el dueno acaba de hacer y que esta esperando ver. Era justo el
146
+ * que se perdia -- con el slot unico, el que quedaba anunciado podia ser
147
+ * cualquiera, y si era un huerfano la guarda por id del telefono dejaba al
148
+ * pedido nuevo sin burbuja (PND-237, sintoma 6).
149
+ *
150
+ * La edad del turno no entra en la decision de VIDA: un turno de tres horas
151
+ * que sigue latiendo es un turno vivo. Lo que lo saca es el silencio.
99
152
  */
100
153
  export function inFlightTurn(now = Date.now()) {
101
- if (!current)
102
- return null;
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) {
106
- current = null;
107
- return null;
154
+ podar(now);
155
+ let elegido = null;
156
+ let mejor = -Infinity;
157
+ for (const r of vivos.values()) {
158
+ /* Un started_at ilegible NO puede ganar la eleccion. Date.parse devuelve
159
+ * NaN y toda comparacion con NaN es false, asi que comparando en crudo el
160
+ * primero de la iteracion se quedaba con el anuncio para siempre -- el
161
+ * mismo sintoma que estamos arreglando, por otra puerta. Vale -Infinity:
162
+ * se anuncia solo si es el unico que hay. */
163
+ const t = Date.parse(r.started_at);
164
+ const orden = Number.isFinite(t) ? t : -Infinity;
165
+ if (!elegido || orden > mejor) {
166
+ elegido = r;
167
+ mejor = orden;
168
+ }
108
169
  }
109
- const { id, user, started_at } = current;
170
+ if (!elegido)
171
+ return null;
172
+ const { id, user, started_at } = elegido;
110
173
  return { id, user, started_at };
111
174
  }
175
+ /** Cuantos turnos vivos declara este panel (diagnostico / tests). */
176
+ export function inFlightCount(now = Date.now()) {
177
+ podar(now);
178
+ return vivos.size;
179
+ }
112
180
  /** Solo para tests. */
113
181
  export function _resetInFlightTurn() {
114
- current = null;
182
+ vivos.clear();
115
183
  }
116
184
  //# sourceMappingURL=in_flight_turn.js.map
@@ -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,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"}
1
+ {"version":3,"file":"in_flight_turn.js","sourceRoot":"","sources":["../../src/relay/in_flight_turn.ts"],"names":[],"mappings":"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"}
@@ -9,47 +9,21 @@
9
9
  * sin una segunda implementacion que se desincronice.
10
10
  *
11
11
  * EL DEFECTO QUE ESTE MODULO CIERRA (PND-234. Pablo, 29-oct-2026, desde el
12
- * telefono: "No pude completar tu pedido: fetch failed").
12
+ * telefono: "No pude completar tu pedido: fetch failed"). La respuesta de
13
+ * /api/chat es un JSON que recien se arma cuando el turno entero termino, asi
14
+ * que con `fetch` el POST moria a los cinco minutos y el usuario recibia un
15
+ * fracaso por un trabajo que el escritorio estaba terminando bien.
13
16
  *
14
- * La respuesta de /api/chat es un JSON, no un stream, asi que sus CABECERAS
15
- * recien salen cuando el turno entero termino. Con `fetch` sin opciones ese
16
- * POST hereda el headersTimeout de undici, que es un limite pensado para una
17
- * llamada corta, y pasado ese punto muere el fetch -- no el turno. El
18
- * escritorio seguia trabajando y terminaba el trabajo, pero el telefono ya
19
- * habia recibido "fetch failed" y el usuario quedaba creyendo que no se hizo
20
- * nada.
21
- *
22
- * El limite esta MEDIDO, no estimado: contra un servidor que acepta la
23
- * conexion y nunca responde, el fetch global de Node 22.16 murio a los
24
- * 305.677 ms con name=TypeError, message="fetch failed" y
25
- * cause.code=UND_ERR_HEADERS_TIMEOUT. El turno del reporte habia durado 22
26
- * minutos (turn_routing.jsonl 13:36:33Z; los archivos que escribio tienen
27
- * mtime hasta 10:58 local). O sea: cinco minutos de techo para turnos que
28
- * duran veinte. El defecto es determinista y crece con la utilidad del
29
- * pedido -- cuanto mas trabajo pide el usuario desde el telefono, mas seguro
30
- * es que no vea el resultado.
31
- *
32
- * POR QUE node:http Y NO fetch. Es la unica forma de mandar los tiempos sin
33
- * depender de undici: Node lo trae adentro pero no lo expone como paquete, y
34
- * construir un Agent con otro headersTimeout exigiria declarar una
35
- * dependencia nueva para algo que el stdlib ya hace. La conexion es loopback
36
- * y en claro, asi que no se pierde nada por bajar de capa.
37
- *
38
- * EL PRESUPUESTO NO DESAPARECE, cambia de escala. Sin ningun limite, un
39
- * servidor de chat colgado dejaria al consumidor esperando para siempre -- y
40
- * como atiende un pedido a la vez, ningun pedido POSTERIOR del telefono se
41
- * correria nunca. Lo que no se usa es `req.setTimeout`, que mide INACTIVIDAD
42
- * del socket: un turno que piensa diez minutos no manda un byte, y ese
43
- * temporizador lo mataria sin motivo, que es la forma exacta del bug que esto
44
- * cierra. Lo que se mide es el tiempo TOTAL, con un techo mas alto que
45
- * cualquier turno legitimo.
46
- *
47
- * Y al vencerse, el usuario recibe algo accionable y VERDADERO: que el
48
- * escritorio puede seguir trabajando y donde ver el resultado. El mensaje
49
- * anterior ("no pude completar tu pedido") era falso en el caso mas comun.
17
+ * EL TRANSPORTE YA NO VIVE ACA (PND-235). El mismo techo aparecio identico en
18
+ * el canal de los verbos MCP, asi que el POST con presupuesto se mudo a
19
+ * core/loopback_post.ts y lo comparten los dos caminos. Lo que queda aca es lo
20
+ * que SI es del telefono: el techo de una hora y como se le cuenta el corte al
21
+ * usuario. El detalle de por que node:http y no fetch esta en ese modulo.
50
22
  *
51
23
  * ASCII puro.
52
24
  */
25
+ import { type LoopbackResponse } from '../core/loopback_post.js';
26
+ export type { LoopbackResponse };
53
27
  /**
54
28
  * Techo de tiempo total para un turno pedido desde el telefono.
55
29
  *
@@ -63,15 +37,14 @@
63
37
  export declare const MOBILE_TURN_BUDGET_MS: number;
64
38
  /** El techo efectivo, con el override de operacion. */
65
39
  export declare function mobileTurnBudgetMs(env?: NodeJS.ProcessEnv): number;
66
- export interface LoopbackResponse {
67
- status: number;
68
- body: string;
69
- }
70
40
  /**
71
41
  * POST al /api/chat local, esperando lo que tarde el turno.
72
42
  *
73
43
  * Devuelve el status y el cuerpo crudo; parsear es del llamador, que es el
74
- * que conoce la forma de la respuesta.
44
+ * que conoce la forma de la respuesta. Al cortarse, el usuario recibe algo
45
+ * accionable y VERDADERO: que el escritorio puede seguir trabajando y donde
46
+ * ver el resultado. El mensaje anterior ("no pude completar tu pedido") era
47
+ * falso en el caso mas comun.
75
48
  */
76
49
  export declare function postChatTurn(args: {
77
50
  port: number;
@@ -1 +1 @@
1
- {"version":3,"file":"loopback_turn.d.ts","sourceRoot":"","sources":["../../src/relay/loopback_turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAQH;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,QAAc,CAAC;AAEjD,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAU/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA6E5B"}
1
+ {"version":3,"file":"loopback_turn.d.ts","sourceRoot":"","sources":["../../src/relay/loopback_turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,EAAiC,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEhG,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,QAAc,CAAC;AAEjD,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAU/E;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAwB5B"}