xonecode 0.1.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 +167 -0
- package/dist/agent/authEnDisco.js +56 -0
- package/dist/agent/authEnDisco.js.map +1 -0
- package/dist/agent/configEnDisco.js +109 -0
- package/dist/agent/configEnDisco.js.map +1 -0
- package/dist/agent/crearProyecto.js +34 -0
- package/dist/agent/crearProyecto.js.map +1 -0
- package/dist/agent/entorno.js +124 -0
- package/dist/agent/entorno.js.map +1 -0
- package/dist/agent/guionizado.js +53 -0
- package/dist/agent/guionizado.js.map +1 -0
- package/dist/agent/instantanea.js +129 -0
- package/dist/agent/instantanea.js.map +1 -0
- package/dist/agent/interrupts.js +83 -0
- package/dist/agent/interrupts.js.map +1 -0
- package/dist/agent/mensajes.js +24 -0
- package/dist/agent/mensajes.js.map +1 -0
- package/dist/agent/modelos.js +42 -0
- package/dist/agent/modelos.js.map +1 -0
- package/dist/agent/normalizar.js +20 -0
- package/dist/agent/normalizar.js.map +1 -0
- package/dist/agent/perfiles.js +103 -0
- package/dist/agent/perfiles.js.map +1 -0
- package/dist/agent/proyecto.js +107 -0
- package/dist/agent/proyecto.js.map +1 -0
- package/dist/agent/puente.js +142 -0
- package/dist/agent/puente.js.map +1 -0
- package/dist/agent/resumenDeTool.js +48 -0
- package/dist/agent/resumenDeTool.js.map +1 -0
- package/dist/agent/skills.js +65 -0
- package/dist/agent/skills.js.map +1 -0
- package/dist/agent/textoDeTool.js +113 -0
- package/dist/agent/textoDeTool.js.map +1 -0
- package/dist/agent/turnoReal.js +173 -0
- package/dist/agent/turnoReal.js.map +1 -0
- package/dist/agent/verificador.js +65 -0
- package/dist/agent/verificador.js.map +1 -0
- package/dist/agent/xoneAgent.js +106 -0
- package/dist/agent/xoneAgent.js.map +1 -0
- package/dist/bin.js +9 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli/aprobar.js +82 -0
- package/dist/cli/aprobar.js.map +1 -0
- package/dist/cli/config.js +133 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/consola.js +346 -0
- package/dist/cli/consola.js.map +1 -0
- package/dist/cli/describe.js +53 -0
- package/dist/cli/describe.js.map +1 -0
- package/dist/cli/doctor.js +45 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/main.js +520 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/markdown.js +90 -0
- package/dist/cli/markdown.js.map +1 -0
- package/dist/cli/run.js +164 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/spinner.js +89 -0
- package/dist/cli/spinner.js.map +1 -0
- package/dist/cli/stdio.js +193 -0
- package/dist/cli/stdio.js.map +1 -0
- package/dist/cli/tema.js +40 -0
- package/dist/cli/tema.js.map +1 -0
- package/dist/cli/verify.js +62 -0
- package/dist/cli/verify.js.map +1 -0
- package/dist/core/bitacora.js +30 -0
- package/dist/core/bitacora.js.map +1 -0
- package/dist/core/config.js +236 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/contextos.js +56 -0
- package/dist/core/contextos.js.map +1 -0
- package/dist/core/deps.js +51 -0
- package/dist/core/deps.js.map +1 -0
- package/dist/core/diff.js +79 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/esqueleto.js +366 -0
- package/dist/core/esqueleto.js.map +1 -0
- package/dist/core/events.js +2 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/modelos.js +85 -0
- package/dist/core/modelos.js.map +1 -0
- package/dist/core/notify.js +106 -0
- package/dist/core/notify.js.map +1 -0
- package/dist/core/ports.js +114 -0
- package/dist/core/ports.js.map +1 -0
- package/dist/core/turno.js +127 -0
- package/dist/core/turno.js.map +1 -0
- package/dist/vendor/hitl.js +103 -0
- package/dist/vendor/hitl.js.map +1 -0
- package/dist/vendor/skillLoaders/catalog.js +74 -0
- package/dist/vendor/skillLoaders/catalog.js.map +1 -0
- package/dist/vendor/tokenTracking.js +58 -0
- package/dist/vendor/tokenTracking.js.map +1 -0
- package/package.json +41 -0
- package/skills/xone-debugging/SKILL.md +52 -0
- package/skills/xone-debugging/references/faq.md +561 -0
- package/skills/xone-debugging/references/troubleshooting-y-glosario.md +429 -0
- package/skills/xone-development/SKILL.md +105 -0
- package/skills/xone-development/references/anti-patrones.md +94 -0
- package/skills/xone-development/references/css/atributos-por-categoria.md +509 -0
- package/skills/xone-development/references/css/buenas-practicas-y-parser.md +419 -0
- package/skills/xone-development/references/css/dinamicos-cascada-y-componentes.md +550 -0
- package/skills/xone-development/references/css/patrones-material-y-temas.md +1140 -0
- package/skills/xone-development/references/css/propiedades-y-herencia.md +885 -0
- package/skills/xone-development/references/css/selectores-unidades-colores.md +840 -0
- package/skills/xone-development/references/datos/appdata-referencia-ampliada.md +457 -0
- package/skills/xone-development/references/datos/appdata.md +611 -0
- package/skills/xone-development/references/datos/http-sqlmanager-y-crypto.md +720 -0
- package/skills/xone-development/references/datos/http.md +366 -0
- package/skills/xone-development/references/datos/oauth2-y-replica.md +156 -0
- package/skills/xone-development/references/device/biometria-imagedrawing-y-otros.md +544 -0
- package/skills/xone-development/references/device/objetos-de-dispositivo.md +583 -0
- package/skills/xone-development/references/device/systemsettings-referencia-ampliada.md +396 -0
- package/skills/xone-development/references/device/systemsettings-y-permisos.md +342 -0
- package/skills/xone-development/references/fundamentos/conceptos-clave.md +695 -0
- package/skills/xone-development/references/fundamentos/configuracion-app-xml-ini-mappings.md +668 -0
- package/skills/xone-development/references/fundamentos/errores-comunes.md +312 -0
- package/skills/xone-development/references/fundamentos/navegacion-convenciones-y-primer-proyecto.md +576 -0
- package/skills/xone-development/references/fundamentos/plataforma-y-anatomia-de-proyecto.md +387 -0
- package/skills/xone-development/references/indice-completo.md +77 -0
- package/skills/xone-development/references/javascript/coleccion-error-y-usuario.md +260 -0
- package/skills/xone-development/references/javascript/debugging-y-best-practices.md +245 -0
- package/skills/xone-development/references/javascript/metodos-de-los-controles.md +556 -0
- package/skills/xone-development/references/javascript/metodos-nativos-de-la-vista.md +114 -0
- package/skills/xone-development/references/javascript/motor-js-y-contexto-de-ejecucion.md +319 -0
- package/skills/xone-development/references/javascript/objeto-ai-llm-en-dispositivo.md +358 -0
- package/skills/xone-development/references/javascript/objetos-creables-a-m.md +527 -0
- package/skills/xone-development/references/javascript/objetos-creables-n-z.md +385 -0
- package/skills/xone-development/references/javascript/patrones-criticos-seguridad-y-rendimiento.md +595 -0
- package/skills/xone-development/references/javascript/patrones-de-navegacion-datos-y-codigo.md +751 -0
- package/skills/xone-development/references/javascript/patrones-de-ui-voz-integracion-y-seguridad.md +701 -0
- package/skills/xone-development/references/javascript/plantillas-y-funciones-utilitarias.md +647 -0
- package/skills/xone-development/references/javascript/self-y-dataobject.md +449 -0
- package/skills/xone-development/references/javascript/singletons-globales.md +361 -0
- package/skills/xone-development/references/javascript/ui-catalogo-de-metodos.md +464 -0
- package/skills/xone-development/references/javascript/ui-gps-camara-y-multimedia.md +506 -0
- package/skills/xone-development/references/javascript/ui-navegacion-mensajes-y-vista.md +422 -0
- package/skills/xone-development/references/resumen-js-datos-dispositivo.md +80 -0
- package/skills/xone-development/references/resumen-xml-y-css.md +74 -0
- package/skills/xone-development/references/tipos-de-prop.md +28 -0
- package/skills/xone-development/references/xml-ui/asfilter-visibilidad-eventos-y-macros.md +576 -0
- package/skills/xone-development/references/xml-ui/atributos-coll-group-frame.md +235 -0
- package/skills/xone-development/references/xml-ui/atributos-method-macro-script-event-app.md +244 -0
- package/skills/xone-development/references/xml-ui/atributos-prop.md +439 -0
- package/skills/xone-development/references/xml-ui/contents-y-macros.md +392 -0
- package/skills/xone-development/references/xml-ui/errores-comunes-xml.md +245 -0
- package/skills/xone-development/references/xml-ui/estructura-y-nodo-coll.md +326 -0
- package/skills/xone-development/references/xml-ui/eventos-ciclo-de-vida-e-interaccion.md +704 -0
- package/skills/xone-development/references/xml-ui/eventos-sistema-login-y-personalizados.md +944 -0
- package/skills/xone-development/references/xml-ui/layouts-herencia-y-buenas-practicas.md +618 -0
- package/skills/xone-development/references/xml-ui/mapas.md +685 -0
- package/skills/xone-development/references/xml-ui/mappings-y-colecciones-separadas.md +148 -0
- package/skills/xone-development/references/xml-ui/nodos-group-y-frame.md +666 -0
- package/skills/xone-development/references/xml-ui/patrones-de-pantalla.md +643 -0
- package/skills/xone-development/references/xml-ui/prop-atributos-y-condiciones.md +298 -0
- package/skills/xone-development/references/xml-ui/prop-tipos-basicos.md +493 -0
- package/skills/xone-development/references/xml-ui/prop-tipos-combos-y-controles.md +664 -0
- package/skills/xone-development/references/xml-ui/prop-tipos-listas-y-mapas.md +412 -0
- package/skills/xone-plan-builder/SKILL.md +183 -0
- package/skills/xone-plan-builder/agents/openai.yaml +5 -0
- package/skills/xone-plan-builder/references/TASKS-FORMAT.md +132 -0
- package/skills/xone-project-generator/SKILL.md +111 -0
- package/skills/xone-project-generator/references/canonical-sizes.md +292 -0
- package/skills/xone-project-generator/references/colecciones-base.md +16 -0
- package/skills/xone-project-generator/references/convenciones-de-nombres.md +16 -0
- package/skills/xone-project-generator/references/ejemplos-por-sector-y-prohibiciones.md +140 -0
- package/skills/xone-project-generator/references/fase-3-estilos-css.md +597 -0
- package/skills/xone-project-generator/references/fase-6-colecciones.md +762 -0
- package/skills/xone-project-generator/references/fase-7-asfilter-e-integraciones.md +142 -0
- package/skills/xone-project-generator/references/fase-7-entidades-y-estructura-de-pantalla.md +494 -0
- package/skills/xone-project-generator/references/fase-7-plantilla-consola.md +477 -0
- package/skills/xone-project-generator/references/fase-7-plantillas-de-pantalla.md +302 -0
- package/skills/xone-project-generator/references/fase-7-viewmodes-graficos-y-listas.md +681 -0
- package/skills/xone-project-generator/references/fase-7-viewmodes-mapa-y-calendario.md +479 -0
- package/skills/xone-project-generator/references/fases-0-2-analisis-y-modelo-de-datos.md +350 -0
- package/skills/xone-project-generator/references/fases-10-12-readmes-y-validacion.md +194 -0
- package/skills/xone-project-generator/references/fases-4-5-estructura-y-configuracion.md +587 -0
- package/skills/xone-project-generator/references/fases-8-9-eventos-y-javascript.md +861 -0
- package/skills/xone-project-generator/references/flujo-de-generacion.md +67 -0
- package/skills/xone-project-generator/references/plantillas-estandar.md +98 -0
- package/skills/xone-project-generator/references/tamanos-canonicos.md +49 -0
- package/skills/xone-review/SKILL.md +189 -0
- package/skills/xone-spec-builder/SKILL.md +235 -0
- package/skills/xone-spec-builder/agents/openai.yaml +5 -0
- package/skills/xone-spec-builder/references/ADR-FORMAT.md +55 -0
- package/skills/xone-spec-builder/references/CONTEXT-FORMAT.md +42 -0
- package/skills/xone-spec-builder/references/PLAN-FORMAT.md +120 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { normalizar } from "./normalizar.js";
|
|
2
|
+
import { Mensajes } from "./mensajes.js";
|
|
3
|
+
import { detalleDe } from "./resumenDeTool.js";
|
|
4
|
+
/** El texto de un mensaje, venga como venga. */
|
|
5
|
+
export function textoDe(msg) {
|
|
6
|
+
if (!msg || typeof msg !== "object")
|
|
7
|
+
return "";
|
|
8
|
+
const m = msg;
|
|
9
|
+
if (typeof m.text === "string")
|
|
10
|
+
return m.text;
|
|
11
|
+
const c = m.content;
|
|
12
|
+
if (typeof c === "string")
|
|
13
|
+
return c;
|
|
14
|
+
// Contenido en BLOQUES (Gemini con thinking): concatenar solo los de texto. Un
|
|
15
|
+
// `String(content)` daría el `repr` de la lista, razonamiento incluido, y el usuario
|
|
16
|
+
// vería basura donde esperaba una frase.
|
|
17
|
+
if (Array.isArray(c)) {
|
|
18
|
+
return c
|
|
19
|
+
.map((b) => (b && typeof b === "object" && typeof b.text === "string" ? b.text : ""))
|
|
20
|
+
.join("");
|
|
21
|
+
}
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
/** Los nombres de tool de un chunk de `updates`, con su detalle de la lista blanca. */
|
|
25
|
+
export function toolsDe(dato) {
|
|
26
|
+
if (!dato || typeof dato !== "object")
|
|
27
|
+
return [];
|
|
28
|
+
const salida = [];
|
|
29
|
+
for (const nodo of Object.values(dato)) {
|
|
30
|
+
const msgs = nodo?.messages;
|
|
31
|
+
if (!Array.isArray(msgs))
|
|
32
|
+
continue;
|
|
33
|
+
for (const m of msgs) {
|
|
34
|
+
const llamadas = m?.tool_calls;
|
|
35
|
+
if (Array.isArray(llamadas)) {
|
|
36
|
+
for (const l of llamadas) {
|
|
37
|
+
const n = l?.name;
|
|
38
|
+
if (typeof n === "string")
|
|
39
|
+
salida.push({ nombre: n, detalle: detalleDe(n, l.args) });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return salida;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* ¿Este chunk lo emite el grafo PADRE, o un especialista?
|
|
48
|
+
*
|
|
49
|
+
* **Medido el 2026-08-30**, sobre un turno real con delegación. Los namespaces que
|
|
50
|
+
* llegan con `subgraphs: true` son exactamente tres formas:
|
|
51
|
+
*
|
|
52
|
+
* | namespace | quién | chunks |
|
|
53
|
+
* |----------------------------------------|---------------|-------:|
|
|
54
|
+
* | `["model_request:…"]` | el padre | 148 |
|
|
55
|
+
* | `["tools:…", "model_request:…"]` | un especialista | 745 |
|
|
56
|
+
* | `["tools:…", "tools:…"]` | tools de un especialista | 12 |
|
|
57
|
+
*
|
|
58
|
+
* Un especialista SIEMPRE lleva el segmento `tools:` delante, porque se le invoca con la
|
|
59
|
+
* tool `task`. Así que la frontera es la LONGITUD.
|
|
60
|
+
*
|
|
61
|
+
* **El fallo que esto cierra era mudo y caro.** Antes se descartaba todo `ns` no vacío,
|
|
62
|
+
* dando por hecho que el padre venía con `ns: []` — cierto para `updates`, FALSO para
|
|
63
|
+
* `messages`, que llegan con `["model_request:<uuid>"]`. Consecuencia: se descartaban
|
|
64
|
+
* TODOS los tokens, respuesta incluida. El turno corría entero, enseñaba sus tools,
|
|
65
|
+
* decía «sin cambios en el proyecto»… y no contestaba nada. Sin excepción y sin aviso.
|
|
66
|
+
*
|
|
67
|
+
* Y los números explican por qué el filtro tiene que existir: 745 chunks de especialista
|
|
68
|
+
* frente a 148 del padre. Emitirlos todos sería volcar el razonamiento interno de los
|
|
69
|
+
* especialistas encima de la respuesta.
|
|
70
|
+
*/
|
|
71
|
+
export function esDelPadre(ns) {
|
|
72
|
+
return ns.length <= 1;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Convierte el stream del grafo en `DomainEvent`.
|
|
76
|
+
*
|
|
77
|
+
* **Se lee con `subgraphs: true`**, porque sin él lo que hacen los especialistas —que son
|
|
78
|
+
* grafos compilados invocados dentro de un nodo— no llega al padre, y el fallo es MUDO. El
|
|
79
|
+
* precio, medido: el chunk pasa de `[modo, dato]` a `[namespace, modo, dato]`, y de eso se
|
|
80
|
+
* encarga `normalizar`.
|
|
81
|
+
*
|
|
82
|
+
* Los tokens que se emiten son SOLO los del grafo padre. Los de un
|
|
83
|
+
* especialista son su razonamiento interno, no la respuesta: enseñarlos convierte la
|
|
84
|
+
* consola en un volcado. Su trabajo se cuenta por `tool`, que es lo que el usuario
|
|
85
|
+
* necesita ver mientras espera.
|
|
86
|
+
*/
|
|
87
|
+
export async function* aEventos(stream, pendientes) {
|
|
88
|
+
const mensajes = new Mensajes();
|
|
89
|
+
try {
|
|
90
|
+
for await (const bruto of stream) {
|
|
91
|
+
const chunk = normalizar(bruto);
|
|
92
|
+
if (!chunk)
|
|
93
|
+
continue;
|
|
94
|
+
if (chunk.modo === "updates") {
|
|
95
|
+
for (const { nombre, detalle } of toolsDe(chunk.dato)) {
|
|
96
|
+
yield { tipo: "tool", nombre, ...(detalle !== undefined ? { detalle } : {}) };
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (chunk.modo === "messages") {
|
|
101
|
+
if (!esDelPadre(chunk.ns))
|
|
102
|
+
continue; // de un especialista: no es la respuesta
|
|
103
|
+
if (!Array.isArray(chunk.dato))
|
|
104
|
+
continue;
|
|
105
|
+
const [msg] = chunk.dato;
|
|
106
|
+
const texto = textoDe(msg);
|
|
107
|
+
const id = msg?.id;
|
|
108
|
+
const idTexto = typeof id === "string" ? id : undefined;
|
|
109
|
+
if (mensajes.trozo(idTexto, texto).pintar) {
|
|
110
|
+
yield { tipo: "token", texto, msgId: idTexto };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
mensajes.fin();
|
|
117
|
+
// La pausa se consulta AL AGOTARSE el stream, no dentro del bucle: una pausa
|
|
118
|
+
// TERMINA el turno, así que el interrupt solo puede estar en el estado cuando el
|
|
119
|
+
// stream ya se ha acabado. Y solo si alguien pasó `pendientes`: sin el parámetro,
|
|
120
|
+
// el comportamiento es el de antes.
|
|
121
|
+
if (pendientes !== undefined) {
|
|
122
|
+
try {
|
|
123
|
+
const lista = await pendientes();
|
|
124
|
+
if (lista.length > 0) {
|
|
125
|
+
// Una sola `pausa` aunque vengan varios: con dos `task` en un turno quedan
|
|
126
|
+
// DOS interrupts a la vez, y dos eventos harían preguntar dos veces por lotes.
|
|
127
|
+
yield { tipo: "pausa", pendientes: lista };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
// No se traga: callarlo haría creer que no había nada que aprobar cuando en
|
|
132
|
+
// verdad no se pudo comprobar. Fallar entero tampoco: el turno ya acabó.
|
|
133
|
+
yield {
|
|
134
|
+
tipo: "aviso",
|
|
135
|
+
texto: `⚠ No se pudo comprobar si había algo pendiente de aprobación: ${e instanceof Error ? e.message : String(e)}`,
|
|
136
|
+
severidad: "grave",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=puente.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"puente.js","sourceRoot":"","sources":["../../src/agent/puente.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* La LISTA BLANCA de la línea de estado: qué argumento de cada tool puede salir como
|
|
3
|
+
* `detalle` del evento `tool`.
|
|
4
|
+
*
|
|
5
|
+
* La regla dura está documentada en `core/events.ts`: ningún evento lleva argumentos de
|
|
6
|
+
* tool crudos, porque `write_file` lleva el contenido del fichero y una tool MCP lleva
|
|
7
|
+
* el bearer — enseñar el progreso no puede ser la forma de filtrar un secreto. Este
|
|
8
|
+
* módulo es la otra mitad de esa regla: no se FILTRA nada, se ELIGE a mano un campo por
|
|
9
|
+
* tool — una ruta o un patrón, nunca contenido — y lo que no está en la tabla no sale.
|
|
10
|
+
* Los nombres de campo son los de las tools de fichero de deepagents, verificados
|
|
11
|
+
* contra su dist (`file_path` con `path` normalizado, `pattern` en glob/grep).
|
|
12
|
+
*/
|
|
13
|
+
/** El único campo permitido de cada tool. Sin entrada: nada sale. */
|
|
14
|
+
const CAMPO_SEGURO = {
|
|
15
|
+
read_file: "file_path",
|
|
16
|
+
write_file: "file_path",
|
|
17
|
+
edit_file: "file_path",
|
|
18
|
+
ls: "path",
|
|
19
|
+
glob: "pattern",
|
|
20
|
+
grep: "pattern",
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* El `detalle` de una llamada, o `undefined` si no hay nada permitido que contar.
|
|
24
|
+
*
|
|
25
|
+
* Los argumentos llegan como objeto o como cadena JSON (así viajan en las
|
|
26
|
+
* `tool_calls`); un JSON roto es una tool rota, no una excepción que tumbe el turno.
|
|
27
|
+
*/
|
|
28
|
+
export function detalleDe(nombre, args) {
|
|
29
|
+
const campo = CAMPO_SEGURO[nombre];
|
|
30
|
+
if (campo === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
let objeto = args;
|
|
33
|
+
if (typeof args === "string") {
|
|
34
|
+
try {
|
|
35
|
+
objeto = JSON.parse(args);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!objeto || typeof objeto !== "object")
|
|
42
|
+
return undefined;
|
|
43
|
+
const valor = objeto[campo];
|
|
44
|
+
// Una cadena vacía no describe nada, y un tipo raro (`file_path: 42`) tampoco:
|
|
45
|
+
// sin detalle es una respuesta válida.
|
|
46
|
+
return typeof valor === "string" && valor !== "" ? valor : undefined;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=resumenDeTool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resumenDeTool.js","sourceRoot":"","sources":["../../src/agent/resumenDeTool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,qEAAqE;AACrE,MAAM,YAAY,GAA2B;IAC3C,SAAS,EAAE,WAAW;IACtB,UAAU,EAAE,WAAW;IACvB,SAAS,EAAE,WAAW;IACtB,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;CAChB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,IAAa;IACrD,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAE1C,IAAI,MAAM,GAAY,IAAI,CAAC;IAC3B,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAE5D,MAAM,KAAK,GAAI,MAAkC,CAAC,KAAK,CAAC,CAAC;IACzD,+EAA+E;IAC/E,uCAAuC;IACvC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readdirSync, existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parseFrontmatter } from "../vendor/skillLoaders/catalog.js";
|
|
5
|
+
/**
|
|
6
|
+
* Dónde viven las skills.
|
|
7
|
+
*
|
|
8
|
+
* Dos niveles arriba, y sirve igual desde `src/` (con tsx) que desde `dist/`: los dos
|
|
9
|
+
* cuelgan de la raíz del repo, así que `../../skills` acierta en ambos.
|
|
10
|
+
*
|
|
11
|
+
* Se resuelve contra ESTE módulo y no contra el cwd: en la v1 el cwd es el proyecto del
|
|
12
|
+
* cliente, así que un `./skills` relativo apuntaría al proyecto del usuario y el catálogo
|
|
13
|
+
* saldría vacío sin que nadie supiera por qué.
|
|
14
|
+
*/
|
|
15
|
+
export const RAIZ_SKILLS = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "skills");
|
|
16
|
+
/** Estimación de coste. 4 caracteres por token es la regla de servilleta de siempre. */
|
|
17
|
+
export const tokensDe = (texto) => Math.ceil(texto.length / 4);
|
|
18
|
+
/**
|
|
19
|
+
* Las skills en disco. NO lleva la marca de doble: son las de verdad.
|
|
20
|
+
*
|
|
21
|
+
* El catálogo se lee UNA vez y se cachea: son seis ficheros que no cambian a mitad de
|
|
22
|
+
* turno, y releerlos por cada consulta convierte el catálogo en E/S dentro del lazo.
|
|
23
|
+
*/
|
|
24
|
+
export class SkillsEnDisco {
|
|
25
|
+
raiz;
|
|
26
|
+
cache = null;
|
|
27
|
+
constructor(raiz = RAIZ_SKILLS) {
|
|
28
|
+
this.raiz = raiz;
|
|
29
|
+
}
|
|
30
|
+
catalogo() {
|
|
31
|
+
if (this.cache)
|
|
32
|
+
return this.cache;
|
|
33
|
+
if (!existsSync(this.raiz))
|
|
34
|
+
return (this.cache = []);
|
|
35
|
+
const salida = [];
|
|
36
|
+
for (const entrada of readdirSync(this.raiz)) {
|
|
37
|
+
const skillMd = join(this.raiz, entrada, "SKILL.md");
|
|
38
|
+
if (!existsSync(skillMd))
|
|
39
|
+
continue;
|
|
40
|
+
try {
|
|
41
|
+
const bruto = readFileSync(skillMd, "utf8");
|
|
42
|
+
const { data } = parseFrontmatter(bruto);
|
|
43
|
+
salida.push({
|
|
44
|
+
nombre: data.name ?? entrada,
|
|
45
|
+
descripcion: data.description ?? "",
|
|
46
|
+
tokens: tokensDe(bruto),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Una skill ilegible no puede tumbar el catálogo entero: se queda fuera y las
|
|
51
|
+
// demás siguen. Que falte se ve en `xonecode skills`, que las cuenta.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return (this.cache = salida.sort((a, b) => a.nombre.localeCompare(b.nombre)));
|
|
55
|
+
}
|
|
56
|
+
cargar(nombre) {
|
|
57
|
+
const skillMd = join(this.raiz, nombre, "SKILL.md");
|
|
58
|
+
if (!existsSync(skillMd)) {
|
|
59
|
+
const hay = this.catalogo().map((s) => s.nombre).join(", ");
|
|
60
|
+
throw new Error(`no hay skill «${nombre}». Las que hay: ${hay || "(ninguna)"}`);
|
|
61
|
+
}
|
|
62
|
+
return readFileSync(skillMd, "utf8");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/agent/skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAY,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAGrE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAElG,wFAAwF;AACxF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAE/E;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IAGK;IAFrB,KAAK,GAAuB,IAAI,CAAC;IAEzC,YAA6B,OAAe,WAAW;QAA1B,SAAI,GAAJ,IAAI,CAAsB;IAAG,CAAC;IAE3D,QAAQ;QACN,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC;QAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;QAErD,MAAM,MAAM,GAAgB,EAAE,CAAC;QAC/B,KAAK,MAAM,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;YACrD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;gBAAE,SAAS;YACnC,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAC5C,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;gBACzC,MAAM,CAAC,IAAI,CAAC;oBACV,MAAM,EAAE,IAAI,CAAC,IAAI,IAAI,OAAO;oBAC5B,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE;oBACnC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC;iBACxB,CAAC,CAAC;YACL,CAAC;YAAC,MAAM,CAAC;gBACP,8EAA8E;gBAC9E,sEAAsE;YACxE,CAAC;QACH,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,CAAC,MAAc;QACnB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,iBAAiB,MAAM,mBAAmB,GAAG,IAAI,WAAW,EAAE,CAAC,CAAC;QAClF,CAAC;QACD,OAAO,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;CACF"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convierte a TEXTO el contenido de los `ToolMessage` antes de mandárselo al modelo.
|
|
3
|
+
*
|
|
4
|
+
* ## El fallo que cierra, medido el 2026-08-30
|
|
5
|
+
*
|
|
6
|
+
* Un turno real reventaba con `Non string tool message content is not supported` tras
|
|
7
|
+
* 8-10 llamadas a tools. Reproducido con DOS modelos distintos —`glm-5.3-flash:cloud` y
|
|
8
|
+
* `qwen3.8:27b-mlx`, nube y local— y con la misma traza, lo que hacía pensar en un
|
|
9
|
+
* problema de langchain o de deepagents.
|
|
10
|
+
*
|
|
11
|
+
* **No lo era.** El mensaje sale de `@langchain/ollama/dist/utils.js:107`:
|
|
12
|
+
*
|
|
13
|
+
* ```js
|
|
14
|
+
* function convertToolMessageToOllama(message) {
|
|
15
|
+
* if (typeof message.content !== "string")
|
|
16
|
+
* throw new Error("Non string tool message content is not supported");
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* O sea: es del PROVEEDOR, no del harness. Los dos modelos fallaban igual porque los dos
|
|
20
|
+
* eran Ollama. Su conversor no acepta contenido en bloques —que es lo que devuelven las
|
|
21
|
+
* tools de fichero de deepagents— y no lo convierte ni degrada: lanza.
|
|
22
|
+
*
|
|
23
|
+
* Y es una combinación que en este laboratorio importa: Ollama local es la vía por
|
|
24
|
+
* omisión (el coste manda), así que sin esto el harness no puede correr con su propio
|
|
25
|
+
* proveedor por defecto.
|
|
26
|
+
*
|
|
27
|
+
* ## Por qué se aplica SIEMPRE y no solo con Ollama
|
|
28
|
+
*
|
|
29
|
+
* Un `ToolMessage` de texto lo acepta cualquier proveedor, así que convertir de más no
|
|
30
|
+
* rompe a nadie. Condicionarlo al proveedor tendría dos costes: la rama solo se
|
|
31
|
+
* ejercitaría con Ollama —o sea, un camino que se rompe sin que ningún test lo note— y
|
|
32
|
+
* obligaría a este módulo a saber qué proveedor hay detrás, que es justo lo que los
|
|
33
|
+
* puertos existen para no tener que saber.
|
|
34
|
+
*/
|
|
35
|
+
import { createMiddleware } from "langchain";
|
|
36
|
+
/** Un bloque de contenido: `{type:"text", text}` es el caso que hay que conservar. */
|
|
37
|
+
function textoDeBloque(bloque) {
|
|
38
|
+
if (typeof bloque === "string")
|
|
39
|
+
return bloque;
|
|
40
|
+
if (!bloque || typeof bloque !== "object")
|
|
41
|
+
return "";
|
|
42
|
+
const b = bloque;
|
|
43
|
+
if (typeof b.text === "string")
|
|
44
|
+
return b.text;
|
|
45
|
+
// Un bloque que no es de texto (una imagen, por ejemplo) no se puede meter en una
|
|
46
|
+
// cadena sin mentir sobre lo que es. Se declara en vez de perderlo en silencio.
|
|
47
|
+
return typeof b.type === "string" ? `[${b.type}]` : "";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* El contenido de un mensaje, como cadena.
|
|
51
|
+
*
|
|
52
|
+
* `JSON.stringify` es el último recurso y no el primero: para bloques de texto daría
|
|
53
|
+
* `[{"type":"text","text":"…"}]`, y el modelo tendría que desenterrar el contenido de
|
|
54
|
+
* su propio envoltorio en cada llamada.
|
|
55
|
+
*/
|
|
56
|
+
export function aTexto(contenido) {
|
|
57
|
+
if (typeof contenido === "string")
|
|
58
|
+
return contenido;
|
|
59
|
+
if (Array.isArray(contenido))
|
|
60
|
+
return contenido.map(textoDeBloque).join("");
|
|
61
|
+
if (contenido === null || contenido === undefined)
|
|
62
|
+
return "";
|
|
63
|
+
try {
|
|
64
|
+
return JSON.stringify(contenido);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return String(contenido);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** ¿Es un `ToolMessage`? Se mira por las dos vías: el tipo de langchain y el campo. */
|
|
71
|
+
export function esMensajeDeTool(msg) {
|
|
72
|
+
if (!msg || typeof msg !== "object")
|
|
73
|
+
return false;
|
|
74
|
+
const m = msg;
|
|
75
|
+
if (m.type === "tool" || m.role === "tool")
|
|
76
|
+
return true;
|
|
77
|
+
return typeof m.tool_call_id === "string" && m.tool_call_id.length > 0;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Convierte los `ToolMessage` de la petición, sin tocar el historial.
|
|
81
|
+
*
|
|
82
|
+
* Va en `wrapModelCall` y no en `beforeModel` a propósito: lo que hay que arreglar es lo
|
|
83
|
+
* que SE MANDA, no lo que se guarda. Reescribir el estado perdería el contenido
|
|
84
|
+
* estructurado para todo lo demás —el rastreo, un proveedor que sí lo acepte— por un
|
|
85
|
+
* problema de un solo conversor.
|
|
86
|
+
*/
|
|
87
|
+
export function middlewareTextoDeTool() {
|
|
88
|
+
return createMiddleware({
|
|
89
|
+
name: "TextoDeToolMiddleware",
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
91
|
+
wrapModelCall: (request, handler) => {
|
|
92
|
+
const mensajes = request?.messages;
|
|
93
|
+
if (!Array.isArray(mensajes))
|
|
94
|
+
return handler(request);
|
|
95
|
+
let hubo = false;
|
|
96
|
+
const convertidos = mensajes.map((m) => {
|
|
97
|
+
if (!esMensajeDeTool(m))
|
|
98
|
+
return m;
|
|
99
|
+
const msg = m;
|
|
100
|
+
if (typeof msg.content === "string")
|
|
101
|
+
return m;
|
|
102
|
+
hubo = true;
|
|
103
|
+
// Se clona: mutar el mensaje lo cambiaría también en el historial, que es
|
|
104
|
+
// exactamente lo que este middleware evita.
|
|
105
|
+
const copia = Object.create(Object.getPrototypeOf(msg));
|
|
106
|
+
Object.assign(copia, msg, { content: aTexto(msg.content) });
|
|
107
|
+
return copia;
|
|
108
|
+
});
|
|
109
|
+
return handler(hubo ? { ...request, messages: convertidos } : request);
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=textoDeTool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textoDeTool.js","sourceRoot":"","sources":["../../src/agent/textoDeTool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { readdirSync, statSync, existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, sep, resolve } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { HumanMessage } from "@langchain/core/messages";
|
|
5
|
+
import { Command, MemorySaver } from "@langchain/langgraph";
|
|
6
|
+
import { collectPending, MAX_APPROVAL_ROUNDS } from "../vendor/hitl.js";
|
|
7
|
+
import { aPendiente, ficheroDe, cambioDe, buildResume } from "./interrupts.js";
|
|
8
|
+
import { correrTurno } from "../core/turno.js";
|
|
9
|
+
import { tomarInstantanea } from "./instantanea.js";
|
|
10
|
+
import { construirAgente } from "./xoneAgent.js";
|
|
11
|
+
import { aEventos } from "./puente.js";
|
|
12
|
+
import { createTokenTracker } from "../vendor/tokenTracking.js";
|
|
13
|
+
/**
|
|
14
|
+
* Recorre la raíz y devuelve las rutas en el ESPACIO VIRTUAL del backend.
|
|
15
|
+
*
|
|
16
|
+
* Exportada porque la consola la usa para el completado de «@ficheros» (cli puede
|
|
17
|
+
* importar de agent; al revés no). El recorrido se hace EN CADA llamada a propósito:
|
|
18
|
+
* la lista no se cachea porque los ficheros cambian durante la sesión.
|
|
19
|
+
*/
|
|
20
|
+
export function ficherosDelProyecto(raiz, prof = 0) {
|
|
21
|
+
if (prof > 4 || !existsSync(raiz))
|
|
22
|
+
return new Set();
|
|
23
|
+
const salida = new Set();
|
|
24
|
+
for (const entrada of readdirSync(raiz)) {
|
|
25
|
+
if (entrada === "node_modules" || entrada === ".git")
|
|
26
|
+
continue;
|
|
27
|
+
const ruta = join(raiz, entrada);
|
|
28
|
+
try {
|
|
29
|
+
if (statSync(ruta).isDirectory()) {
|
|
30
|
+
// La recursión devuelve rutas relativas al SUBDIRECTORIO: sin recoserle el
|
|
31
|
+
// nombre, «app/Clientes.xne» saldría como «/Clientes.xne» y el Set dejaría de
|
|
32
|
+
// responder por las vistas aplanadas ANIDADAS (este universo es el que consulta
|
|
33
|
+
// `esVistaAplanada`).
|
|
34
|
+
for (const f of ficherosDelProyecto(ruta, prof + 1))
|
|
35
|
+
salida.add(`/${entrada}${f}`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
salida.add("/" + ruta.slice(raiz.length + 1).split(sep).join("/"));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Un enlace roto o un permiso no tumba el arranque; el agente vivirá sin ese fichero.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return salida;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Abre una sesión real: agente, checkpointer, hilo y tracker se construyen UNA vez aquí, y
|
|
49
|
+
* viven en el cierre. Todo lo que sobreviva al primer turno tiene que salir de este cierre:
|
|
50
|
+
* reconstruir el agente fuera (o un `MemorySaver` nuevo por llamada) tiraría la conversación.
|
|
51
|
+
*/
|
|
52
|
+
export async function abrirSesionReal(opciones) {
|
|
53
|
+
const { raiz, entorno } = opciones;
|
|
54
|
+
const checkpointer = new MemorySaver();
|
|
55
|
+
const tracker = createTokenTracker();
|
|
56
|
+
let modelos = opciones.modelos;
|
|
57
|
+
let hilo = `xonecode-${randomUUID()}`;
|
|
58
|
+
const construir = async () => construirAgente({
|
|
59
|
+
raiz,
|
|
60
|
+
ficheros: ficherosDelProyecto(raiz),
|
|
61
|
+
modelos,
|
|
62
|
+
skills: opciones.skills,
|
|
63
|
+
checkpointer: checkpointer,
|
|
64
|
+
tracker,
|
|
65
|
+
});
|
|
66
|
+
let agente = await construir();
|
|
67
|
+
/**
|
|
68
|
+
* Los pendientes de aprobación, del ESTADO y no del resultado del stream.
|
|
69
|
+
*
|
|
70
|
+
* Se leen los crudos (`collectPending`) y no solo los traducidos, porque de ellos sale la
|
|
71
|
+
* RUTA del fichero que se va a tocar (`ficheroDe`). Los argumentos NO se publican
|
|
72
|
+
* —`write_file` lleva el contenido entero—, pero aprobar a ciegas es peor que no aprobar,
|
|
73
|
+
* así que la ruta sí. Una sola llamada a `getState` para las dos cosas.
|
|
74
|
+
*/
|
|
75
|
+
const leerPendientes = async () => {
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
const crudos = collectPending(await agente.getState({
|
|
78
|
+
configurable: { thread_id: hilo },
|
|
79
|
+
}));
|
|
80
|
+
const ficheros = new Map();
|
|
81
|
+
const diffs = new Map();
|
|
82
|
+
for (const c of crudos) {
|
|
83
|
+
const f = ficheroDe(c);
|
|
84
|
+
if (f)
|
|
85
|
+
ficheros.set(c.id, f);
|
|
86
|
+
// El ANTES es el disco: el interrupt pausa ANTES de escribir. La ruta viene del
|
|
87
|
+
// backend, que es relativo a la raíz del proyecto — y si no está en el disco, el
|
|
88
|
+
// fichero es nuevo y su «antes» es la cadena vacía, no un error.
|
|
89
|
+
const vista = cambioDe(c, (ruta) => {
|
|
90
|
+
try {
|
|
91
|
+
return readFileSync(resolve(raiz, ruta), "utf8");
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
if (vista)
|
|
98
|
+
diffs.set(c.id, vista.lineas);
|
|
99
|
+
}
|
|
100
|
+
return { lista: crudos.map(aPendiente), ficheros, diffs };
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Un turno: foto del ANTES → stream con bucle de aprobación → diff contra esa foto.
|
|
104
|
+
*
|
|
105
|
+
* La foto es POR TURNO, no por sesión: con una sola, el diff del turno 3 incluiría lo que
|
|
106
|
+
* escribió el turno 1.
|
|
107
|
+
*/
|
|
108
|
+
const turno = async (peticion, piel) => {
|
|
109
|
+
const instantanea = await tomarInstantanea(raiz, entorno.git);
|
|
110
|
+
let payload = { messages: [new HumanMessage(peticion)] };
|
|
111
|
+
let bitacora = null;
|
|
112
|
+
let ronda = 0;
|
|
113
|
+
// El bucle de aprobación, tal como está en `correrReal` (`cli/run.ts`): una pausa
|
|
114
|
+
// TERMINA la ronda, el interrupt queda en el estado, y se reanuda con un `Command`
|
|
115
|
+
// con las decisiones — también con rejects, que si no se resumen dejan el interrupt
|
|
116
|
+
// colgado para siempre y el modelo nunca llega a saber que se rechazó.
|
|
117
|
+
let cortadoPorTope = false;
|
|
118
|
+
while (true) {
|
|
119
|
+
ronda += 1;
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
121
|
+
const stream = await agente.stream(payload, {
|
|
122
|
+
configurable: { thread_id: hilo },
|
|
123
|
+
streamMode: ["updates", "messages"],
|
|
124
|
+
subgraphs: true,
|
|
125
|
+
});
|
|
126
|
+
bitacora = await correrTurno(aEventos(stream, async () => (await leerPendientes()).lista), piel, {
|
|
127
|
+
avisos: (b) => (b.corrio("verify") ? [] : ["⚠ el verificador no ha corrido en este turno"]),
|
|
128
|
+
});
|
|
129
|
+
const { lista, ficheros, diffs } = await leerPendientes();
|
|
130
|
+
if (lista.length === 0)
|
|
131
|
+
break;
|
|
132
|
+
// El tope existe porque un modelo que insiste tras cada rechazo convierte esto en un
|
|
133
|
+
// ciclo automático de ~200k tokens por ronda (medido en da04). Sin resumir: nada se
|
|
134
|
+
// aplica, pero el interrupt queda en el estado.
|
|
135
|
+
if (ronda >= MAX_APPROVAL_ROUNDS) {
|
|
136
|
+
piel.linea(`\n⚠ tope de ${MAX_APPROVAL_ROUNDS} rondas de aprobación agotado.`);
|
|
137
|
+
piel.linea(` quedaban ${lista.length} sin resolver, y NO se han aplicado.`);
|
|
138
|
+
cortadoPorTope = true;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
if (!opciones.pedirAprobacion) {
|
|
142
|
+
piel.linea(`\n⏸ ${lista.length} escritura(s) piden aprobación, y no hay quién apruebe.`);
|
|
143
|
+
piel.linea(" Nada se ha aprobado y nada se ha aplicado.");
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
const decisiones = await opciones.pedirAprobacion(lista, ficheros, diffs);
|
|
147
|
+
payload = new Command({ resume: buildResume(decisiones) });
|
|
148
|
+
}
|
|
149
|
+
// El diff contra la foto de ESTE turno. Un turno que no tocó nada TIENE que verse igual.
|
|
150
|
+
const cambios = await instantanea.cambios();
|
|
151
|
+
// `cortadoPorTope` viaja en el retorno y no en la bitácora porque es lo que decide el
|
|
152
|
+
// CÓDIGO DE SALIDA de quien invoca: un turno que se quedó con escrituras sin resolver
|
|
153
|
+
// no es un éxito, y CI no puede leerlo como tal.
|
|
154
|
+
return { bitacora: bitacora, cambios, cortadoPorTope };
|
|
155
|
+
};
|
|
156
|
+
return {
|
|
157
|
+
turno,
|
|
158
|
+
/** Para `/modelo`: agente nuevo con los mismos hilo, checkpointer y tracker. */
|
|
159
|
+
async cambiarModelos(nuevos) {
|
|
160
|
+
modelos = nuevos;
|
|
161
|
+
agente = await construir();
|
|
162
|
+
},
|
|
163
|
+
/** Para `/nuevo`: solo cambia el hilo. El agente no se reconstruye. */
|
|
164
|
+
nuevoHilo(id) {
|
|
165
|
+
hilo = id ?? `xonecode-${randomUUID()}`;
|
|
166
|
+
},
|
|
167
|
+
tracker,
|
|
168
|
+
get hilo() {
|
|
169
|
+
return hilo;
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=turnoReal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turnoReal.js","sourceRoot":"","sources":["../../src/agent/turnoReal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const ejecutar = promisify(execFile);
|
|
4
|
+
/** La forma que emite `xone-simulator validate --json` (xone-linter 1.4.0). */
|
|
5
|
+
/**
|
|
6
|
+
* Cuando el simulador no pudo ni empezar. **Medido**: ante una ruta que no existe devuelve
|
|
7
|
+
* `{"success": false, "error": "No existe el directorio del proyecto: …"}` — sin `issues`,
|
|
8
|
+
* y **saliendo con código 0**, así que no lo caza ningún `catch` del proceso.
|
|
9
|
+
*
|
|
10
|
+
* Es un fallo del ENTORNO, no del proyecto, y por eso es una excepción y no un informe en
|
|
11
|
+
* rojo: un informe rojo dice «tu proyecto tiene errores», que aquí sería mentira.
|
|
12
|
+
*/
|
|
13
|
+
export class ErrorDelSimulador extends Error {
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* De la salida del simulador al informe del harness. PURA: sin subprocesos.
|
|
17
|
+
*
|
|
18
|
+
* Separada de quien invoca el binario para poder probar la traducción con el fichero de
|
|
19
|
+
* `__oro__/` — es el patrón del puerto aplicado dentro del puerto, igual que el harness
|
|
20
|
+
* Python separa la función que recibe la sesión ya lista del puerto que la construye.
|
|
21
|
+
*/
|
|
22
|
+
export function aInforme(salida) {
|
|
23
|
+
// La forma de fallo no trae `issues`. Sin esta guarda, `.map` de `undefined` reventaba
|
|
24
|
+
// con un TypeError que le llegaba al usuario TAL CUAL —«Cannot read properties of
|
|
25
|
+
// undefined (reading 'map')»— tapando el mensaje que el simulador ya daba claro.
|
|
26
|
+
if (!Array.isArray(salida.issues)) {
|
|
27
|
+
throw new ErrorDelSimulador(salida.error ?? "el simulador no devolvió hallazgos ni error");
|
|
28
|
+
}
|
|
29
|
+
const hallazgos = salida.issues.map((i) => ({
|
|
30
|
+
code: i.code,
|
|
31
|
+
severidad: i.severity,
|
|
32
|
+
mensaje: i.message,
|
|
33
|
+
fichero: i.location?.file ?? i.file,
|
|
34
|
+
linea: i.location?.line,
|
|
35
|
+
columna: i.location?.column,
|
|
36
|
+
}));
|
|
37
|
+
return { verde: salida.success, hallazgos };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* El verificador real. NO lleva la marca `ES_DOBLE`: es de verdad, y `describe()` lo dirá.
|
|
41
|
+
*
|
|
42
|
+
* `validate` sale con código != 0 cuando encuentra errores, así que un fallo del proceso
|
|
43
|
+
* NO es un fallo del harness: es el resultado. Se distingue por si hubo JSON parseable —
|
|
44
|
+
* confundir las dos cosas es el patrón «fallo del proyecto != fallo del harness».
|
|
45
|
+
*/
|
|
46
|
+
export class SimuladorVerifier {
|
|
47
|
+
binario;
|
|
48
|
+
constructor(binario = "xone-simulator") {
|
|
49
|
+
this.binario = binario;
|
|
50
|
+
}
|
|
51
|
+
async verificar(rutaProyecto) {
|
|
52
|
+
let stdout;
|
|
53
|
+
try {
|
|
54
|
+
({ stdout } = await ejecutar(this.binario, ["validate", rutaProyecto, "--json"]));
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
const conSalida = e;
|
|
58
|
+
if (!conSalida.stdout)
|
|
59
|
+
throw e; // el binario no está, o reventó de verdad
|
|
60
|
+
stdout = conSalida.stdout; // salió != 0 porque HAY hallazgos: es el resultado
|
|
61
|
+
}
|
|
62
|
+
return aInforme(JSON.parse(stdout));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=verificador.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verificador.js","sourceRoot":"","sources":["../../src/agent/verificador.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAErC,+EAA+E;AAC/E;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAiB/C;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,MAA0B;IACjD,uFAAuF;IACvF,kFAAkF;IAClF,iFAAiF;IACjF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,KAAK,IAAI,6CAA6C,CAAC,CAAC;IAC7F,CAAC;IACD,MAAM,SAAS,GAAe,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtD,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,SAAS,EAAE,CAAC,CAAC,QAAiC;QAC9C,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,CAAC,IAAI;QACnC,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE,IAAI;QACvB,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM;KAC5B,CAAC,CAAC,CAAC;IACJ,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,iBAAiB;IACC;IAA7B,YAA6B,UAAU,gBAAgB;QAA1B,YAAO,GAAP,OAAO,CAAmB;IAAG,CAAC;IAE3D,KAAK,CAAC,SAAS,CAAC,YAAoB;QAClC,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACH,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;QACpF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,SAAS,GAAG,CAAwB,CAAC;YAC3C,IAAI,CAAC,SAAS,CAAC,MAAM;gBAAE,MAAM,CAAC,CAAC,CAAC,0CAA0C;YAC1E,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAM,mDAAmD;QACrF,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAuB,CAAC,CAAC;IAC5D,CAAC;CACF"}
|