xonecode 0.17.2 → 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/apps/web/dist/assets/{index-BRVuyPBf.js → index-BvPUqjUm.js} +72 -72
- package/apps/web/dist/assets/{index-DLfF2uEK.css → index-CGEXMJBx.css} +1 -1
- package/apps/web/dist/assets/{php-B5LpMCn-.js → php-Dk28VW45.js} +1 -1
- package/apps/web/dist/assets/{ruby-Clm-qfwM.js → ruby-CPzEcmdp.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/config/iconosEnRed.js +47 -0
- package/dist/agent/config/iconosEnRed.js.map +1 -0
- package/dist/agent/config/modelos.js +13 -8
- package/dist/agent/config/modelos.js.map +1 -1
- package/dist/agent/dispositivos/decodificarImagen.js +62 -0
- package/dist/agent/dispositivos/decodificarImagen.js.map +1 -0
- package/dist/agent/grafo/atributosXone.js +58 -0
- package/dist/agent/grafo/atributosXone.js.map +1 -0
- package/dist/agent/grafo/buscarIcono.js +92 -0
- package/dist/agent/grafo/buscarIcono.js.map +1 -0
- package/dist/agent/grafo/compararCapturas.js +103 -0
- package/dist/agent/grafo/compararCapturas.js.map +1 -0
- package/dist/agent/grafo/criticaVisual.js +23 -3
- package/dist/agent/grafo/criticaVisual.js.map +1 -1
- package/dist/agent/grafo/diferenciaDeCapturas.js +88 -0
- package/dist/agent/grafo/diferenciaDeCapturas.js.map +1 -0
- package/dist/agent/grafo/generarFondoSvg.js +83 -0
- package/dist/agent/grafo/generarFondoSvg.js.map +1 -0
- package/dist/agent/grafo/lectorDeReferencias.js +19 -0
- package/dist/agent/grafo/lectorDeReferencias.js.map +1 -0
- package/dist/agent/grafo/xoneAgent.js +11 -3
- 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/eventosTrueforge.js +11 -1
- package/dist/agent/motores/trueforge/eventosTrueforge.js.map +1 -1
- 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/modeloLangchain.js +5 -0
- package/dist/agent/motores/trueforge/modeloLangchain.js.map +1 -1
- 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 +545 -26
- 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 +88 -19
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
- package/dist/agent/turno/diagnosticoDeTools.js +44 -3
- package/dist/agent/turno/diagnosticoDeTools.js.map +1 -1
- package/dist/agent/turno/informeDeTraza.js +66 -4
- package/dist/agent/turno/informeDeTraza.js.map +1 -1
- package/dist/agent/turno/registroDeFallos.js +6 -1
- package/dist/agent/turno/registroDeFallos.js.map +1 -1
- package/dist/agent/turno/resumenDeTool.js +12 -0
- package/dist/agent/turno/resumenDeTool.js.map +1 -1
- package/dist/agent/turno/turnoReal.js +8 -2
- package/dist/agent/turno/turnoReal.js.map +1 -1
- package/dist/cli/run.js +11 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/core/agentes.js +36 -2
- package/dist/core/agentes.js.map +1 -1
- package/dist/core/analisisDeSesion.js +198 -0
- package/dist/core/analisisDeSesion.js.map +1 -0
- package/dist/core/atributosXone.js +187 -0
- package/dist/core/atributosXone.js.map +1 -0
- package/dist/core/compararCapturas.js +405 -0
- package/dist/core/compararCapturas.js.map +1 -0
- package/dist/core/esfuerzo.js +33 -0
- package/dist/core/esfuerzo.js.map +1 -1
- package/dist/core/fallos.js.map +1 -1
- package/dist/core/fondosSvg.js +223 -0
- package/dist/core/fondosSvg.js.map +1 -0
- package/dist/core/iconos.js +140 -0
- package/dist/core/iconos.js.map +1 -0
- package/dist/core/modoDeEscritura.js +40 -0
- package/dist/core/modoDeEscritura.js.map +1 -1
- package/dist/core/notify.js +8 -0
- package/dist/core/notify.js.map +1 -1
- package/dist/core/nucleoXone.js +113 -0
- package/dist/core/nucleoXone.js.map +1 -0
- package/dist/core/paqueteDeSoporte.js +118 -0
- package/dist/core/paqueteDeSoporte.js.map +1 -0
- package/dist/core/ports.js +31 -0
- package/dist/core/ports.js.map +1 -1
- package/dist/core/referenciasDeImagen.js +55 -0
- package/dist/core/referenciasDeImagen.js.map +1 -0
- 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/dist/web/servidor/arranque.js +110 -0
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/sesiones.js +8 -0
- package/dist/web/servidor/sesiones.js.map +1 -1
- package/dist/web/servidor/soporte.js +571 -0
- package/dist/web/servidor/soporte.js.map +1 -0
- package/dist/web/servidor/transporte.js.map +1 -1
- package/package.json +4 -1
- package/skills/xone-development/references/css/propiedades-y-herencia.md +2 -0
- 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 +49 -0
- package/skills/xone-development/references/xml-ui/prop-tipos-basicos.md +2 -0
- package/skills/xone-hotswap/SKILL.md +4 -0
- package/skills/xone-hotswap/scripts/xone-log-android +18 -1
- package/skills/xone-plan-builder/SKILL.md +18 -10
- package/skills/xone-plan-builder/references/TASKS-FORMAT.md +13 -5
- package/skills/xone-project-generator/references/fases-10-12-readmes-y-validacion.md +1 -0
- 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,28 +63,213 @@ 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";
|
|
65
72
|
import { conHechosDelProyecto } from "../../../core/hechosDelProyecto.js";
|
|
66
73
|
import { crearBusquedaRegex } from "../../grafo/busquedaRegex.js";
|
|
74
|
+
import { crearBuscarIcono, recibeBuscarIcono } from "../../grafo/buscarIcono.js";
|
|
75
|
+
import { crearGenerarFondoSvg, recibeGenerarFondo } from "../../grafo/generarFondoSvg.js";
|
|
67
76
|
import { crearCopiarArtefacto } from "../../grafo/copiarArtefacto.js";
|
|
68
77
|
import { crearMarcarCriteriosDelPlan } from "../../grafo/marcarCriteriosDelPlan.js";
|
|
69
78
|
import { crearUnirSecciones } from "../../grafo/unirSecciones.js";
|
|
70
79
|
import { crearIncorporarAdjunto, recibeIncorporarAdjunto } from "../../grafo/incorporarAdjunto.js";
|
|
71
80
|
import { crearCriticaVisual } from "../../grafo/criticaVisual.js";
|
|
81
|
+
import { crearCompararCapturas } from "../../grafo/compararCapturas.js";
|
|
82
|
+
import { crearAtributosXone, NOMBRE_ATRIBUTOS_XONE } from "../../grafo/atributosXone.js";
|
|
83
|
+
import { crearDiferenciaDeCapturas } from "../../grafo/diferenciaDeCapturas.js";
|
|
84
|
+
import { crearLectorDeReferencias } from "../../grafo/lectorDeReferencias.js";
|
|
72
85
|
import { crearTraerDeLaMaquina } from "../../grafo/traerDeLaMaquina.js";
|
|
73
86
|
import { invocarVisualConModelos } from "../../dispositivos/juezVisual.js";
|
|
74
87
|
import { estilosDeDisco, indiceEnDisco } from "../../navegacion/indiceEnDisco.js";
|
|
75
88
|
/** El hilo raíz de TrueForge. Se llama así en la librería y no se elige. */
|
|
76
89
|
const HILO_RAIZ = "main";
|
|
90
|
+
/**
|
|
91
|
+
* **Ser fiel a un diseño es parte del encargo, y se comprueba con números Y con ojos.**
|
|
92
|
+
*
|
|
93
|
+
* Rehaciendo una calculadora de Stitch, el orquestador dio la pantalla por buena una y otra vez con
|
|
94
|
+
* el crítico en verde, porque corría SIN la maqueta: su verde significaba «nada roto» y no veía que
|
|
95
|
+
* faltaba la barra superior. Con la maqueta a mano (`/diseno/`, `/adjuntos/`), el bucle es este, y
|
|
96
|
+
* termina: una condición de parada de estructura y un tope de vueltas, porque sin tope un crítico
|
|
97
|
+
* y un agente pueden retocar para siempre.
|
|
98
|
+
*/
|
|
99
|
+
const BUCLE_DE_CALIDAD = [
|
|
100
|
+
"SI EL ENCARGO TRAE UN DISEÑO (una imagen en /diseno/, /adjuntos/ o /artefactos/), SER FIEL A ÉL es parte del",
|
|
101
|
+
"encargo, y se comprueba con números y con ojos. Tras cada cambio visual: pide al conductor una captura NATIVA",
|
|
102
|
+
"nueva y contrástala con `comparar_capturas` y con `xone_critica_visual`, pasando el diseño como `referencia` en las",
|
|
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",
|
|
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",
|
|
106
|
+
"di lo que sigue faltando en vez de darlo por hecho.",
|
|
107
|
+
].join("\n");
|
|
77
108
|
/**
|
|
78
109
|
* Lo que el orquestador tiene que saber para DELEGAR en ESTE motor. Su prompt es el de siempre
|
|
79
110
|
* (`promptOrquestador`), que habla de `task` y de la ficha que va en su descripción: aquí la
|
|
80
111
|
* delegación es `create_sub_agent` y no tiene descripción por especialista, así que se traduce
|
|
81
112
|
* el nombre de la tool y las fichas van escritas aquí, con la MISMA función que deepagents.
|
|
82
113
|
*/
|
|
83
|
-
|
|
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
|
+
}
|
|
272
|
+
export function notaDeDelegacion(agentes, opciones = {}) {
|
|
84
273
|
if (agentes.length === 0)
|
|
85
274
|
return "";
|
|
86
275
|
return [
|
|
@@ -88,7 +277,38 @@ export function notaDeDelegacion(agentes) {
|
|
|
88
277
|
"`name` es EXACTAMENTE el nombre del especialista y `input` el encargo, autosuficiente —el",
|
|
89
278
|
"especialista no ve esta conversación—. Donde estas instrucciones dicen `task` o `subagent_type`,",
|
|
90
279
|
"entiende `create_sub_agent` y `name`. Las fichas de los especialistas:",
|
|
91
|
-
...agentes.map((a) => `- ${a.nombre}: ${fichaDeAgente(a)}`),
|
|
280
|
+
...agentes.map((a) => `- ${a.nombre}: ${fichaDeAgente(a, opciones)}`),
|
|
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
|
+
: []),
|
|
92
312
|
].join("\n");
|
|
93
313
|
}
|
|
94
314
|
/** Lo que cada clase NO puede hacer, que la lista de tools no dice sola. */
|
|
@@ -101,6 +321,14 @@ const LIMITES_DE = {
|
|
|
101
321
|
* La frase de las tools que TIENE, sacada de los nombres que de verdad se le montan: escrita a
|
|
102
322
|
* mano se quedaba vieja en cuanto se añadía una —la nota decía cuatro y el modelo veía seis—.
|
|
103
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.";
|
|
104
332
|
export function notaDeTools(nombres, limite) {
|
|
105
333
|
return `NOTA DEL HARNESS: aunque tu identidad diga lo contrario, NO tienes las mismas tools que quien te delega. Tienes ${nombres
|
|
106
334
|
.map((n) => `\`${n}\``)
|
|
@@ -175,7 +403,9 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
175
403
|
* parámetro solo sirve para doblarla—, que es lo que evita dejarla escrita y sin montar.
|
|
176
404
|
*/
|
|
177
405
|
const entornoDeDiagnostico = entornoConDepuracion(opciones.depurar === true);
|
|
178
|
-
|
|
406
|
+
// El CHAT es la sesión con la que se abrió (`opciones.hilo`, el id del índice), no el `hilo` de
|
|
407
|
+
// ahora: `/nuevo` abre otro hilo huérfano pero los actos siguen yendo a la misma sesión.
|
|
408
|
+
const diagnostico = opciones.diagnostico ?? crearDiagnosticoDeTools(raiz, entornoDeDiagnostico, opciones.hilo);
|
|
179
409
|
/**
|
|
180
410
|
* La traza de EXCEPCIONES e HITOS, la MISMA de deepagents (`turnoReal.ts`) y que aquí
|
|
181
411
|
* faltaba por completo: TrueForge nunca la encendía, ni siquiera con la variable de entorno
|
|
@@ -201,6 +431,36 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
201
431
|
* viaja por el cable como nombre de un especialista tiene que serlo de verdad.
|
|
202
432
|
*/
|
|
203
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();
|
|
204
464
|
/** Lo que el agente dejó en `/artefactos/` y aún no se ha anunciado: un artefacto se escribe
|
|
205
465
|
* SIN aprobación, así que tiene que ANUNCIARSE, como en deepagents. */
|
|
206
466
|
const artefactosPorAnunciar = [];
|
|
@@ -302,21 +562,36 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
302
562
|
const cargarEstilos = estilosDeDisco(raiz);
|
|
303
563
|
const navegacion = () => crearNavegacionXone(cargarIndice, ficheros, cargarEstilos);
|
|
304
564
|
const carpeta = opciones.artefactos;
|
|
565
|
+
const lectorDeReferencias = crearLectorDeReferencias({
|
|
566
|
+
raiz,
|
|
567
|
+
...(carpeta === undefined ? {} : { artefactos: carpeta }),
|
|
568
|
+
...(opciones.adjuntos === undefined ? {} : { adjuntos: opciones.adjuntos }),
|
|
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();
|
|
305
585
|
const propiasDelRaiz = [
|
|
306
586
|
navegacion(),
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
crearCriticaVisual({
|
|
311
|
-
leerArtefacto: async (nombre) => readFileSync(join(carpeta, nombre)),
|
|
312
|
-
invocar: invocarVisualConModelos({ paraPapel: (p) => modelos.paraPapel(p) }),
|
|
313
|
-
}),
|
|
314
|
-
crearTraerDeLaMaquina({ carpeta, alEscribir: anotarArtefacto }),
|
|
315
|
-
]),
|
|
587
|
+
atributos,
|
|
588
|
+
...herramientasDeJuicio(),
|
|
589
|
+
...(carpeta === undefined ? [] : [crearTraerDeLaMaquina({ carpeta, alEscribir: anotarArtefacto })]),
|
|
316
590
|
];
|
|
317
591
|
const propiasDe = (agente) => [
|
|
318
592
|
crearBusquedaRegex(backend),
|
|
319
593
|
navegacion(),
|
|
594
|
+
atributos,
|
|
320
595
|
...(carpeta !== undefined && (agente.escribeEn ?? []).length > 0
|
|
321
596
|
? [crearCopiarArtefacto({ raiz, carpetaDeArtefactos: carpeta, perfil: agente })]
|
|
322
597
|
: []),
|
|
@@ -328,8 +603,24 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
328
603
|
...(opciones.adjuntos !== undefined && recibeIncorporarAdjunto(agente)
|
|
329
604
|
? [crearIncorporarAdjunto({ raiz, carpetaDeAdjuntos: opciones.adjuntos, perfil: agente })]
|
|
330
605
|
: []),
|
|
606
|
+
// Buscar un icono cuando faltan los assets (IXCODE-18), con el reparto de deepagents: a quien
|
|
607
|
+
// escribe el proyecto y solo con el puerto. Es de LECTURA: no entra en `requireApprovalForTools`.
|
|
608
|
+
...(opciones.iconos !== undefined && recibeBuscarIcono(agente)
|
|
609
|
+
? [crearBuscarIcono(opciones.iconos)]
|
|
610
|
+
: []),
|
|
611
|
+
// Fondos SVG: pura y sin red, sin puerto; a quien escribe el proyecto, como deepagents.
|
|
612
|
+
...(recibeGenerarFondo(agente) ? [crearGenerarFondoSvg()] : []),
|
|
613
|
+
// ¿Cambió la zona tras el toque? Al conductor, que es quien saca las capturas: comprueba una
|
|
614
|
+
// ACCIÓN suya, no juzga la pantalla (eso sigue siendo del crítico y de `comparar_capturas`).
|
|
615
|
+
...(carpeta !== undefined && agente.ejecucion === true
|
|
616
|
+
? [crearDiferenciaDeCapturas({ leerArtefacto: async (nombre) => readFileSync(join(carpeta, nombre)) })]
|
|
617
|
+
: []),
|
|
331
618
|
// Y marcar en el plan lo comprobado en el aparato, con el reparto de deepagents: a quien ejecuta.
|
|
332
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() : []),
|
|
333
624
|
];
|
|
334
625
|
/**
|
|
335
626
|
* **Un cliente de modelo por papel, modelo y esfuerzo, que dura la SESIÓN** —hasta `/modelo`—, y
|
|
@@ -353,7 +644,13 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
353
644
|
}
|
|
354
645
|
return cliente;
|
|
355
646
|
};
|
|
356
|
-
|
|
647
|
+
// Lo que la librería pierde: el razonamiento de cada llamada, por tokens de salida, hasta que su evento llega.
|
|
648
|
+
const razonamientoPendiente = new Map();
|
|
649
|
+
const alRazonar = (salida, razonamiento) => {
|
|
650
|
+
razonamientoPendiente.set(salida, [...(razonamientoPendiente.get(salida) ?? []), razonamiento]);
|
|
651
|
+
};
|
|
652
|
+
const razonamientoDeSalida = (salida) => razonamientoPendiente.get(salida)?.shift();
|
|
653
|
+
const llm = modeloParaTrueforge({ modelo: () => clienteDe("papel:trabajo:", () => modelos.paraPapel("trabajo")), senal: () => aborto?.signal, alRazonar });
|
|
357
654
|
/**
|
|
358
655
|
* El hilo de un hijo EXTERNO: un `AgentThread` normal con UNA llamada, cuyo «modelo» es el
|
|
359
656
|
* producto (`modeloExterno.ts`). Sin tools, sin capabilities y sin compactación: el bucle vive
|
|
@@ -400,8 +697,37 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
400
697
|
* LECTURA y sin shell: un nombre inventado no tumba el turno ni se lleva la escritura.
|
|
401
698
|
*/
|
|
402
699
|
const crearHijo = async (params) => {
|
|
403
|
-
|
|
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
|
+
}
|
|
404
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());
|
|
405
731
|
quienEs.set(params.threadId, agente?.nombre ?? params.request.name);
|
|
406
732
|
if (agente !== undefined)
|
|
407
733
|
especialistaDeHilo.set(params.threadId, agente.nombre);
|
|
@@ -413,6 +739,13 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
413
739
|
backend: backend,
|
|
414
740
|
propias: propiasDe,
|
|
415
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,
|
|
416
749
|
conShell: () => montarBackend({
|
|
417
750
|
entorno: entornoDeLaShellDelProyecto(raiz, opciones.artefactos),
|
|
418
751
|
// El MISMO patrón que ya usa `modeloParaTrueforge` un poco más arriba en este
|
|
@@ -422,20 +755,56 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
422
755
|
}),
|
|
423
756
|
});
|
|
424
757
|
const nombres = toolsDe(piezas);
|
|
425
|
-
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");
|
|
426
762
|
const instrucciones = agente === undefined
|
|
427
|
-
? `${nota} Contesta con lo que encuentres y dónde.`
|
|
428
|
-
: [
|
|
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
|
+
]
|
|
429
773
|
.filter((l) => l !== undefined)
|
|
430
774
|
.join("\n")
|
|
431
775
|
.trimEnd();
|
|
432
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
|
+
];
|
|
433
801
|
const definicionDelHijo = {
|
|
434
802
|
modelClient: modeloParaTrueforge({
|
|
435
803
|
modelo: () => agente?.modelo === undefined
|
|
436
|
-
? clienteDe(`papel:${papel}:${agente?.esfuerzo ?? ""}`, () => modelos.paraPapel(papel, agente?.esfuerzo))
|
|
437
|
-
: 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)),
|
|
438
806
|
senal: () => aborto?.signal,
|
|
807
|
+
alRazonar,
|
|
439
808
|
soloTexto: () => (detencion.soloTexto(params.threadId) ? RESUMEN_DE_RELLENO : undefined),
|
|
440
809
|
corte: () => detencion.corte(params.threadId),
|
|
441
810
|
alTirarLlamadas: (n) => {
|
|
@@ -444,11 +813,11 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
444
813
|
anotarPaso("trueforge.detener", `${quienEs.get(params.threadId) ?? params.threadId}: ${n} tool call(s) tiradas`)();
|
|
445
814
|
},
|
|
446
815
|
}),
|
|
447
|
-
messages:
|
|
816
|
+
messages: mensajesIniciales,
|
|
448
817
|
// Los topes MEDIDOS de deepagents: el conductor más, porque cada paso suyo es un comando.
|
|
449
818
|
iterationLimit: agente?.ejecucion === true ? TOPE_DE_LLAMADAS_DEL_CONDUCTOR : TOPE_DE_LLAMADAS_DEL_ESPECIALISTA,
|
|
450
819
|
};
|
|
451
|
-
|
|
820
|
+
const hijo = new AgentThread({
|
|
452
821
|
definition: definicionDelHijo,
|
|
453
822
|
threadId: params.threadId,
|
|
454
823
|
title: params.request.name,
|
|
@@ -473,6 +842,9 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
473
842
|
tracing: NOOP_AGENT_TRACING,
|
|
474
843
|
logger,
|
|
475
844
|
});
|
|
845
|
+
if (agente !== undefined && apertura !== undefined)
|
|
846
|
+
hijosConMemoria.set(params.threadId, { nombre: agente.nombre, hilo: hijo, iniciales: mensajesIniciales });
|
|
847
|
+
return hijo;
|
|
476
848
|
};
|
|
477
849
|
/**
|
|
478
850
|
* El árbol de hilos de UNA conversación. Se rehace con `nuevoHilo`, al reabrir una sesión
|
|
@@ -484,9 +856,13 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
484
856
|
/** La pregunta del orquestador que espera respuesta: el siguiente mensaje la contesta. */
|
|
485
857
|
let preguntaEnEspera;
|
|
486
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();
|
|
487
863
|
const definicion = {
|
|
488
864
|
modelClient: llm,
|
|
489
|
-
instruction: [promptOrquestador(especialistas()), notaDeDelegacion(especialistas())].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"),
|
|
490
866
|
// Por TURNO, porque el raíz se rehace desde su foto al final de cada uno (ver `turno`).
|
|
491
867
|
iterationLimit: LIMITE_DE_LLAMADAS_DEL_RAIZ,
|
|
492
868
|
};
|
|
@@ -503,6 +879,7 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
503
879
|
capacidadDeRecortes(backend),
|
|
504
880
|
capacidadDeFecha(),
|
|
505
881
|
capacidadDeNotasDeLaSesion,
|
|
882
|
+
capacidadDeInformes,
|
|
506
883
|
]),
|
|
507
884
|
/**
|
|
508
885
|
* **La conversación se RESUME al mismo umbral que deepagents** (`UMBRAL_RESUMEN_TOKENS`):
|
|
@@ -511,7 +888,10 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
511
888
|
* instrucciones van en el prompt de sistema —aquí `instruction`, en un hijo su
|
|
512
889
|
* capability—, que no se compacta.
|
|
513
890
|
*/
|
|
514
|
-
contextCompaction({
|
|
891
|
+
contextCompaction({
|
|
892
|
+
definition: { ...definicion, modelClient: conResumenSeguro(definicion.modelClient, (t) => anotarPaso("trueforge.compactacion", t)()) },
|
|
893
|
+
compactionThresholdTokens: UMBRAL_RESUMEN_TOKENS,
|
|
894
|
+
}),
|
|
515
895
|
// `ask_user_question`, SOLO en el raíz —la librería tampoco se la da a un hijo—: es el
|
|
516
896
|
// único que tiene a una persona delante.
|
|
517
897
|
askUserQuestion(),
|
|
@@ -572,6 +952,39 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
572
952
|
* `thread_id`: mandarla al raíz no la contestaría —o la rechazaría por hilo desconocido—.
|
|
573
953
|
* Por eso las tool calls se apuntan por hilo Y por id: dos hijos pueden repetir id.
|
|
574
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
|
+
}
|
|
575
988
|
async function* paso(lote, senal, pendientes, preguntas = []) {
|
|
576
989
|
for await (const _ of orquestador.send(lote))
|
|
577
990
|
void _;
|
|
@@ -594,9 +1007,23 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
594
1007
|
args = {};
|
|
595
1008
|
}
|
|
596
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());
|
|
597
1014
|
// La traza: la MISMA lista blanca que el evento (`detalleDe`, `parametrosDe`), nunca
|
|
598
1015
|
// los argumentos crudos.
|
|
599
|
-
|
|
1016
|
+
const quien = quienEs.get(deHilo) ?? deHilo;
|
|
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
|
+
}
|
|
1022
|
+
// Qué le pide el orquestador a quién: el encargo, acotado (ver `DiagnosticoDeTools.delegacion`).
|
|
1023
|
+
if (t.function.name === "create_sub_agent" && typeof args["name"] === "string" && typeof args["input"] === "string") {
|
|
1024
|
+
diagnostico?.delegacion?.(quien, args["name"], args["input"]);
|
|
1025
|
+
esperas.anunciar(args["name"]);
|
|
1026
|
+
}
|
|
600
1027
|
}
|
|
601
1028
|
}
|
|
602
1029
|
}
|
|
@@ -605,8 +1032,52 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
605
1032
|
const llamada = llamadas.get(claveDe(deHilo, evento.tool_call_id));
|
|
606
1033
|
const chars = typeof evento.content === "string" ? evento.content.length : 0;
|
|
607
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
|
+
}
|
|
608
1046
|
}
|
|
609
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
|
+
}
|
|
610
1081
|
const tope = topeAgotadoDe(evento);
|
|
611
1082
|
if (tope !== undefined)
|
|
612
1083
|
diagnostico?.corte?.(quienEs.get(deHilo) ?? deHilo, tope);
|
|
@@ -625,12 +1096,14 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
625
1096
|
if (deHilo === HILO_RAIZ && evento.type === "internal.agent.context.append")
|
|
626
1097
|
tracker.contexto = uso.input;
|
|
627
1098
|
// Por ORIGEN, como deepagents: el orquestador y cada especialista por su nombre.
|
|
1099
|
+
const razonado = uso.razonamiento ?? razonamientoDeSalida(uso.output);
|
|
628
1100
|
diagnostico?.modelo(quienEs.get(deHilo) ?? deHilo, {
|
|
629
1101
|
input: uso.input,
|
|
630
1102
|
output: uso.output,
|
|
631
1103
|
cache: uso.cache,
|
|
632
1104
|
llamadas: tracker.calls,
|
|
633
1105
|
contexto: uso.input,
|
|
1106
|
+
...(razonado === undefined ? {} : { razonamiento: razonado }),
|
|
634
1107
|
});
|
|
635
1108
|
avisar();
|
|
636
1109
|
}
|
|
@@ -684,6 +1157,7 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
684
1157
|
const memoriaDescartada = avisoDeMemoria;
|
|
685
1158
|
avisoDeMemoria = undefined;
|
|
686
1159
|
let cortadoPorTope = false;
|
|
1160
|
+
let preguntasSolas = 0;
|
|
687
1161
|
let sinResolver = 0;
|
|
688
1162
|
// El veredicto del turno, con las MISMAS reglas que deepagents (`verificacion.ts`).
|
|
689
1163
|
let veredicto = "no-corrio";
|
|
@@ -709,6 +1183,36 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
709
1183
|
const preguntas = [];
|
|
710
1184
|
yield* paso(lote, senal, pendientes, preguntas);
|
|
711
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
|
+
}
|
|
712
1216
|
// El orquestador PREGUNTA: el turno acaba aquí con la pregunta a la vista, y lo que la
|
|
713
1217
|
// persona escriba después vuelve como la respuesta de esa tool (`turno`, arriba).
|
|
714
1218
|
const pregunta = preguntas[0];
|
|
@@ -1102,6 +1606,21 @@ export async function abrirSesionTrueforge(opciones) {
|
|
|
1102
1606
|
},
|
|
1103
1607
|
nuevoHilo(id) {
|
|
1104
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();
|
|
1105
1624
|
// Un hilo NUEVO: lo que hubiera guardado con ese id no se pisa ni se carga a medias. Y una
|
|
1106
1625
|
// pregunta de la conversación de antes no la contesta el primer mensaje de la nueva.
|
|
1107
1626
|
const foto = fotoDeLaSesion(hilo);
|