xonecode 0.19.0 → 0.20.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.
- package/dist/agent/config/modelos.js +13 -8
- package/dist/agent/config/modelos.js.map +1 -1
- package/dist/agent/grafo/atributosXone.js +58 -0
- package/dist/agent/grafo/atributosXone.js.map +1 -0
- package/dist/agent/grafo/xoneAgent.js +3 -2
- package/dist/agent/grafo/xoneAgent.js.map +1 -1
- package/dist/agent/motores/trueforge/capacidades.js +49 -1
- package/dist/agent/motores/trueforge/capacidades.js.map +1 -1
- package/dist/agent/motores/trueforge/esperas.js +61 -0
- package/dist/agent/motores/trueforge/esperas.js.map +1 -0
- package/dist/agent/motores/trueforge/informesDeHijos.js +63 -0
- package/dist/agent/motores/trueforge/informesDeHijos.js.map +1 -0
- package/dist/agent/motores/trueforge/medidaAutomatica.js +118 -0
- package/dist/agent/motores/trueforge/medidaAutomatica.js.map +1 -0
- package/dist/agent/motores/trueforge/memoriaDeEspecialistas.js +186 -0
- package/dist/agent/motores/trueforge/memoriaDeEspecialistas.js.map +1 -0
- package/dist/agent/motores/trueforge/resumenSeguro.js +52 -0
- package/dist/agent/motores/trueforge/resumenSeguro.js.map +1 -0
- package/dist/agent/motores/trueforge/sesionTrueforge.js +485 -27
- package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
- package/dist/agent/motores/trueforge/toolsDeFichero.js +69 -3
- package/dist/agent/motores/trueforge/toolsDeFichero.js.map +1 -1
- package/dist/agent/subagentes/agentesEnDisco.js +71 -18
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
- package/dist/agent/turno/diagnosticoDeTools.js +12 -0
- package/dist/agent/turno/diagnosticoDeTools.js.map +1 -1
- package/dist/agent/turno/informeDeTraza.js +13 -1
- package/dist/agent/turno/informeDeTraza.js.map +1 -1
- package/dist/agent/turno/resumenDeTool.js +3 -0
- package/dist/agent/turno/resumenDeTool.js.map +1 -1
- package/dist/core/agentes.js +7 -1
- package/dist/core/agentes.js.map +1 -1
- package/dist/core/atributosXone.js +187 -0
- package/dist/core/atributosXone.js.map +1 -0
- package/dist/core/compararCapturas.js +80 -14
- package/dist/core/compararCapturas.js.map +1 -1
- package/dist/core/esfuerzo.js +33 -0
- package/dist/core/esfuerzo.js.map +1 -1
- package/dist/core/iconos.js +6 -1
- package/dist/core/iconos.js.map +1 -1
- package/dist/core/modoDeEscritura.js +18 -0
- package/dist/core/modoDeEscritura.js.map +1 -1
- package/dist/core/nucleoXone.js +3 -1
- package/dist/core/nucleoXone.js.map +1 -1
- package/dist/core/selectorDeCarpeta.js +15 -1
- package/dist/core/selectorDeCarpeta.js.map +1 -1
- package/dist/core/settings.js +19 -4
- package/dist/core/settings.js.map +1 -1
- package/dist/core/shellDeAgente.js +39 -0
- package/dist/core/shellDeAgente.js.map +1 -1
- package/dist/core/tareasDelPlan.js +69 -7
- package/dist/core/tareasDelPlan.js.map +1 -1
- package/package.json +1 -1
- package/skills/xone-development/references/datos/appdata-referencia-ampliada.md +2 -2
- package/skills/xone-development/references/datos/appdata.md +5 -3
- package/skills/xone-development/references/fundamentos/errores-comunes.md +34 -0
- package/skills/xone-hotswap/SKILL.md +2 -0
- package/skills/xone-hotswap/scripts/xone-log-android +18 -1
- package/skills/xone-plan-builder/SKILL.md +10 -10
- package/skills/xone-plan-builder/references/TASKS-FORMAT.md +5 -5
- package/skills/xone-spec-builder/SKILL.md +3 -3
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
* (`modeloExterno.ts`).
|
|
24
24
|
* - NO: un presupuesto GLOBAL por turno (la suma de todos los hilos), que deepagents tampoco tiene.
|
|
25
25
|
*/
|
|
26
|
-
import {
|
|
26
|
+
import { TOPE_DE_PREGUNTAS_CONTESTADAS_SOLAS, opcionRecomendada } from "../../../core/modoDeEscritura.js";
|
|
27
|
+
import { claseDeTrabajo } from "../../../core/esfuerzo.js";
|
|
28
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
27
29
|
import { join } from "node:path";
|
|
28
30
|
import winston from "winston";
|
|
29
31
|
import { AgentThread, AgentThreadOrchestrator, EventType, NOOP_AGENT_TRACING, askUserQuestion, contextCompaction, dynamicSubAgents } from "./trueforge.js";
|
|
@@ -51,7 +53,9 @@ import { modeloParaTrueforge } from "./modeloLangchain.js";
|
|
|
51
53
|
import { TOOLS_DE_LECTURA } from "./toolsDeFichero.js";
|
|
52
54
|
import { topeAgotadoDe, traducirEvento } from "./eventosTrueforge.js";
|
|
53
55
|
import { anuncioDeSkills } from "./skillsTrueforge.js";
|
|
54
|
-
import { capabilitiesDe, capacidadDeFecha, capacidadDeFicheros, capacidadDeInstrucciones, capacidadDeNotas, capacidadDePropias, capacidadDeRecortes, capacidadesDelEspecialista, clasesDeTools, toolsDe, } from "./capacidades.js";
|
|
56
|
+
import { capabilitiesDe, capacidadDeFecha, capacidadDeFicheros, capacidadDeAvisoDeVueltas, capacidadDeInstrucciones, capacidadDeNotas, capacidadDePropias, capacidadDeRecortes, capacidadesDelEspecialista, clasesDeTools, toolsDe, } from "./capacidades.js";
|
|
57
|
+
import { buscarMaqueta, medirContraMaqueta, resumenDeMedida, textoDeMedidaAutomatica, ultimaCaptura } from "./medidaAutomatica.js";
|
|
58
|
+
import { anotarEscritura, capacidadDeInformesDeHijos, escrituraConExito, textoDelInforme } from "./informesDeHijos.js";
|
|
55
59
|
import { crearDiagnosticoDeTools } from "../../turno/diagnosticoDeTools.js";
|
|
56
60
|
import { encenderTrazaDeErrores } from "../../trazaDeErroresEnDisco.js";
|
|
57
61
|
import { anotarPaso } from "../../../core/trazaDeErrores.js";
|
|
@@ -59,6 +63,9 @@ import { entornoConDepuracion } from "../../turno/depuracion.js";
|
|
|
59
63
|
import { detalleDe, parametrosDe } from "../../turno/resumenDeTool.js";
|
|
60
64
|
import { apartarMemoria, cargarMemoria, fotoSaneada, guardarMemoria, textoDeMemoriaDescartada } from "./memoriaTrueforge.js";
|
|
61
65
|
import { crearNota, sobrantes } from "./notas.js";
|
|
66
|
+
import { crearMemoriaDeEspecialistas } from "./memoriaDeEspecialistas.js";
|
|
67
|
+
import { crearEsperas } from "./esperas.js";
|
|
68
|
+
import { conResumenSeguro } from "./resumenSeguro.js";
|
|
62
69
|
import { crearControlDeDetencion, RESUMEN_DE_RELLENO } from "./detencion.js";
|
|
63
70
|
import { crearNavegacionXone } from "../../grafo/navegacionXone.js";
|
|
64
71
|
import { hechosDelProyectoDe } from "../../navegacion/hechosEnDisco.js";
|
|
@@ -72,6 +79,7 @@ import { crearUnirSecciones } from "../../grafo/unirSecciones.js";
|
|
|
72
79
|
import { crearIncorporarAdjunto, recibeIncorporarAdjunto } from "../../grafo/incorporarAdjunto.js";
|
|
73
80
|
import { crearCriticaVisual } from "../../grafo/criticaVisual.js";
|
|
74
81
|
import { crearCompararCapturas } from "../../grafo/compararCapturas.js";
|
|
82
|
+
import { crearAtributosXone, NOMBRE_ATRIBUTOS_XONE } from "../../grafo/atributosXone.js";
|
|
75
83
|
import { crearDiferenciaDeCapturas } from "../../grafo/diferenciaDeCapturas.js";
|
|
76
84
|
import { crearLectorDeReferencias } from "../../grafo/lectorDeReferencias.js";
|
|
77
85
|
import { crearTraerDeLaMaquina } from "../../grafo/traerDeLaMaquina.js";
|
|
@@ -93,8 +101,8 @@ const BUCLE_DE_CALIDAD = [
|
|
|
93
101
|
"encargo, y se comprueba con números y con ojos. Tras cada cambio visual: pide al conductor una captura NATIVA",
|
|
94
102
|
"nueva y contrástala con `comparar_capturas` y con `xone_critica_visual`, pasando el diseño como `referencia` en las",
|
|
95
103
|
"dos. Sin referencia el crítico solo dice «nada roto», que no es «se parece». Si la estructura o el crítico señalan",
|
|
96
|
-
"diferencias con el diseño —piezas que faltan, tamaños, formas—, delega el arreglo con esas diferencias concretas y",
|
|
97
|
-
"repite. Para cuando la
|
|
104
|
+
"diferencias con el diseño —piezas que faltan, tamaños, formas—, delega el arreglo en quien MAQUETA la pantalla (el que escribe su `.xne`, no el de los recursos) con esas diferencias concretas y",
|
|
105
|
+
"repite. Para cuando la distancia estructural baje del 10 % en vertical y en horizontal y el crítico no señale diferencias de forma con el diseño, o tras TRES vueltas, y",
|
|
98
106
|
"di lo que sigue faltando en vez de darlo por hecho.",
|
|
99
107
|
].join("\n");
|
|
100
108
|
/**
|
|
@@ -103,6 +111,164 @@ const BUCLE_DE_CALIDAD = [
|
|
|
103
111
|
* delegación es `create_sub_agent` y no tiene descripción por especialista, así que se traduce
|
|
104
112
|
* el nombre de la tool y las fichas van escritas aquí, con la MISMA función que deepagents.
|
|
105
113
|
*/
|
|
114
|
+
/**
|
|
115
|
+
* **El idioma, dicho desde código**, al raíz y a cada especialista. Visto en la consola web (MyAllXOne): el texto entre
|
|
116
|
+
* herramientas y el razonamiento salían en inglés («Let me call…», «I'll start by reconnoitering…») con la persona
|
|
117
|
+
* escribiendo en español. Nuestros prompts están en español pero ninguno lo decía, y encima hay piezas internas en inglés
|
|
118
|
+
* que tiran de él: la identidad que añade TrueForge («You are the Agent…»), las descripciones de las tools y la costumbre
|
|
119
|
+
* de DeepSeek de razonar en inglés.
|
|
120
|
+
*/
|
|
121
|
+
export const IDIOMA_DE_LA_RESPUESTA = "IDIOMA: escribe SIEMPRE en español —lo que dices entre herramientas, tu razonamiento, tus encargos y tus respuestas—, " +
|
|
122
|
+
"aunque algunas instrucciones internas y las descripciones de las herramientas estén en inglés. El código, los nombres de " +
|
|
123
|
+
"ficheros, atributos y funciones, y las citas literales se quedan como son.";
|
|
124
|
+
/** Lo que recibe el agente en modo autónomo cuando pregunta sin marcar ninguna opción como recomendada. */
|
|
125
|
+
export const RESPUESTA_AUTONOMA_SIN_RECOMENDADA = "Modo autónomo: no hay nadie a quien preguntar. Decide tú con lo que dicen el encargo y el plan, sigue, y di en tu respuesta final qué elegiste y por qué.";
|
|
126
|
+
/** Un comando que saca una captura de pantalla del aparato. */
|
|
127
|
+
export const COMANDO_DE_CAPTURA = /xone-captura-android|xone-hotswap\s+shot\b|\bscreencap\b/;
|
|
128
|
+
/** Con cuántas capturas de UN encargo se le avisa a quien ejecuta. */
|
|
129
|
+
export const UMBRALES_DE_CAPTURAS = [2, 4];
|
|
130
|
+
/**
|
|
131
|
+
* El aviso de capturas. El prompt del conductor ya decía que una captura «no es una herramienta de diagnóstico», y una
|
|
132
|
+
* comprobación de una pasada real sacó 18 —61 ficheros y 92 pares casi iguales en total—, casi todas para mirar valores
|
|
133
|
+
* y excepciones que `getText`/`getFields` y el log contestan sin mirar nada. Un texto no es un contador.
|
|
134
|
+
*/
|
|
135
|
+
export function textoDeCapturas(capturas) {
|
|
136
|
+
return [
|
|
137
|
+
`LLEVAS ${String(capturas)} CAPTURAS en este encargo.`,
|
|
138
|
+
"Para un VALOR, un estado o una excepción no hace falta ninguna: `xone-log-android --app` (lo que la app escribió con",
|
|
139
|
+
"`console.log`), `getFields`, `elements` acotado y el log de errores lo dicen sin mirar la pantalla. Una captura es para lo VISUAL (algo cortado, tapado, del color o tamaño equivocado) y una por",
|
|
140
|
+
"cosa que compruebes, con `shot name=<qué pruebas>`. Si solo quieres saber si algo cambió, `diferencia_de_capturas`.",
|
|
141
|
+
"No repitas la misma pantalla: si la anterior ya lo decía, no hay nada nuevo que sacar.",
|
|
142
|
+
].join("\n");
|
|
143
|
+
}
|
|
144
|
+
/** Con cuántas comprobaciones seguidas se le avisa a quien lleva un lazo. El primero es el «tres vueltas» del prompt. */
|
|
145
|
+
export const UMBRALES_DE_VUELTAS = [3, 5];
|
|
146
|
+
/**
|
|
147
|
+
* El aviso: no dice «para», dice «cambia de procedimiento». La misma familia de fallo se vio arreglada de cinco
|
|
148
|
+
* maneras (un control que falta, y al arreglarlo, otro): lo que hay que hacer es aislar la causa una vez y aplicarla
|
|
149
|
+
* a todos los sitios, no reescribir otra vez.
|
|
150
|
+
*/
|
|
151
|
+
export function textoDeVueltas(vueltas) {
|
|
152
|
+
return [
|
|
153
|
+
`LLEVAS ${String(vueltas)} COMPROBACIONES SEGUIDAS con el aparato.`,
|
|
154
|
+
"Si el fallo de la última es de la MISMA FAMILIA que el anterior (la misma excepción, el mismo tipo de control), NO hagas otra",
|
|
155
|
+
"reescritura: (1) di la hipótesis en una frase, (2) pide al de pruebas UN experimento mínimo —un control, un evento— y lee",
|
|
156
|
+
"el resultado BRUTO del aparato, (3) con la causa confirmada, aplica el arreglo a TODOS los sitios parecidos de una vez.",
|
|
157
|
+
"Si tras eso el fallo sigue igual, devuelve el trabajo diciendo qué falta y qué mediste, en vez de otra vuelta.",
|
|
158
|
+
].join("\n");
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* El hilo de un especialista que NO arrancó porque espera a otros: nace ya terminado, con la respuesta al orquestador
|
|
162
|
+
* ya escrita (`preComputedCompletion`), y la librería lo trata como cualquier hijo que acaba. No llama al modelo ni tiene
|
|
163
|
+
* tools: lo único que hace es contestar al que lo pidió.
|
|
164
|
+
*/
|
|
165
|
+
function hiloQueNoArranca(nombre, esperados, params, modelo, logger) {
|
|
166
|
+
const padre = params.parent;
|
|
167
|
+
const texto = `${nombre} NO HA ARRANCADO: espera a que termine ${esperados.join(", ")}, que sigue trabajando y produce lo que ${nombre} necesita. ` +
|
|
168
|
+
`Cuando ${esperados.join(", ")} te devuelva su informe, vuelve a llamar a ${nombre} con ese informe dentro del encargo.`;
|
|
169
|
+
return new AgentThread({
|
|
170
|
+
definition: { modelClient: modelo, iterationLimit: 1 },
|
|
171
|
+
threadId: params.threadId,
|
|
172
|
+
title: params.request.name,
|
|
173
|
+
parent: params.parent,
|
|
174
|
+
agentInfo: { type: "dynamic", ...params.request },
|
|
175
|
+
preComputedCompletion: {
|
|
176
|
+
type: "done",
|
|
177
|
+
output: { role: "assistant", content: texto },
|
|
178
|
+
send_to_parent: { role: "tool", tool_call_id: padre.tool_call_id ?? "", content: texto },
|
|
179
|
+
},
|
|
180
|
+
tracing: NOOP_AGENT_TRACING,
|
|
181
|
+
logger: logger,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Lo que se le cuenta a quien ESPERÓ a otros especialistas, al soltarse: a quién esperó y qué hay en el disco.
|
|
186
|
+
* Solo NOMBRES —los recursos de `icons/` y los `ASSETS.md`/`TASKS.md` del plan—, nunca su contenido: es lo que dejó
|
|
187
|
+
* el otro, que no viaja por ningún canal más que el disco. Sin recursos que nombrar, un aviso corto.
|
|
188
|
+
*/
|
|
189
|
+
export function textoDeLoQueDejaronLosEsperados(raiz, esperadas) {
|
|
190
|
+
let recursos = [];
|
|
191
|
+
try {
|
|
192
|
+
recursos = readdirSync(join(raiz, "icons")).filter((f) => /\.(svg|png|jpg|jpeg)$/i.test(f)).sort();
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
recursos = [];
|
|
196
|
+
}
|
|
197
|
+
let notas = [];
|
|
198
|
+
try {
|
|
199
|
+
notas = readdirSync(join(raiz, ".xonecode", "planes"))
|
|
200
|
+
.flatMap((p) => ["ASSETS.md"].filter((f) => existsSync(join(raiz, ".xonecode", "planes", p, f))).map((f) => `/planes/${p}/${f}`))
|
|
201
|
+
.sort();
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
notas = [];
|
|
205
|
+
}
|
|
206
|
+
return [
|
|
207
|
+
`Antes de ti han terminado ${esperadas.join(", ")}, que trabajaban a la vez o antes que tú.`,
|
|
208
|
+
...(recursos.length === 0 ? [] : [`Lo que hay ahora en icons/ (${String(recursos.length)}): ${recursos.join(", ")}.`]),
|
|
209
|
+
...(notas.length === 0 ? [] : [`Lo que dejó escrito para ti: ${notas.join(", ")}. Léelo antes de escribir.`]),
|
|
210
|
+
"USA los recursos que te sirvan por su nombre exacto, y si no vas a usar alguno, dilo al devolver el trabajo.",
|
|
211
|
+
].join("\n");
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Lo que se le dice a quien puede llamar a otros. Cada llamada de estas es un hilo nuevo que solo sabe lo que le
|
|
215
|
+
* escribas (aunque recuerde sus encargos anteriores de la sesión), y lo que te devuelve es lo que vio, no un
|
|
216
|
+
* veredicto: decidir si lo que escribiste funciona sigue siendo cosa tuya, con lo que ese hijo te traiga.
|
|
217
|
+
*/
|
|
218
|
+
export function textoDelBucle(llama, opciones = {}) {
|
|
219
|
+
const puedeDiseñar = llama.includes("designer-xone");
|
|
220
|
+
return [
|
|
221
|
+
`PUEDES LLAMAR A: ${llama.join(", ")}, con \`create_sub_agent\` (\`name\` exacto y \`input\` autosuficiente).`,
|
|
222
|
+
"Tu bucle es: escribe, llama al de pruebas para desplegar y comprobar, lee lo que te devuelve y corrige.",
|
|
223
|
+
"Dile QUÉ comprobar y qué esperas ver. No lo llames por cada línea que cambies: junta los cambios.",
|
|
224
|
+
"",
|
|
225
|
+
"PRUEBAS FUNCIONALES Y VISUALES, SEPARADAS Y EN ESTE ORDEN:",
|
|
226
|
+
"- Instrumenta tu código con `console.log(\"CALC tecla 8\")`, `console.log(\"CALC resultado \" + r)` en los puntos",
|
|
227
|
+
" que deciden (tecla, expresión, resultado, error). Nunca `ui.showToast` ni `appData.writeConsoleString` para depurar:",
|
|
228
|
+
" el aviso no deja rastro y el segundo es legacy y no llega al log de Android.",
|
|
229
|
+
"- FUNCIONAL: UN encargo con TODOS los casos, como acciones y lo que esperas («teclea 2 + 3 × 4 = → log `CALC resultado",
|
|
230
|
+
" 14`, display 14»). El de pruebas los hace, lee el log con `xone-log-android --app` y te devuelve cada caso: pasa o falla",
|
|
231
|
+
" con lo que salió. SIN capturas. Corrige todos los fallos juntos y repite solo los que fallaron.",
|
|
232
|
+
"- VISUAL, solo cuando lo funcional pasa: UNA captura por ronda. Con ella el harness te trae la medida contra la maqueta;",
|
|
233
|
+
" pasa el crítico, arregla TODAS las diferencias en la misma ronda y solo entonces pide otra captura. Tres rondas como mucho.",
|
|
234
|
+
"- Una pantalla que se parece a la maqueta con datos de ejemplo fijos NO está terminada: la lógica tiene que funcionar.",
|
|
235
|
+
"Lo que te devuelva es lo que ha visto, no un permiso para dar el trabajo por bueno: si dice que la app se cae, se cae.",
|
|
236
|
+
"EMPIEZA POR LO MÍNIMO: no escribas la pantalla entera de una vez. Primero la versión más pequeña que se pueda VER y PULSAR",
|
|
237
|
+
"—el arranque, el display y UN botón—, y pídele al de pruebas que la arranque y la toque. Solo cuando eso funcione, amplía",
|
|
238
|
+
"(el teclado, las funciones, el historial). Un fallo de arranque descubierto con la pantalla entera escrita cuesta reescribirla.",
|
|
239
|
+
...(puedeDiseñar
|
|
240
|
+
? [
|
|
241
|
+
"El LAYOUT y el CSS son TUYOS: si la pantalla se ve mal, lo arreglas tú, no se lo pasas a nadie ni lo devuelves al que te",
|
|
242
|
+
"encargó. `designer-xone` solo hace RECURSOS —iconos de una biblioteca y fondos SVG en `icons/`—: pídeselos juntos, con para",
|
|
243
|
+
"qué control, tamaño en píxeles y colores, y usa los nombres que te devuelva. No le pidas que toque el `.xne` ni el CSS: no puede.",
|
|
244
|
+
"Los recursos te llegan con una TABLA (fichero → control → atributo → tamaño → si lleva el símbolo dentro): úsala y no abras",
|
|
245
|
+
"los SVG para enterarte de lo que ya dice.",
|
|
246
|
+
]
|
|
247
|
+
: []),
|
|
248
|
+
...(opciones.conCritica === true
|
|
249
|
+
? [
|
|
250
|
+
"",
|
|
251
|
+
"PARA JUZGAR UNA PANTALLA tienes `xone_critica_visual` (un modelo aparte mira la captura) y `comparar_capturas` (mide la",
|
|
252
|
+
"estructura contra la maqueta con números). Pídele al de pruebas una captura y que te diga su nombre; pásala con `pantalla` y,",
|
|
253
|
+
"si hay diseño (/diseno/ o un adjunto), como `referencia`. Lo que digan esas dos herramientas manda sobre tu impresión: no",
|
|
254
|
+
"lo descartes como «no fiable»; si no estás de acuerdo, dilo con el dato.",
|
|
255
|
+
"Cada vez que vuelva el de pruebas con una captura, el HARNESS te trae su medida contra la maqueta: no hace falta que",
|
|
256
|
+
"la pidas tú. El juicio es tuyo: con esa cifra y el crítico decides si sigues; no se lo devuelvas al que te encargó para que juzgue.",
|
|
257
|
+
]
|
|
258
|
+
: []),
|
|
259
|
+
"",
|
|
260
|
+
"NO TERMINAS hasta que se cumpla el CRITERIO DE ACEPTACIÓN de tu encargo. Si el encargo no lo trae, es: (1) todas las casillas",
|
|
261
|
+
"de la tarea en el `TASKS.md` comprobadas, (2) la app arranca y hace lo que pide, y (3) con diseño, la distancia que mide",
|
|
262
|
+
"`comparar_capturas` por debajo del 10 % en vertical y en horizontal, que ENCAJE en la pantalla (cada borde del contenido a",
|
|
263
|
+
"un 3 % o menos del de la maqueta: que llegue a los lados y abajo como ella), y el crítico sin diferencias de forma ni de estructura.",
|
|
264
|
+
"Lo que XOne NO puede reproducir no cuenta y no lo persigas: las tipografías del diseño (no hay .ttf), el desenfoque de fondo y",
|
|
265
|
+
"las animaciones; una sombra o un degradado se aproximan con un SVG. Tienes TRES vueltas de corregir y volver a comprobar para",
|
|
266
|
+
"llegar; si al agotarlas algo",
|
|
267
|
+
"sigue sin cumplirse, devuelve el trabajo diciendo QUÉ falta, con lo que midieron el crítico y la medida tal cual. Devolver antes",
|
|
268
|
+
"porque «ya funciona» es dejar el trabajo a medias. Al devolver cuenta lo que se midió: el juicio final no es tuyo, y el arnés",
|
|
269
|
+
"lo revisa igual.",
|
|
270
|
+
].join("\n");
|
|
271
|
+
}
|
|
106
272
|
export function notaDeDelegacion(agentes, opciones = {}) {
|
|
107
273
|
if (agentes.length === 0)
|
|
108
274
|
return "";
|
|
@@ -113,6 +279,36 @@ export function notaDeDelegacion(agentes, opciones = {}) {
|
|
|
113
279
|
"entiende `create_sub_agent` y `name`. Las fichas de los especialistas:",
|
|
114
280
|
...agentes.map((a) => `- ${a.nombre}: ${fichaDeAgente(a, opciones)}`),
|
|
115
281
|
...(opciones.conComparacion === true ? ["", BUCLE_DE_CALIDAD] : []),
|
|
282
|
+
...(opciones.conEsperas === true
|
|
283
|
+
? [
|
|
284
|
+
"",
|
|
285
|
+
"ESPERAS ENTRE ESPECIALISTAS: developer-xone NO arranca mientras designer-xone esté trabajando (lo que el diseñador produce",
|
|
286
|
+
"—los recursos de `icons/`— lo necesita el `.xne`). Si los lanzas a la vez, la llamada al desarrollador te vuelve AL INSTANTE",
|
|
287
|
+
"diciendo que no ha arrancado; cuando el diseñador te devuelva su informe, llama de nuevo al desarrollador con ESE INFORME dentro",
|
|
288
|
+
"del encargo: su TABLA de recursos (fichero → control → atributo → tamaño) TAL CUAL, no un resumen, y no revises tú los SVG. Lo más simple es lanzar primero al diseñador y, con su informe, al desarrollador.",
|
|
289
|
+
]
|
|
290
|
+
: []),
|
|
291
|
+
...(opciones.conBucle === true
|
|
292
|
+
? [
|
|
293
|
+
"",
|
|
294
|
+
"BUCLE DEL DESARROLLADOR: developer-xone prueba lo que escribe él mismo, llamando a device-controller (despliega, toca y lee",
|
|
295
|
+
"el log), maqueta y arregla lo visual él mismo —designer-xone solo le hace iconos y fondos SVG—, y corrige hasta cumplir el CRITERIO DE ACEPTACIÓN que le des. Por eso",
|
|
296
|
+
"cada encargo a developer-xone lleva el CRITERIO ESCRITO y MEDIBLE: qué casillas del plan tiene que dejar comprobadas, qué",
|
|
297
|
+
"tiene que hacer la app, y, si hay diseño, que la distancia de `comparar_capturas` quede por debajo del 10 % en vertical y en",
|
|
298
|
+
"horizontal, que ENCAJE en la pantalla (cada borde a un 3 % o menos del de la maqueta) y que el crítico no señale diferencias de forma o de estructura (di dónde está la maqueta). No se puede pedir",
|
|
299
|
+
"lo que XOne no reproduce —las tipografías del diseño, el desenfoque de fondo, las animaciones—. Sin criterio, devolverá el",
|
|
300
|
+
"trabajo en cuanto funcione. No le encargues a device-controller comprobar SU",
|
|
301
|
+
"trabajo, ni a designer-xone lo visual de su pantalla (designer-xone no puede tocar un `.xne` ni el CSS): llama tú al diseñador",
|
|
302
|
+
"para los RECURSOS que la pantalla necesite, antes del desarrollador. Lo que developer-xone te devuelva es lo que él vio y midió, no un veredicto: el juicio final es tuyo y del arnés.",
|
|
303
|
+
]
|
|
304
|
+
: []),
|
|
305
|
+
...(opciones.conMemoria === true
|
|
306
|
+
? [
|
|
307
|
+
"",
|
|
308
|
+
"MEMORIA DE LOS ESPECIALISTAS: en esta sesión cada especialista RECUERDA sus encargos anteriores (lo que leyó,",
|
|
309
|
+
"lo que descubrió y lo que hizo). Al volver a llamar a uno, dale solo lo NUEVO: no repitas lo que ya sabe.",
|
|
310
|
+
]
|
|
311
|
+
: []),
|
|
116
312
|
].join("\n");
|
|
117
313
|
}
|
|
118
314
|
/** Lo que cada clase NO puede hacer, que la lista de tools no dice sola. */
|
|
@@ -125,6 +321,14 @@ const LIMITES_DE = {
|
|
|
125
321
|
* La frase de las tools que TIENE, sacada de los nombres que de verdad se le montan: escrita a
|
|
126
322
|
* mano se quedaba vieja en cuanto se añadía una —la nota decía cuatro y el modelo veía seis—.
|
|
127
323
|
*/
|
|
324
|
+
/**
|
|
325
|
+
* CUÁNDO usar el índice de atributos, junto a la lista de tools y solo a quien la tiene. Medido en calc13: con la tool
|
|
326
|
+
* montada y descrita, el consultor la consultó 9 veces y el desarrollador —quien escribe los atributos— ninguna, con 13
|
|
327
|
+
* lecturas de la skill: su prompt solo la nombraba en la lista, y el `SKILL.md` le manda a las referencias. No va en el
|
|
328
|
+
* `SKILL.md` porque ese llega también a los motores externos, que no tienen la tool.
|
|
329
|
+
*/
|
|
330
|
+
export const USO_DE_XONE_ATRIBUTOS = "Para saber si un atributo XML existe en un nodo (coll, group, frame, prop) y qué hace, pregunta PRIMERO a `xone_atributos`: " +
|
|
331
|
+
"contesta en una línea desde las tablas de la skill. Lee la referencia de la skill solo si no lo encuentra, o si necesitas ejemplos o matices.";
|
|
128
332
|
export function notaDeTools(nombres, limite) {
|
|
129
333
|
return `NOTA DEL HARNESS: aunque tu identidad diga lo contrario, NO tienes las mismas tools que quien te delega. Tienes ${nombres
|
|
130
334
|
.map((n) => `\`${n}\``)
|
|
@@ -227,6 +431,36 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
227
431
|
* viaja por el cable como nombre de un especialista tiene que serlo de verdad.
|
|
228
432
|
*/
|
|
229
433
|
const especialistaDeHilo = new Map();
|
|
434
|
+
const conMemoriaDeEspecialistas = opciones.memoriaDeEspecialistas ?? process.env.XONECODE_SIN_MEMORIA_DE_ESPECIALISTAS !== "1";
|
|
435
|
+
// Encendido por omisión desde que se midió (calc9-calc15); `XONECODE_BUCLE_DEL_DEVELOPER=0` lo apaga para comparar.
|
|
436
|
+
const conBucleDelDesarrollador = opciones.bucleDelDesarrollador ?? process.env.XONECODE_BUCLE_DEL_DEVELOPER !== "0";
|
|
437
|
+
const conEsperas = opciones.esperasEntreHijos ?? true;
|
|
438
|
+
/** Cuántas comprobaciones (llamadas a quien EJECUTA) lleva cada hilo que lleva un lazo. */
|
|
439
|
+
const vueltasDeCadaHilo = new Map();
|
|
440
|
+
/** Cuántas CAPTURAS ha sacado cada hilo que ejecuta comandos (`xone-captura-android`, `xone-hotswap shot`). */
|
|
441
|
+
const capturasDeCadaHilo = new Map();
|
|
442
|
+
/** Lo que escribió cada hijo en su encargo, quién lo llamó, y los informes que esperan a ese padre (`informesDeHijos.ts`). */
|
|
443
|
+
const escritosDeCadaHilo = new Map();
|
|
444
|
+
const padreDeHilo = new Map();
|
|
445
|
+
const informesPendientes = new Map();
|
|
446
|
+
/** Las escrituras pedidas y aún sin respuesta, por hilo e id: la respuesta de una aprobada llega en OTRO `paso`. */
|
|
447
|
+
const escriturasEnVuelo = new Map();
|
|
448
|
+
/** Para la medida automática (`medidaAutomatica.ts`): cuándo nació cada hilo, su última escritura, su última crítica y la última medida que recibió. */
|
|
449
|
+
const nacimientoDeHilo = new Map();
|
|
450
|
+
const ultimaEscrituraDeHilo = new Map();
|
|
451
|
+
const ultimaCriticaDeHilo = new Map();
|
|
452
|
+
const ultimaMedidaDeHilo = new Map();
|
|
453
|
+
/** Cuántas rondas VISUALES lleva cada hilo: una por medida automática recibida (`RONDAS_VISUALES`). */
|
|
454
|
+
const rondasVisualesDeHilo = new Map();
|
|
455
|
+
const capacidadDeInformes = capacidadDeInformesDeHijos((hiloPadre) => {
|
|
456
|
+
const textos = informesPendientes.get(hiloPadre) ?? [];
|
|
457
|
+
informesPendientes.delete(hiloPadre);
|
|
458
|
+
return textos;
|
|
459
|
+
});
|
|
460
|
+
const esperas = crearEsperas();
|
|
461
|
+
const memoriaDeEspecialistas = crearMemoriaDeEspecialistas();
|
|
462
|
+
/** Los hijos con memoria que corren ahora: su hilo, para leer su historial al terminar. */
|
|
463
|
+
const hijosConMemoria = new Map();
|
|
230
464
|
/** Lo que el agente dejó en `/artefactos/` y aún no se ha anunciado: un artefacto se escribe
|
|
231
465
|
* SIN aprobación, así que tiene que ANUNCIARSE, como en deepagents. */
|
|
232
466
|
const artefactosPorAnunciar = [];
|
|
@@ -333,25 +567,31 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
333
567
|
...(carpeta === undefined ? {} : { artefactos: carpeta }),
|
|
334
568
|
...(opciones.adjuntos === undefined ? {} : { adjuntos: opciones.adjuntos }),
|
|
335
569
|
});
|
|
570
|
+
/** El crítico y la medida, con las MISMAS piezas para quien las tenga: el raíz, y con el bucle encendido el desarrollador. */
|
|
571
|
+
const herramientasDeJuicio = () => carpeta === undefined
|
|
572
|
+
? []
|
|
573
|
+
: [
|
|
574
|
+
crearCriticaVisual({
|
|
575
|
+
leerArtefacto: async (nombre) => readFileSync(join(carpeta, nombre)),
|
|
576
|
+
invocar: invocarVisualConModelos({ paraPapel: (p) => modelos.paraPapel(p) }),
|
|
577
|
+
// La maqueta casi nunca está en /artefactos/: la trae la persona (/adjuntos/) o vive en /diseno/.
|
|
578
|
+
leerReferencia: lectorDeReferencias,
|
|
579
|
+
}),
|
|
580
|
+
// Medir la estructura contra la maqueta: la otra mitad del crítico, con el raíz como él.
|
|
581
|
+
crearCompararCapturas({ leerArtefacto: async (nombre) => readFileSync(join(carpeta, nombre)), leerReferencia: lectorDeReferencias }),
|
|
582
|
+
];
|
|
583
|
+
// ¿Existe este atributo en este nodo? A todos, como la navegación: es de lectura y contesta desde la skill del paquete.
|
|
584
|
+
const atributos = crearAtributosXone();
|
|
336
585
|
const propiasDelRaiz = [
|
|
337
586
|
navegacion(),
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
crearCriticaVisual({
|
|
342
|
-
leerArtefacto: async (nombre) => readFileSync(join(carpeta, nombre)),
|
|
343
|
-
invocar: invocarVisualConModelos({ paraPapel: (p) => modelos.paraPapel(p) }),
|
|
344
|
-
// La maqueta casi nunca está en /artefactos/: la trae la persona (/adjuntos/) o vive en /diseno/.
|
|
345
|
-
leerReferencia: lectorDeReferencias,
|
|
346
|
-
}),
|
|
347
|
-
// Medir la estructura contra la maqueta: la otra mitad del crítico, con el raíz como él.
|
|
348
|
-
crearCompararCapturas({ leerArtefacto: async (nombre) => readFileSync(join(carpeta, nombre)), leerReferencia: lectorDeReferencias }),
|
|
349
|
-
crearTraerDeLaMaquina({ carpeta, alEscribir: anotarArtefacto }),
|
|
350
|
-
]),
|
|
587
|
+
atributos,
|
|
588
|
+
...herramientasDeJuicio(),
|
|
589
|
+
...(carpeta === undefined ? [] : [crearTraerDeLaMaquina({ carpeta, alEscribir: anotarArtefacto })]),
|
|
351
590
|
];
|
|
352
591
|
const propiasDe = (agente) => [
|
|
353
592
|
crearBusquedaRegex(backend),
|
|
354
593
|
navegacion(),
|
|
594
|
+
atributos,
|
|
355
595
|
...(carpeta !== undefined && (agente.escribeEn ?? []).length > 0
|
|
356
596
|
? [crearCopiarArtefacto({ raiz, carpetaDeArtefactos: carpeta, perfil: agente })]
|
|
357
597
|
: []),
|
|
@@ -377,6 +617,10 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
377
617
|
: []),
|
|
378
618
|
// Y marcar en el plan lo comprobado en el aparato, con el reparto de deepagents: a quien ejecuta.
|
|
379
619
|
...(agente.ejecucion === true ? [crearMarcarCriteriosDelPlan({ raiz })] : []),
|
|
620
|
+
// El BUCLE del desarrollador: quien puede llamar al de pruebas también tiene el crítico y la medida, para
|
|
621
|
+
// juzgar la pantalla con lo que el otro capture. El veredicto sigue siendo de un modelo aparte, no suyo, y el
|
|
622
|
+
// juez final del arnés revisa igual (`textoDelBucle`).
|
|
623
|
+
...(conBucleDelDesarrollador && (agente.llama?.length ?? 0) > 0 ? herramientasDeJuicio() : []),
|
|
380
624
|
];
|
|
381
625
|
/**
|
|
382
626
|
* **Un cliente de modelo por papel, modelo y esfuerzo, que dura la SESIÓN** —hasta `/modelo`—, y
|
|
@@ -453,8 +697,37 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
453
697
|
* LECTURA y sin shell: un nombre inventado no tumba el turno ni se lleva la escritura.
|
|
454
698
|
*/
|
|
455
699
|
const crearHijo = async (params) => {
|
|
456
|
-
|
|
700
|
+
// Quién lo pide: el raíz, o un especialista que llama a otro. Un especialista solo puede llamar a los que su
|
|
701
|
+
// `.md` declara en `llama` y con el interruptor puesto; lo demás cae al ayudante genérico de solo lectura,
|
|
702
|
+
// el mismo de un nombre inventado, y se anota en la traza.
|
|
703
|
+
const quienPide = params.parent === undefined ? undefined : especialistaDeHilo.get(params.parent.thread_id ?? "");
|
|
704
|
+
const permitidos = quienPide === undefined ? undefined : (especialistas().find((a) => a.nombre === quienPide)?.llama ?? []);
|
|
705
|
+
const concedido = permitidos === undefined || (conBucleDelDesarrollador && permitidos.includes(params.request.name));
|
|
706
|
+
if (!concedido)
|
|
707
|
+
anotarPaso("trueforge.llamada", `${quienPide ?? "?"} pidió a ${params.request.name}: no está en su lista`)();
|
|
708
|
+
const agente = concedido ? especialistas().find((a) => a.nombre === params.request.name) : undefined;
|
|
709
|
+
// Cuenta una COMPROBACIÓN: quien lleva un lazo llamó a quien ejecuta. Es lo que dispara el aviso de vueltas.
|
|
710
|
+
if (concedido && agente?.ejecucion === true && params.parent !== undefined) {
|
|
711
|
+
const padre = params.parent.thread_id ?? "";
|
|
712
|
+
if (padre !== "")
|
|
713
|
+
vueltasDeCadaHilo.set(padre, (vueltasDeCadaHilo.get(padre) ?? 0) + 1);
|
|
714
|
+
}
|
|
715
|
+
// ¿Arranca? Un especialista que declara `espera` NO arranca mientras haya vivo —o anunciado por el mismo mensaje del
|
|
716
|
+
// orquestador— uno de esos: se le devuelve al instante diciéndolo, y el orquestador lo llama de nuevo con el informe del
|
|
717
|
+
// otro. No se espera bloqueando: bloqueaba a la librería, que no devuelve el control mientras un hilo no termine su
|
|
718
|
+
// paso, y una aprobación del otro hilo no se atendía (`esperas.ts`).
|
|
719
|
+
const debeEsperar = conEsperas && agente !== undefined ? esperas.vivos(agente.espera ?? [], params.threadId) : [];
|
|
720
|
+
if (agente !== undefined && debeEsperar.length > 0) {
|
|
721
|
+
anotarPaso("trueforge.espera", `${agente.nombre} no arranca: espera a ${debeEsperar.join(", ")}`)();
|
|
722
|
+
diagnostico?.memoria?.(agente.nombre, "arranca-sin", `espera a ${debeEsperar.join(", ")}`);
|
|
723
|
+
return hiloQueNoArranca(agente.nombre, debeEsperar, params, llm, logger);
|
|
724
|
+
}
|
|
457
725
|
detencion.nacio(params.threadId);
|
|
726
|
+
esperas.nacio(agente?.nombre ?? params.request.name, params.threadId);
|
|
727
|
+
const hiloDelPadre = params.parent?.thread_id;
|
|
728
|
+
if (hiloDelPadre !== undefined && hiloDelPadre !== "")
|
|
729
|
+
padreDeHilo.set(params.threadId, hiloDelPadre);
|
|
730
|
+
nacimientoDeHilo.set(params.threadId, Date.now());
|
|
458
731
|
quienEs.set(params.threadId, agente?.nombre ?? params.request.name);
|
|
459
732
|
if (agente !== undefined)
|
|
460
733
|
especialistaDeHilo.set(params.threadId, agente.nombre);
|
|
@@ -466,6 +739,13 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
466
739
|
backend: backend,
|
|
467
740
|
propias: propiasDe,
|
|
468
741
|
notas: capacidadDeNotasDeLaSesion,
|
|
742
|
+
puedeLlamar: (a) => conBucleDelDesarrollador && (a.llama?.length ?? 0) > 0,
|
|
743
|
+
informes: capacidadDeInformes,
|
|
744
|
+
vueltas: (a) => a.ejecucion === true
|
|
745
|
+
? capacidadDeAvisoDeVueltas({ vueltasDe: (hilo) => capturasDeCadaHilo.get(hilo) ?? 0, umbrales: UMBRALES_DE_CAPTURAS, texto: textoDeCapturas })
|
|
746
|
+
: conBucleDelDesarrollador && (a.llama?.length ?? 0) > 0
|
|
747
|
+
? capacidadDeAvisoDeVueltas({ vueltasDe: (hilo) => vueltasDeCadaHilo.get(hilo) ?? 0, umbrales: UMBRALES_DE_VUELTAS, texto: textoDeVueltas })
|
|
748
|
+
: undefined,
|
|
469
749
|
conShell: () => montarBackend({
|
|
470
750
|
entorno: entornoDeLaShellDelProyecto(raiz, opciones.artefactos),
|
|
471
751
|
// El MISMO patrón que ya usa `modeloParaTrueforge` un poco más arriba en este
|
|
@@ -475,19 +755,54 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
475
755
|
}),
|
|
476
756
|
});
|
|
477
757
|
const nombres = toolsDe(piezas);
|
|
478
|
-
const nota =
|
|
758
|
+
const nota = [
|
|
759
|
+
notaDeTools(nombres, agente === undefined ? LIMITE_DEL_GENERICO : LIMITES_DE[clase]),
|
|
760
|
+
...(nombres.includes(NOMBRE_ATRIBUTOS_XONE) ? [USO_DE_XONE_ATRIBUTOS] : []),
|
|
761
|
+
].join("\n");
|
|
479
762
|
const instrucciones = agente === undefined
|
|
480
|
-
? `${nota} Contesta con lo que encuentres y dónde.`
|
|
481
|
-
: [
|
|
763
|
+
? `${IDIOMA_DE_LA_RESPUESTA}\n\n${nota} Contesta con lo que encuentres y dónde.`
|
|
764
|
+
: [
|
|
765
|
+
IDIOMA_DE_LA_RESPUESTA,
|
|
766
|
+
"",
|
|
767
|
+
promptDeAgente(agente, repartirSkills(agente, disponibles)),
|
|
768
|
+
"",
|
|
769
|
+
nota,
|
|
770
|
+
anuncioDeSkills(agente, catalogo),
|
|
771
|
+
...(conBucleDelDesarrollador && (agente.llama?.length ?? 0) > 0 ? ["", textoDelBucle(agente.llama ?? [], { conCritica: carpeta !== undefined })] : []),
|
|
772
|
+
]
|
|
482
773
|
.filter((l) => l !== undefined)
|
|
483
774
|
.join("\n")
|
|
484
775
|
.trimEnd();
|
|
485
776
|
const papel = agente?.soloLectura === true ? "rapido" : "trabajo";
|
|
777
|
+
const claseDeEsfuerzo = agente === undefined ? undefined : claseDeTrabajo(agente);
|
|
778
|
+
// La memoria del especialista: su conversación anterior de ESTA sesión, si la hay y se puede usar.
|
|
779
|
+
const apertura = conMemoriaDeEspecialistas && agente !== undefined ? memoriaDeEspecialistas.abrir(agente.nombre, params.threadId) : undefined;
|
|
780
|
+
const previa = apertura !== undefined && "previa" in apertura ? apertura.previa : undefined;
|
|
781
|
+
if (agente !== undefined && apertura !== undefined) {
|
|
782
|
+
if (previa !== undefined)
|
|
783
|
+
diagnostico?.memoria?.(agente.nombre, "arranca-con", "historial de la sesión", previa.tokens);
|
|
784
|
+
else
|
|
785
|
+
diagnostico?.memoria?.(agente.nombre, "arranca-sin", "sin" in apertura ? apertura.sin : "primera");
|
|
786
|
+
}
|
|
787
|
+
// Si lo que esperaba ya terminó en esta conversación, sabe qué dejó (solo nombres, nunca contenido).
|
|
788
|
+
const esperado = conEsperas && agente !== undefined ? esperas.terminados(agente.espera ?? []) : [];
|
|
789
|
+
const avisoDeLoEsperado = esperado.length > 0 ? `${textoDeLoQueDejaronLosEsperados(raiz, esperado)}\n\n` : "";
|
|
790
|
+
const mensajesIniciales = previa === undefined
|
|
791
|
+
? [{ role: "user", content: `${avisoDeLoEsperado}${params.request.input}` }]
|
|
792
|
+
: [
|
|
793
|
+
...previa.mensajes,
|
|
794
|
+
{
|
|
795
|
+
role: "user",
|
|
796
|
+
content: "NUEVO ENCARGO. Ya trabajaste antes en esta sesión: lo de arriba es tu conversación anterior, con lo que leíste y decidiste. " +
|
|
797
|
+
"No repitas lecturas de ficheros que no hayan cambiado.\n\n" +
|
|
798
|
+
`${avisoDeLoEsperado}${params.request.input}`,
|
|
799
|
+
},
|
|
800
|
+
];
|
|
486
801
|
const definicionDelHijo = {
|
|
487
802
|
modelClient: modeloParaTrueforge({
|
|
488
803
|
modelo: () => agente?.modelo === undefined
|
|
489
|
-
? clienteDe(`papel:${papel}:${agente?.esfuerzo ?? ""}`, () => modelos.paraPapel(papel, agente?.esfuerzo))
|
|
490
|
-
: clienteDe(`modelo:${agente.modelo}:${agente.esfuerzo ?? ""}`, () => modelos.paraModelo(agente.modelo, agente.esfuerzo)),
|
|
804
|
+
? clienteDe(`papel:${papel}:${agente?.esfuerzo ?? ""}:${claseDeEsfuerzo ?? ""}:${agente?.pensamiento ?? ""}`, () => modelos.paraPapel(papel, agente?.esfuerzo, claseDeEsfuerzo, agente?.pensamiento))
|
|
805
|
+
: clienteDe(`modelo:${agente.modelo}:${agente.esfuerzo ?? ""}:${claseDeEsfuerzo ?? ""}:${agente.pensamiento ?? ""}`, () => modelos.paraModelo(agente.modelo, agente.esfuerzo, claseDeEsfuerzo, agente.pensamiento)),
|
|
491
806
|
senal: () => aborto?.signal,
|
|
492
807
|
alRazonar,
|
|
493
808
|
soloTexto: () => (detencion.soloTexto(params.threadId) ? RESUMEN_DE_RELLENO : undefined),
|
|
@@ -498,11 +813,11 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
498
813
|
anotarPaso("trueforge.detener", `${quienEs.get(params.threadId) ?? params.threadId}: ${n} tool call(s) tiradas`)();
|
|
499
814
|
},
|
|
500
815
|
}),
|
|
501
|
-
messages:
|
|
816
|
+
messages: mensajesIniciales,
|
|
502
817
|
// Los topes MEDIDOS de deepagents: el conductor más, porque cada paso suyo es un comando.
|
|
503
818
|
iterationLimit: agente?.ejecucion === true ? TOPE_DE_LLAMADAS_DEL_CONDUCTOR : TOPE_DE_LLAMADAS_DEL_ESPECIALISTA,
|
|
504
819
|
};
|
|
505
|
-
|
|
820
|
+
const hijo = new AgentThread({
|
|
506
821
|
definition: definicionDelHijo,
|
|
507
822
|
threadId: params.threadId,
|
|
508
823
|
title: params.request.name,
|
|
@@ -527,6 +842,9 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
527
842
|
tracing: NOOP_AGENT_TRACING,
|
|
528
843
|
logger,
|
|
529
844
|
});
|
|
845
|
+
if (agente !== undefined && apertura !== undefined)
|
|
846
|
+
hijosConMemoria.set(params.threadId, { nombre: agente.nombre, hilo: hijo, iniciales: mensajesIniciales });
|
|
847
|
+
return hijo;
|
|
530
848
|
};
|
|
531
849
|
/**
|
|
532
850
|
* El árbol de hilos de UNA conversación. Se rehace con `nuevoHilo`, al reabrir una sesión
|
|
@@ -538,9 +856,13 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
538
856
|
/** La pregunta del orquestador que espera respuesta: el siguiente mensaje la contesta. */
|
|
539
857
|
let preguntaEnEspera;
|
|
540
858
|
const nuevoOrquestador = (foto) => {
|
|
859
|
+
// Los hijos que corrían no pueden seguir; lo que aprendieron sí se queda.
|
|
860
|
+
memoriaDeEspecialistas.darPorMuertos();
|
|
861
|
+
esperas.darPorMuertos();
|
|
862
|
+
hijosConMemoria.clear();
|
|
541
863
|
const definicion = {
|
|
542
864
|
modelClient: llm,
|
|
543
|
-
instruction: [promptOrquestador(especialistas()), notaDeDelegacion(especialistas(), { conIconos: opciones.iconos !== undefined, conComparacion: carpeta !== undefined })].filter((l) => l !== "").join("\n\n"),
|
|
865
|
+
instruction: [IDIOMA_DE_LA_RESPUESTA, promptOrquestador(especialistas()), notaDeDelegacion(especialistas(), { conIconos: opciones.iconos !== undefined, conComparacion: carpeta !== undefined, conMemoria: conMemoriaDeEspecialistas, conBucle: conBucleDelDesarrollador, conEsperas })].filter((l) => l !== "").join("\n\n"),
|
|
544
866
|
// Por TURNO, porque el raíz se rehace desde su foto al final de cada uno (ver `turno`).
|
|
545
867
|
iterationLimit: LIMITE_DE_LLAMADAS_DEL_RAIZ,
|
|
546
868
|
};
|
|
@@ -557,6 +879,7 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
557
879
|
capacidadDeRecortes(backend),
|
|
558
880
|
capacidadDeFecha(),
|
|
559
881
|
capacidadDeNotasDeLaSesion,
|
|
882
|
+
capacidadDeInformes,
|
|
560
883
|
]),
|
|
561
884
|
/**
|
|
562
885
|
* **La conversación se RESUME al mismo umbral que deepagents** (`UMBRAL_RESUMEN_TOKENS`):
|
|
@@ -565,7 +888,10 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
565
888
|
* instrucciones van en el prompt de sistema —aquí `instruction`, en un hijo su
|
|
566
889
|
* capability—, que no se compacta.
|
|
567
890
|
*/
|
|
568
|
-
contextCompaction({
|
|
891
|
+
contextCompaction({
|
|
892
|
+
definition: { ...definicion, modelClient: conResumenSeguro(definicion.modelClient, (t) => anotarPaso("trueforge.compactacion", t)()) },
|
|
893
|
+
compactionThresholdTokens: UMBRAL_RESUMEN_TOKENS,
|
|
894
|
+
}),
|
|
569
895
|
// `ask_user_question`, SOLO en el raíz —la librería tampoco se la da a un hijo—: es el
|
|
570
896
|
// único que tiene a una persona delante.
|
|
571
897
|
askUserQuestion(),
|
|
@@ -626,6 +952,39 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
626
952
|
* `thread_id`: mandarla al raíz no la contestaría —o la rechazaría por hilo desconocido—.
|
|
627
953
|
* Por eso las tool calls se apuntan por hilo Y por id: dos hijos pueden repetir id.
|
|
628
954
|
*/
|
|
955
|
+
/**
|
|
956
|
+
* Cuando vuelve quien EJECUTA, la medida de su última captura contra la maqueta, para quien lo llamó
|
|
957
|
+
* (`medidaAutomatica.ts`). Sin carpeta de artefactos, sin maqueta o sin captura nueva, nada. Un fallo al abrir o
|
|
958
|
+
* decodificar se anota y no tumba el turno.
|
|
959
|
+
*/
|
|
960
|
+
function medidaTrasProbar(deHilo, hiloPadre, quien) {
|
|
961
|
+
if (carpeta === undefined || quien?.ejecucion !== true)
|
|
962
|
+
return undefined;
|
|
963
|
+
const maqueta = buscarMaqueta(raiz, opciones.adjuntos);
|
|
964
|
+
const captura = ultimaCaptura(carpeta, nacimientoDeHilo.get(deHilo) ?? Date.now());
|
|
965
|
+
if (maqueta === undefined || captura === undefined)
|
|
966
|
+
return undefined;
|
|
967
|
+
try {
|
|
968
|
+
const comparacion = medirContraMaqueta(join(carpeta, captura), maqueta.disco);
|
|
969
|
+
ultimaMedidaDeHilo.set(hiloPadre, resumenDeMedida(comparacion, captura));
|
|
970
|
+
const ronda = (rondasVisualesDeHilo.get(hiloPadre) ?? 0) + 1;
|
|
971
|
+
rondasVisualesDeHilo.set(hiloPadre, ronda);
|
|
972
|
+
const escribio = ultimaEscrituraDeHilo.get(hiloPadre);
|
|
973
|
+
const critico = ultimaCriticaDeHilo.get(hiloPadre);
|
|
974
|
+
return textoDeMedidaAutomatica({
|
|
975
|
+
quien: quien.nombre,
|
|
976
|
+
captura,
|
|
977
|
+
maqueta: maqueta.virtual,
|
|
978
|
+
comparacion,
|
|
979
|
+
pedirCritica: critico === undefined || (escribio !== undefined && escribio > critico),
|
|
980
|
+
ronda,
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
catch (e) {
|
|
984
|
+
anotarPaso("trueforge.medida", `no se pudo medir ${captura}: ${e instanceof Error ? e.message : String(e)}`)();
|
|
985
|
+
return undefined;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
629
988
|
async function* paso(lote, senal, pendientes, preguntas = []) {
|
|
630
989
|
for await (const _ of orquestador.send(lote))
|
|
631
990
|
void _;
|
|
@@ -648,13 +1007,22 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
648
1007
|
args = {};
|
|
649
1008
|
}
|
|
650
1009
|
llamadas.set(claveDe(deHilo, t.id), { nombre: t.function.name, args });
|
|
1010
|
+
if (t.function.name === "write_file" || t.function.name === "edit_file")
|
|
1011
|
+
escriturasEnVuelo.set(claveDe(deHilo, t.id), { nombre: t.function.name, args });
|
|
1012
|
+
if (t.function.name === "xone_critica_visual")
|
|
1013
|
+
ultimaCriticaDeHilo.set(deHilo, Date.now());
|
|
651
1014
|
// La traza: la MISMA lista blanca que el evento (`detalleDe`, `parametrosDe`), nunca
|
|
652
1015
|
// los argumentos crudos.
|
|
653
1016
|
const quien = quienEs.get(deHilo) ?? deHilo;
|
|
654
1017
|
diagnostico?.herramienta(t.function.name, detalleDe(t.function.name, args), parametrosDe(t.function.name, args), tracker, deHilo === HILO_RAIZ ? "orquestador" : "especialista", undefined, quien);
|
|
1018
|
+
// Una CAPTURA más de quien ejecuta: es lo que dispara el aviso de capturas (`textoDeCapturas`).
|
|
1019
|
+
if (t.function.name === "execute" && typeof args["command"] === "string" && COMANDO_DE_CAPTURA.test(args["command"])) {
|
|
1020
|
+
capturasDeCadaHilo.set(deHilo, (capturasDeCadaHilo.get(deHilo) ?? 0) + 1);
|
|
1021
|
+
}
|
|
655
1022
|
// Qué le pide el orquestador a quién: el encargo, acotado (ver `DiagnosticoDeTools.delegacion`).
|
|
656
1023
|
if (t.function.name === "create_sub_agent" && typeof args["name"] === "string" && typeof args["input"] === "string") {
|
|
657
1024
|
diagnostico?.delegacion?.(quien, args["name"], args["input"]);
|
|
1025
|
+
esperas.anunciar(args["name"]);
|
|
658
1026
|
}
|
|
659
1027
|
}
|
|
660
1028
|
}
|
|
@@ -664,8 +1032,52 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
664
1032
|
const llamada = llamadas.get(claveDe(deHilo, evento.tool_call_id));
|
|
665
1033
|
const chars = typeof evento.content === "string" ? evento.content.length : 0;
|
|
666
1034
|
diagnostico?.resultado?.(llamada?.nombre, llamada === undefined ? undefined : detalleDe(llamada.nombre, llamada.args), chars);
|
|
1035
|
+
// Lo que ESCRIBIÓ este hilo, para decírselo a quien lo llamó al terminar (`informesDeHijos.ts`).
|
|
1036
|
+
const escritura = escriturasEnVuelo.get(claveDe(deHilo, evento.tool_call_id));
|
|
1037
|
+
if (escritura !== undefined) {
|
|
1038
|
+
escriturasEnVuelo.delete(claveDe(deHilo, evento.tool_call_id));
|
|
1039
|
+
if (deHilo !== HILO_RAIZ && escrituraConExito(evento.content)) {
|
|
1040
|
+
const cambios = escritosDeCadaHilo.get(deHilo) ?? new Map();
|
|
1041
|
+
anotarEscritura(cambios, escritura.nombre, escritura.args);
|
|
1042
|
+
escritosDeCadaHilo.set(deHilo, cambios);
|
|
1043
|
+
ultimaEscrituraDeHilo.set(deHilo, Date.now());
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
667
1046
|
}
|
|
668
1047
|
// Un hilo que agotó su tope se ANOTA en la traza, con quién era: el raíz y cualquier hijo.
|
|
1048
|
+
if (evento.type === "internal.agent.done") {
|
|
1049
|
+
esperas.murio(deHilo);
|
|
1050
|
+
const hiloPadre = padreDeHilo.get(deHilo);
|
|
1051
|
+
// Lo que devolvió a quien lo llamó, a la traza (`DiagnosticoDeTools.devolucion`): la otra mitad del encargo.
|
|
1052
|
+
const devuelto = evento.send_to_parent?.content;
|
|
1053
|
+
if (hiloPadre !== undefined && devuelto !== undefined) {
|
|
1054
|
+
const texto = typeof devuelto === "string" ? devuelto : JSON.stringify(devuelto);
|
|
1055
|
+
diagnostico?.devolucion?.(quienEs.get(deHilo) ?? deHilo, quienEs.get(hiloPadre) ?? (hiloPadre === HILO_RAIZ ? "orquestador" : hiloPadre), texto);
|
|
1056
|
+
}
|
|
1057
|
+
if (hiloPadre !== undefined) {
|
|
1058
|
+
padreDeHilo.delete(deHilo);
|
|
1059
|
+
const nombre = especialistaDeHilo.get(deHilo);
|
|
1060
|
+
const quien = especialistas().find((a) => a.nombre === nombre);
|
|
1061
|
+
const medida = medidaTrasProbar(deHilo, hiloPadre, quien);
|
|
1062
|
+
const texto = medida ??
|
|
1063
|
+
textoDelInforme(quienEs.get(deHilo) ?? deHilo, escritosDeCadaHilo.get(deHilo) ?? new Map(), quien !== undefined && quien.motor === "modelo" && clasesDeTools(quien) === "escribe", ultimaMedidaDeHilo.get(deHilo));
|
|
1064
|
+
escritosDeCadaHilo.delete(deHilo);
|
|
1065
|
+
nacimientoDeHilo.delete(deHilo);
|
|
1066
|
+
if (texto !== undefined)
|
|
1067
|
+
informesPendientes.set(hiloPadre, [...(informesPendientes.get(hiloPadre) ?? []), texto]);
|
|
1068
|
+
}
|
|
1069
|
+
const h = hijosConMemoria.get(deHilo);
|
|
1070
|
+
if (h !== undefined) {
|
|
1071
|
+
hijosConMemoria.delete(deHilo);
|
|
1072
|
+
const foto = h.hilo.toSnapshot();
|
|
1073
|
+
const tokens = (foto.current_context_usage?.prompt_tokens ?? 0) + (foto.current_context_usage?.completion_tokens ?? 0);
|
|
1074
|
+
const destino = memoriaDeEspecialistas.cerrar(deHilo, {
|
|
1075
|
+
// sin que nadie se lo haya pedido, así que no deja memoria (como uno que falló).
|
|
1076
|
+
bien: evento.status !== "error" && !detencion.soloTexto(deHilo), mensajes: [...h.iniciales, ...foto.context], tokens
|
|
1077
|
+
});
|
|
1078
|
+
diagnostico?.memoria?.(h.nombre, "cierra", destino, tokens);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
669
1081
|
const tope = topeAgotadoDe(evento);
|
|
670
1082
|
if (tope !== undefined)
|
|
671
1083
|
diagnostico?.corte?.(quienEs.get(deHilo) ?? deHilo, tope);
|
|
@@ -745,6 +1157,7 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
745
1157
|
const memoriaDescartada = avisoDeMemoria;
|
|
746
1158
|
avisoDeMemoria = undefined;
|
|
747
1159
|
let cortadoPorTope = false;
|
|
1160
|
+
let preguntasSolas = 0;
|
|
748
1161
|
let sinResolver = 0;
|
|
749
1162
|
// El veredicto del turno, con las MISMAS reglas que deepagents (`verificacion.ts`).
|
|
750
1163
|
let veredicto = "no-corrio";
|
|
@@ -770,6 +1183,36 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
770
1183
|
const preguntas = [];
|
|
771
1184
|
yield* paso(lote, senal, pendientes, preguntas);
|
|
772
1185
|
if (pendientes.length === 0) {
|
|
1186
|
+
/**
|
|
1187
|
+
* **En modo autónomo no se le pregunta a nadie**, y se dice en el chat. Una pregunta con una opción
|
|
1188
|
+
* `(Recommended)` se contesta con ella; una sin recomendada vuelve al agente pidiéndole que decida él y diga
|
|
1189
|
+
* qué eligió. Era «todas o ninguna»: medido en MyAllXOne, el orquestador hizo DOS a la vez —una con
|
|
1190
|
+
* recomendada, la otra no— y no se contestó ninguna, se enseñó solo la primera y la segunda se perdió. Con
|
|
1191
|
+
* tope, para que no dé vueltas.
|
|
1192
|
+
*/
|
|
1193
|
+
if (preguntas.length > 0 && opciones.sinAprobacion?.() === true && preguntasSolas + preguntas.length <= TOPE_DE_PREGUNTAS_CONTESTADAS_SOLAS) {
|
|
1194
|
+
preguntasSolas += preguntas.length;
|
|
1195
|
+
const respuestas = [];
|
|
1196
|
+
for (const q of preguntas) {
|
|
1197
|
+
const { pregunta, opciones: posibles } = consultaDe(q.args);
|
|
1198
|
+
const elegida = opcionRecomendada(posibles);
|
|
1199
|
+
yield {
|
|
1200
|
+
tipo: "aviso",
|
|
1201
|
+
texto: elegida !== undefined
|
|
1202
|
+
? `modo autónomo: a «${pregunta}» contesto la recomendada: ${elegida}`
|
|
1203
|
+
: `modo autónomo: «${pregunta}» no se pregunta; lo decide el agente y lo dirá`,
|
|
1204
|
+
severidad: "aviso",
|
|
1205
|
+
};
|
|
1206
|
+
respuestas.push({
|
|
1207
|
+
type: "user.tool_response",
|
|
1208
|
+
thread_id: q.hilo,
|
|
1209
|
+
tool_call_id: q.id,
|
|
1210
|
+
content: elegida ?? RESPUESTA_AUTONOMA_SIN_RECOMENDADA,
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
lote = respuestas;
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
773
1216
|
// El orquestador PREGUNTA: el turno acaba aquí con la pregunta a la vista, y lo que la
|
|
774
1217
|
// persona escriba después vuelve como la respuesta de esa tool (`turno`, arriba).
|
|
775
1218
|
const pregunta = preguntas[0];
|
|
@@ -1163,6 +1606,21 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
1163
1606
|
},
|
|
1164
1607
|
nuevoHilo(id) {
|
|
1165
1608
|
hilo = id ?? `tf-${Date.now()}`;
|
|
1609
|
+
// Otra conversación: lo que los especialistas recordaban era de la anterior.
|
|
1610
|
+
memoriaDeEspecialistas.olvidar();
|
|
1611
|
+
esperas.olvidar();
|
|
1612
|
+
vueltasDeCadaHilo.clear();
|
|
1613
|
+
capturasDeCadaHilo.clear();
|
|
1614
|
+
escritosDeCadaHilo.clear();
|
|
1615
|
+
escriturasEnVuelo.clear();
|
|
1616
|
+
nacimientoDeHilo.clear();
|
|
1617
|
+
ultimaEscrituraDeHilo.clear();
|
|
1618
|
+
ultimaCriticaDeHilo.clear();
|
|
1619
|
+
ultimaMedidaDeHilo.clear();
|
|
1620
|
+
rondasVisualesDeHilo.clear();
|
|
1621
|
+
padreDeHilo.clear();
|
|
1622
|
+
informesPendientes.clear();
|
|
1623
|
+
hijosConMemoria.clear();
|
|
1166
1624
|
// Un hilo NUEVO: lo que hubiera guardado con ese id no se pisa ni se carga a medias. Y una
|
|
1167
1625
|
// pregunta de la conversación de antes no la contesta el primer mensaje de la nueva.
|
|
1168
1626
|
const foto = fotoDeLaSesion(hilo);
|