xonecode 0.6.0 → 0.8.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-rqecK6s2.css → index-Boak51UP.css} +1 -1
- package/apps/web/dist/assets/index-Cg28x6nG.js +477 -0
- package/apps/web/dist/assets/{php-BBij3Lhc.js → php-Cu6F_QCJ.js} +1 -1
- package/apps/web/dist/assets/{ruby-Dq2YEaLo.js → ruby-BiqQPoC6.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/cloudstudio/cloudstudioClient.js +10 -0
- package/dist/agent/cloudstudio/cloudstudioClient.js.map +1 -1
- package/dist/agent/cloudstudio/cloudstudioMcp.js +84 -8
- package/dist/agent/cloudstudio/cloudstudioMcp.js.map +1 -1
- package/dist/agent/cloudstudio/descarga.js +14 -4
- package/dist/agent/cloudstudio/descarga.js.map +1 -1
- package/dist/agent/cloudstudio/ramaActiva.js +11 -0
- package/dist/agent/cloudstudio/ramaActiva.js.map +1 -0
- package/dist/agent/cloudstudio/subida.js +13 -4
- package/dist/agent/cloudstudio/subida.js.map +1 -1
- package/dist/agent/config/catalogoModelos.js +45 -0
- package/dist/agent/config/catalogoModelos.js.map +1 -1
- package/dist/agent/config/modelos.js +103 -12
- package/dist/agent/config/modelos.js.map +1 -1
- package/dist/agent/config/mudanzaEnDisco.js +121 -0
- package/dist/agent/config/mudanzaEnDisco.js.map +1 -0
- package/dist/agent/config/selectorEnMaquina.js +45 -0
- package/dist/agent/config/selectorEnMaquina.js.map +1 -0
- package/dist/agent/config/settingsEnDisco.js +35 -0
- package/dist/agent/config/settingsEnDisco.js.map +1 -1
- package/dist/agent/dispositivos/juezVisual.js +152 -0
- package/dist/agent/dispositivos/juezVisual.js.map +1 -0
- package/dist/agent/dispositivos/sensorDeArranque.js +154 -0
- package/dist/agent/dispositivos/sensorDeArranque.js.map +1 -0
- package/dist/agent/exportarPdf.js +140 -0
- package/dist/agent/exportarPdf.js.map +1 -0
- package/dist/agent/grafo/criticaVisual.js +115 -0
- package/dist/agent/grafo/criticaVisual.js.map +1 -0
- package/dist/agent/grafo/navegacionXone.js +37 -4
- package/dist/agent/grafo/navegacionXone.js.map +1 -1
- package/dist/agent/grafo/perfiles.js +52 -9
- package/dist/agent/grafo/perfiles.js.map +1 -1
- package/dist/agent/grafo/proyecto.js +74 -7
- package/dist/agent/grafo/proyecto.js.map +1 -1
- package/dist/agent/grafo/skills.js +32 -0
- package/dist/agent/grafo/skills.js.map +1 -1
- package/dist/agent/grafo/xoneAgent.js +103 -13
- package/dist/agent/grafo/xoneAgent.js.map +1 -1
- package/dist/agent/navegacion/estilosEnDisco.js +110 -0
- package/dist/agent/navegacion/estilosEnDisco.js.map +1 -0
- package/dist/agent/navegacion/hechosEnDisco.js +26 -0
- package/dist/agent/navegacion/hechosEnDisco.js.map +1 -0
- package/dist/agent/navegacion/indiceEnDisco.js +15 -0
- package/dist/agent/navegacion/indiceEnDisco.js.map +1 -1
- package/dist/agent/subagentes/agentesEnDisco.js +85 -24
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
- package/dist/agent/subagentes/escrituraExterna.js +53 -0
- package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
- package/dist/agent/subagentes/pluginDeSkills.js +99 -0
- package/dist/agent/subagentes/pluginDeSkills.js.map +1 -0
- package/dist/agent/subagentes/subagenteExterno.js +48 -0
- package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
- package/dist/agent/subagentes/subagenteOpencode.js +53 -1
- package/dist/agent/subagentes/subagenteOpencode.js.map +1 -1
- package/dist/agent/tareas/juezDeTarea.js +1 -1
- package/dist/agent/tareas/juezDeTarea.js.map +1 -1
- package/dist/agent/tareas/tareasEnDisco.js +49 -0
- package/dist/agent/tareas/tareasEnDisco.js.map +1 -1
- package/dist/agent/turno/diagnosticoDeTools.js +5 -1
- package/dist/agent/turno/diagnosticoDeTools.js.map +1 -1
- package/dist/agent/turno/informeDeTraza.js +27 -4
- package/dist/agent/turno/informeDeTraza.js.map +1 -1
- package/dist/agent/turno/puente.js +24 -1
- package/dist/agent/turno/puente.js.map +1 -1
- package/dist/agent/turno/resumenDeContexto.js +88 -4
- package/dist/agent/turno/resumenDeContexto.js.map +1 -1
- package/dist/agent/turno/resumenDeTool.js +18 -0
- package/dist/agent/turno/resumenDeTool.js.map +1 -1
- package/dist/agent/turno/rubrica.js +91 -0
- package/dist/agent/turno/rubrica.js.map +1 -0
- package/dist/agent/turno/turnoReal.js +172 -11
- package/dist/agent/turno/turnoReal.js.map +1 -1
- package/dist/cli/consola.js +63 -0
- package/dist/cli/consola.js.map +1 -1
- package/dist/cli/main.js +51 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/run.js +8 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/core/agentes.js +111 -9
- package/dist/core/agentes.js.map +1 -1
- package/dist/core/esfuerzo.js +141 -0
- package/dist/core/esfuerzo.js.map +1 -0
- package/dist/core/estilos.js +78 -0
- package/dist/core/estilos.js.map +1 -0
- package/dist/core/exportacion.js +140 -0
- package/dist/core/exportacion.js.map +1 -0
- package/dist/core/hechosDelProyecto.js +87 -0
- package/dist/core/hechosDelProyecto.js.map +1 -0
- package/dist/core/logDeDispositivo.js +143 -0
- package/dist/core/logDeDispositivo.js.map +1 -0
- package/dist/core/modelos.js +32 -0
- package/dist/core/modelos.js.map +1 -1
- package/dist/core/mudanzaDeWorkspace.js +93 -0
- package/dist/core/mudanzaDeWorkspace.js.map +1 -0
- package/dist/core/planes.js +70 -0
- package/dist/core/planes.js.map +1 -0
- package/dist/core/ports.js +4 -0
- package/dist/core/ports.js.map +1 -1
- package/dist/core/rubrica.js +90 -0
- package/dist/core/rubrica.js.map +1 -0
- package/dist/core/selectorDeCarpeta.js +87 -0
- package/dist/core/selectorDeCarpeta.js.map +1 -0
- package/dist/core/settings.js +54 -5
- package/dist/core/settings.js.map +1 -1
- package/dist/core/shellDeAgente.js +45 -0
- package/dist/core/shellDeAgente.js.map +1 -1
- package/dist/core/topeAgotado.js +69 -0
- package/dist/core/topeAgotado.js.map +1 -0
- package/dist/web/servidor/arranque.js +324 -9
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/sesiones.js +30 -2
- package/dist/web/servidor/sesiones.js.map +1 -1
- package/dist/web/servidor/transporte.js.map +1 -1
- package/dist/web/servidor/vestibulo.js +55 -6
- package/dist/web/servidor/vestibulo.js.map +1 -1
- package/package.json +2 -1
- package/skills/xone-hotswap/SKILL.md +31 -9
- package/skills/xone-hotswap/scripts/xone-arrancar-android +202 -0
- package/skills/xone-hotswap/scripts/xone-captura-android +93 -0
- package/skills/xone-hotswap/scripts/xone-desplegar-android +28 -0
- package/skills/xone-hotswap/scripts/xone-hotswap +39 -9
- package/skills/xone-hotswap/scripts/xone-reiniciar-android +28 -0
- package/skills/xone-plan-builder/SKILL.md +22 -4
- package/skills/xone-spec-builder/SKILL.md +37 -7
- package/apps/web/dist/assets/index-DBZTodio.js +0 -477
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El APARATO como sensor del bucle de reparación: ¿qué dice la app al arrancar?
|
|
3
|
+
*
|
|
4
|
+
* Es la tercera fuente de hallazgos, y contesta una pregunta que las otras dos no pueden.
|
|
5
|
+
* `xone-simulator validate` mira la estructura y `smoke` ejecuta el ciclo de vida en un
|
|
6
|
+
* runtime de Node; ninguno de los dos sabe si la app ARRANCA en un Android de verdad, con su
|
|
7
|
+
* SQLite, sus recursos y su Rhino. Medido sobre AppDemo: el simulador daba verde y el aparato
|
|
8
|
+
* decía `Invalid resource ID 0x6000000N` cinco veces distintas.
|
|
9
|
+
*
|
|
10
|
+
* CUATRO REGLAS, y las cuatro salieron de medir:
|
|
11
|
+
*
|
|
12
|
+
* - **Despliega SIEMPRE antes de leer.** Sin eso el log sería de la versión anterior del
|
|
13
|
+
* proyecto, y devolverle al agente errores que ya arregló es la peor clase de feedback:
|
|
14
|
+
* plausible y falso. El despliegue en caliente costó 5 s sobre un `pixel8`.
|
|
15
|
+
* - **El buffer se vacía ANTES de reiniciar.** Es lo que ya hace `xone-desplegar-android` por
|
|
16
|
+
* su cuenta y por el mismo motivo: si no, lo que se lee es la excepción del intento
|
|
17
|
+
* anterior, que es la forma más fácil de creer que algo sigue roto cuando ya no lo está.
|
|
18
|
+
* - **Sin aparato es fallo del ENTORNO, no un rojo.** La misma familia que
|
|
19
|
+
* `ErrorDelSimulador`: un informe rojo diría «tu proyecto está mal» cuando lo que pasa es
|
|
20
|
+
* que no hay móvil enchufado. Y **no se arranca uno solo**: levantar un emulador cuesta
|
|
21
|
+
* minutos de la máquina de alguien, y esa decisión no la toma un sensor — para eso está
|
|
22
|
+
* `xone-arrancar-android`, que el agente puede llamar.
|
|
23
|
+
* - **Solo ve la pantalla de ENTRADA, y se declara.** El despliegue reinicia la app, así que
|
|
24
|
+
* lo que se lee es lo que pasa al arrancar. Para tener hallazgos de una colección concreta
|
|
25
|
+
* hay que NAVEGAR hasta ella, y eso es trabajo del `device-controller` dentro del turno, no
|
|
26
|
+
* de un sensor automático.
|
|
27
|
+
*
|
|
28
|
+
* Todo entra por dependencias: `npm test` no puede necesitar un aparato.
|
|
29
|
+
*/
|
|
30
|
+
import { execFile } from "node:child_process";
|
|
31
|
+
import { existsSync } from "node:fs";
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
import { hallazgosDeLog } from "../../core/logDeDispositivo.js";
|
|
34
|
+
import { RAIZ_SKILLS } from "../grafo/skills.js";
|
|
35
|
+
import { localizadorDeAndroid, TOPES_MS } from "./dispositivosEnMaquina.js";
|
|
36
|
+
import { parsearAdbDevices } from "../../core/dispositivos.js";
|
|
37
|
+
/** No se pudo PREGUNTARLE al aparato. Entorno, no proyecto. */
|
|
38
|
+
export class ErrorDelAparato extends Error {
|
|
39
|
+
constructor(motivo) {
|
|
40
|
+
super(motivo);
|
|
41
|
+
this.name = "ErrorDelAparato";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Despliega y devuelve lo que el aparato dijo, en la forma que el bucle ya consume.
|
|
46
|
+
*
|
|
47
|
+
* **Verde es que no hay hallazgos DE LA APP.** Lo de la plataforma viaja en el informe —no se
|
|
48
|
+
* tira nunca, ver `core/logDeDispositivo.ts`— pero como `warning`, así que no pinta el turno
|
|
49
|
+
* de rojo: son quejas de Android sobre sí mismo y nadie las puede arreglar desde un `.xne`.
|
|
50
|
+
*/
|
|
51
|
+
export async function arranqueEnDispositivo(raiz, deps) {
|
|
52
|
+
if (!(await deps.hayAparato())) {
|
|
53
|
+
throw new ErrorDelAparato("no hay ningún aparato conectado, así que no se ha podido probar el arranque");
|
|
54
|
+
}
|
|
55
|
+
await deps.limpiarLog();
|
|
56
|
+
const despliegue = await deps.desplegar(raiz);
|
|
57
|
+
if (!despliegue.ok) {
|
|
58
|
+
/**
|
|
59
|
+
* Que el despliegue falle NO es un proyecto roto: puede ser el túnel, el canal o que el
|
|
60
|
+
* host no esté instalado. Se lanza, y quien llama lo dice como aviso — igual que el
|
|
61
|
+
* binario del simulador que falta.
|
|
62
|
+
*/
|
|
63
|
+
throw new ErrorDelAparato(`no se pudo desplegar en el aparato: ${despliegue.detalle}`);
|
|
64
|
+
}
|
|
65
|
+
const { aHallazgosDelTurno } = hallazgosDeLog(await deps.leerLog());
|
|
66
|
+
const hallazgos = aHallazgosDelTurno();
|
|
67
|
+
return { verde: !hallazgos.some((h) => h.severidad === "error"), hallazgos };
|
|
68
|
+
}
|
|
69
|
+
/** El host de XOne en Android. Vive aquí y en los scripts de la skill, que son otro proceso. */
|
|
70
|
+
const PAQUETE = "com.xone.android.framework";
|
|
71
|
+
/**
|
|
72
|
+
* Cuánto se le da al despliegue. El script hace la cadena entera —túnel, ZIP, subida,
|
|
73
|
+
* reinicio, lanzamiento y comprobación de que la app está VIVA— y sobre un `pixel8` en
|
|
74
|
+
* caliente tardó 5 s con un proyecto de 6,8 MB. El tope es para el caso frío con un proyecto
|
|
75
|
+
* grande, y por debajo del tope de reloj de un comando del harness.
|
|
76
|
+
*/
|
|
77
|
+
export const TOPE_DE_DESPLIEGUE_MS = 300_000;
|
|
78
|
+
/**
|
|
79
|
+
* El sensor de verdad: `adb` para el log y el script de la skill para el despliegue.
|
|
80
|
+
*
|
|
81
|
+
* **El despliegue NO se reimplementa aquí.** `xone-desplegar-android` ya es la cadena medida
|
|
82
|
+
* —y la única que sabe que hay que reiniciar con `SetupActivity` y no con `.mainEntry`, y que
|
|
83
|
+
* el ZIP va sin `.xonecode`—. Reescribirla sería un segundo sitio donde esa secuencia puede
|
|
84
|
+
* divergir, que es como este repo se ha roto siempre.
|
|
85
|
+
*/
|
|
86
|
+
export function sensorDeArranqueReal() {
|
|
87
|
+
const { enSdk } = localizadorDeAndroid({
|
|
88
|
+
plataforma: process.platform,
|
|
89
|
+
entorno: process.env,
|
|
90
|
+
home: process.env["HOME"] ?? "",
|
|
91
|
+
existe: (ruta) => existsSync(ruta),
|
|
92
|
+
});
|
|
93
|
+
const adb = enSdk("adb", "platform-tools");
|
|
94
|
+
const correr = (binario, args, ms) => new Promise((resolver, rechazar) => {
|
|
95
|
+
execFile(binario, args, { timeout: ms, maxBuffer: 64 * 1024 * 1024 }, (error, stdout) => {
|
|
96
|
+
if (error !== null)
|
|
97
|
+
rechazar(error);
|
|
98
|
+
else
|
|
99
|
+
resolver(stdout);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
const exigirAdb = () => {
|
|
103
|
+
if (adb === undefined)
|
|
104
|
+
throw new ErrorDelAparato("no encuentro «adb»: ni en el PATH ni en el SDK");
|
|
105
|
+
return adb;
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
hayAparato: async () => {
|
|
109
|
+
try {
|
|
110
|
+
return parsearAdbDevices(await correr(exigirAdb(), ["devices"], TOPES_MS.adb)).some((d) => d.estado === "conectado");
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// No poder preguntar no es «no hay»: se dice arriba, con su motivo.
|
|
114
|
+
throw new ErrorDelAparato("no se pudo preguntar a adb qué aparatos hay");
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
limpiarLog: async () => {
|
|
118
|
+
await correr(exigirAdb(), ["logcat", "-c"], TOPES_MS.adb);
|
|
119
|
+
},
|
|
120
|
+
desplegar: async (raiz) => {
|
|
121
|
+
const script = join(RAIZ_SKILLS, "xone-hotswap", "scripts", "xone-desplegar-android");
|
|
122
|
+
if (!existsSync(script))
|
|
123
|
+
return { ok: false, detalle: "falta xone-desplegar-android en el paquete" };
|
|
124
|
+
try {
|
|
125
|
+
// El `cwd` es la raíz del proyecto: el script empaqueta lo que hay ahí.
|
|
126
|
+
await new Promise((resolver, rechazar) => {
|
|
127
|
+
execFile(process.execPath, [script], { cwd: raiz, timeout: TOPE_DE_DESPLIEGUE_MS, maxBuffer: 16 * 1024 * 1024 }, (error) => (error === null ? resolver() : rechazar(error)));
|
|
128
|
+
});
|
|
129
|
+
return { ok: true, detalle: "" };
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// Una línea, no el volcado: esto va a un aviso del turno.
|
|
133
|
+
const texto = error instanceof Error ? error.message : String(error);
|
|
134
|
+
return { ok: false, detalle: texto.split("\n")[0] ?? "falló el despliegue" };
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
leerLog: async () => {
|
|
138
|
+
const pid = (await correr(exigirAdb(), ["shell", "pidof", PAQUETE], TOPES_MS.adb))
|
|
139
|
+
.trim()
|
|
140
|
+
.split(/\s+/)[0];
|
|
141
|
+
if (pid === undefined || pid === "") {
|
|
142
|
+
/**
|
|
143
|
+
* La app NO está viva tras desplegar. Es un hecho del proyecto y no del entorno —el
|
|
144
|
+
* despliegue dijo que sí—, así que no se lanza: se devuelve lo que haya sin acotar por
|
|
145
|
+
* PID, que es donde estará la traza de la muerte. Filtrar por un PID que ya no existe
|
|
146
|
+
* se llevaría por delante justo esa traza.
|
|
147
|
+
*/
|
|
148
|
+
return correr(exigirAdb(), ["logcat", "-d", "-t", "400"], TOPES_MS.adb);
|
|
149
|
+
}
|
|
150
|
+
return correr(exigirAdb(), ["logcat", "-d", "--pid", pid], TOPES_MS.adb);
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=sensorDeArranque.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sensorDeArranque.js","sourceRoot":"","sources":["../../../src/agent/dispositivos/sensorDeArranque.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convertir un documento del proyecto a PDF, imprimiéndolo con un NAVEGADOR.
|
|
3
|
+
*
|
|
4
|
+
* El porqué de que esto lo haga el harness y no el agente está en `core/exportacion.ts`.
|
|
5
|
+
* Aquí está lo que toca disco, y tres decisiones que no son de forma:
|
|
6
|
+
*
|
|
7
|
+
* - **El navegador se busca en una lista CERRADA de rutas conocidas**, igual que las
|
|
8
|
+
* herramientas de `core/dispositivos.ts`: lo que no está en la lista no se lanza. Y su RUTA
|
|
9
|
+
* se queda en el host — por el cable viaja que hay navegador o que no, nunca dónde.
|
|
10
|
+
* - **Se imprime SIN JavaScript** (`--blink-settings=scriptEnabled=false`). Un `.md` o un
|
|
11
|
+
* `.html` del proyecto puede traer un `<script>` dentro, y un PDF no necesita ejecutar
|
|
12
|
+
* nada: es la misma razón por la que un artefacto se sirve en un iframe con origen opaco.
|
|
13
|
+
* - **El HTML intermedio se escribe FUERA del proyecto.** Dentro entraría en git, en el
|
|
14
|
+
* commit de cada turno y en la siguiente subida a CloudStudio — un fichero de andamiaje en
|
|
15
|
+
* la app del cliente.
|
|
16
|
+
*/
|
|
17
|
+
import { execFile } from "node:child_process";
|
|
18
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { basename, join, resolve } from "node:path";
|
|
21
|
+
import { conPoliticaSinScripts, documentoImprimible, esMarkdown, motivoDeExportacionInaceptable, rutaDePdf, } from "../core/exportacion.js";
|
|
22
|
+
import { motivoDeRutaInaceptable } from "./grafo/arbolDeProyecto.js";
|
|
23
|
+
/**
|
|
24
|
+
* Dónde puede estar un navegador que sepa imprimir. Lista CERRADA y en este orden.
|
|
25
|
+
*
|
|
26
|
+
* Son todos Chromium por dentro, que es el que trae `--print-to-pdf`: Safari y Firefox no
|
|
27
|
+
* tienen equivalente en línea de comandos, así que no están y no es un olvido.
|
|
28
|
+
*/
|
|
29
|
+
export const NAVEGADORES_CONOCIDOS = [
|
|
30
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
31
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
32
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
33
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
34
|
+
"/usr/bin/chromium",
|
|
35
|
+
"/usr/bin/chromium-browser",
|
|
36
|
+
"/usr/bin/google-chrome",
|
|
37
|
+
"/usr/bin/google-chrome-stable",
|
|
38
|
+
];
|
|
39
|
+
/** El primero que exista, o AUSENTE. Ausente = aquí no se puede imprimir, y se DICE con el
|
|
40
|
+
* remedio: no hay forma de adivinar que lo que falta es un navegador. */
|
|
41
|
+
export function navegadorParaImprimir(existe = existsSync) {
|
|
42
|
+
return NAVEGADORES_CONOCIDOS.find((ruta) => existe(ruta));
|
|
43
|
+
}
|
|
44
|
+
/** Qué decir cuando no hay ninguno. Vive aquí y no en quien llama para que las tres pieles
|
|
45
|
+
* digan lo mismo. */
|
|
46
|
+
export const SIN_NAVEGADOR = "no encuentro un navegador con el que imprimir. El PDF se genera con Chrome, Chromium, Edge o Brave " +
|
|
47
|
+
"en modo headless; instala uno y vuelve a intentarlo.";
|
|
48
|
+
/**
|
|
49
|
+
* Exporta `ruta` (relativa al proyecto) a un PDF hermano. Nunca LANZA.
|
|
50
|
+
*
|
|
51
|
+
* Las barreras son las MISMAS que usa la pestaña Ficheros y en el mismo orden: la criba de
|
|
52
|
+
* balde sobre el texto (`motivoDeRutaInaceptable`) y luego el camino REAL, porque un enlace
|
|
53
|
+
* simbólico dentro de la raíz puede apuntar fuera. No hay una segunda copia de esas reglas
|
|
54
|
+
* aquí: un segundo sitio donde decidir sobre una ruta es un segundo sitio donde el
|
|
55
|
+
* fail-closed puede dejar de estarlo.
|
|
56
|
+
*/
|
|
57
|
+
export async function exportarAPdf(opciones) {
|
|
58
|
+
const malaRuta = motivoDeRutaInaceptable(opciones.ruta);
|
|
59
|
+
if (malaRuta !== undefined)
|
|
60
|
+
return { error: malaRuta };
|
|
61
|
+
const malaExtension = motivoDeExportacionInaceptable(opciones.ruta);
|
|
62
|
+
if (malaExtension !== undefined)
|
|
63
|
+
return { error: malaExtension };
|
|
64
|
+
const navegador = opciones.navegador ?? (opciones.buscarNavegador ?? navegadorParaImprimir)();
|
|
65
|
+
if (navegador === undefined)
|
|
66
|
+
return { error: SIN_NAVEGADOR };
|
|
67
|
+
const origen = resolve(opciones.raiz, opciones.ruta);
|
|
68
|
+
if (!existsSync(origen))
|
|
69
|
+
return { error: `«${opciones.ruta}» no está en el proyecto` };
|
|
70
|
+
let fuente;
|
|
71
|
+
try {
|
|
72
|
+
fuente = readFileSync(origen, "utf8");
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// El mensaje de Node lleva la ruta absoluta y esto viaja por el cable (`sinRutas`).
|
|
76
|
+
return { error: `no se pudo leer «${opciones.ruta}»` };
|
|
77
|
+
}
|
|
78
|
+
let cuerpo;
|
|
79
|
+
try {
|
|
80
|
+
cuerpo = esMarkdown(opciones.ruta) ? await (opciones.aHtml ?? marcadoAHtml)(fuente) : fuente;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return { error: `no se pudo convertir «${opciones.ruta}»` };
|
|
84
|
+
}
|
|
85
|
+
// Fuera del proyecto: dentro entraría en git, en el commit del turno y en la subida.
|
|
86
|
+
const taller = mkdtempSync(join(tmpdir(), "xonecode-pdf-"));
|
|
87
|
+
const html = join(taller, "documento.html");
|
|
88
|
+
const destino = resolve(opciones.raiz, rutaDePdf(opciones.ruta));
|
|
89
|
+
try {
|
|
90
|
+
writeFileSync(html, esMarkdown(opciones.ruta)
|
|
91
|
+
? documentoImprimible(cuerpo, basename(opciones.ruta))
|
|
92
|
+
: // Un `.html` no pasa por la plantilla, así que se le inyecta la política aparte. No
|
|
93
|
+
// se envuelve dentro de la nuestra: meter un documento entero en nuestro `<body>`
|
|
94
|
+
// lo rompería.
|
|
95
|
+
conPoliticaSinScripts(cuerpo), "utf8");
|
|
96
|
+
await (opciones.imprimir ?? lanzarNavegador)(navegador, [
|
|
97
|
+
"--headless",
|
|
98
|
+
"--disable-gpu",
|
|
99
|
+
// **Aquí NO va ningún flag para desactivar JavaScript**, y no es un olvido: medido
|
|
100
|
+
// contra el Chrome instalado, ni `--disable-javascript` ni
|
|
101
|
+
// `--blink-settings=scriptEnabled=false` lo desactivan — y el segundo además hace que
|
|
102
|
+
// `--print-to-pdf` no escriba nada, en silencio y saliendo con 0. Lo que sí funciona es
|
|
103
|
+
// la CSP del documento (`POLITICA_SIN_SCRIPTS`).
|
|
104
|
+
"--no-pdf-header-footer",
|
|
105
|
+
`--print-to-pdf=${destino}`,
|
|
106
|
+
`file://${html}`,
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const motivo = error instanceof Error ? error.message : String(error);
|
|
111
|
+
return { error: `el navegador no pudo imprimir: ${motivo}` };
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
// El taller se borra pase lo que pase: son ficheros nuestros en un temporal.
|
|
115
|
+
rmSync(taller, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
// «Terminó bien» y «hay PDF» son dos cosas: el navegador sale con 0 aunque no escriba nada.
|
|
118
|
+
// Es la misma regla que ya usa la instalación de dispositivos — manda la MEDIDA, no el
|
|
119
|
+
// código de salida.
|
|
120
|
+
if (!existsSync(destino))
|
|
121
|
+
return { error: "el navegador terminó sin escribir ningún PDF" };
|
|
122
|
+
return { ruta: rutaDePdf(opciones.ruta) };
|
|
123
|
+
}
|
|
124
|
+
/** `marked` cargado perezosamente, por lo dicho arriba. */
|
|
125
|
+
async function marcadoAHtml(markdown) {
|
|
126
|
+
const { marked } = await import("marked");
|
|
127
|
+
return marked.parse(markdown, { async: false });
|
|
128
|
+
}
|
|
129
|
+
function lanzarNavegador(navegador, argumentos) {
|
|
130
|
+
return new Promise((resolver, rechazar) => {
|
|
131
|
+
execFile(navegador, [...argumentos], { timeout: TOPE_DE_IMPRESION_MS }, (error) =>
|
|
132
|
+
// Su stderr trae avisos del sistema gráfico incluso cuando todo va bien, así que lo que
|
|
133
|
+
// se mira es el error del proceso y después si el fichero está.
|
|
134
|
+
error === null ? resolver() : rechazar(error));
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Un documento largo con tablas tarda segundos, no minutos; un navegador que no vuelve en
|
|
138
|
+
* este plazo está colgado y no imprimiendo. */
|
|
139
|
+
export const TOPE_DE_IMPRESION_MS = 2 * 60 * 1000;
|
|
140
|
+
//# sourceMappingURL=exportarPdf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exportarPdf.js","sourceRoot":"","sources":["../../src/agent/exportarPdf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { tool } from "@langchain/core/tools";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { esRutaDeArtefacto, mimeDeArtefacto, nombreDeArtefacto } from "../../core/artefactos.js";
|
|
4
|
+
import { juzgarPantalla, TOPE_DE_PETICIONES, } from "../dispositivos/juezVisual.js";
|
|
5
|
+
/**
|
|
6
|
+
* `xone_critica_visual`: enseñarle una captura al crítico y traer su veredicto.
|
|
7
|
+
*
|
|
8
|
+
* **Por qué es una TOOL y no un paso del cierre.** El crítico necesita PEDIR pantallas —juzga
|
|
9
|
+
* la captura que le den, así que si el conductor fotografió el login, dictamina sobre el
|
|
10
|
+
* login—, y quien puede ir a buscarlas es el conductor. Las tres formas de resolver eso se
|
|
11
|
+
* midieron contra los invariantes del repo y dos se caen solas:
|
|
12
|
+
*
|
|
13
|
+
* - Que el conductor haga de crítico es que el que trabaja se puntúe a sí mismo. Es lo que
|
|
14
|
+
* `juezDeTarea` ya prohíbe por escrito —«a un modelo se le puede pedir que avise y a veces
|
|
15
|
+
* no avisa»— y lo que el prompt del conductor tiene prohibido («no digas que hiciste un
|
|
16
|
+
* paso que no hiciste»). Medido el mismo día: un turno describió los 19 botones de una
|
|
17
|
+
* pantalla leyéndolos del `.xne` y se leía como si los hubiera visto.
|
|
18
|
+
* - Que el crítico conduzca el aparato es darle una shell, y el invariante es «backend con
|
|
19
|
+
* shell, exactamente UNO»: una shell no pasa por `permisosDe` ni por el `virtualMode`, o
|
|
20
|
+
* sea que concederla es conceder la máquina — y precisamente al que existe para ser
|
|
21
|
+
* independiente.
|
|
22
|
+
*
|
|
23
|
+
* Queda ésta: el crítico **pide por nombre** y el orquestador vuelve a delegar en el
|
|
24
|
+
* conductor. La tool no toca el aparato, no escribe nada y solo lee de la carpeta de
|
|
25
|
+
* artefactos de la sesión.
|
|
26
|
+
*
|
|
27
|
+
* **La guarda de ruta se RE-APLICA a mano**, como en las otras dos tools propias: una tool de
|
|
28
|
+
* LangChain añadida por xonecode NO pasa por el middleware de permisos, así que sin esto sería
|
|
29
|
+
* una forma de leer cualquier fichero de la máquina y mandárselo a un modelo.
|
|
30
|
+
*/
|
|
31
|
+
const Entrada = z.object({
|
|
32
|
+
captura: z
|
|
33
|
+
.string()
|
|
34
|
+
.describe("La ruta virtual de la captura, siempre bajo /artefactos/ (la que anunció el evento)"),
|
|
35
|
+
pantalla: z
|
|
36
|
+
.string()
|
|
37
|
+
.describe("De qué pantalla o colección es la captura, para que el crítico sepa qué mira"),
|
|
38
|
+
});
|
|
39
|
+
export function crearCriticaVisual(deps) {
|
|
40
|
+
return tool(async (entrada) => {
|
|
41
|
+
// Lista BLANCA de forma, sobre el TEXTO que escribe el modelo. `esRutaDeArtefacto` no es
|
|
42
|
+
// un `startsWith`: comprueba la forma entera.
|
|
43
|
+
if (!esRutaDeArtefacto(entrada.captura)) {
|
|
44
|
+
return `«${entrada.captura}» no es una captura de esta sesión. Solo puedo mirar lo que hay bajo /artefactos/.`;
|
|
45
|
+
}
|
|
46
|
+
const nombre = nombreDeArtefacto(entrada.captura);
|
|
47
|
+
const mime = mimeDeArtefacto(nombre);
|
|
48
|
+
if (mime === undefined || !mime.startsWith("image/")) {
|
|
49
|
+
return `«${nombre}» no es una imagen, así que no hay nada que mirar.`;
|
|
50
|
+
}
|
|
51
|
+
let bytes;
|
|
52
|
+
try {
|
|
53
|
+
bytes = await deps.leerArtefacto(nombre);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// El mensaje de un error de Node lleva la ruta absoluta y esto va al modelo: solo el
|
|
57
|
+
// nombre del error. Y se DEVUELVE, no se lanza: que falte una captura no puede
|
|
58
|
+
// llevarse el turno por delante.
|
|
59
|
+
return `No pude abrir «${nombre}» (${error instanceof Error ? error.name : "error"}).`;
|
|
60
|
+
}
|
|
61
|
+
let veredicto;
|
|
62
|
+
try {
|
|
63
|
+
veredicto = await juzgarPantalla({ base64: bytes.toString("base64"), mime }, { pantalla: entrada.pantalla }, deps.invocar);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
// Fallo del ENTORNO —sin modelo, sin clave, sin red—: se dice, y no se convierte en un
|
|
67
|
+
// veredicto. Un rojo inventado culparía al trabajo de un problema de la máquina.
|
|
68
|
+
return `No se pudo consultar al crítico visual: ${error instanceof Error ? error.message : "error"}`;
|
|
69
|
+
}
|
|
70
|
+
const lineas = [`Veredicto visual de «${entrada.pantalla}»: ${veredicto.veredicto}.`];
|
|
71
|
+
if (veredicto.observaciones.length > 0) {
|
|
72
|
+
/**
|
|
73
|
+
* **El aviso no es cortesía: está medido.** Seis vueltas sobre la misma captura
|
|
74
|
+
* describieron un texto CORTADO como «girado 180°», y pedirle en el prompt que no
|
|
75
|
+
* diagnostique no lo evitó. A dónde apunta sí acertó las seis veces. Sin esta línea,
|
|
76
|
+
* el desarrollador se pone a buscar una rotación que no existe.
|
|
77
|
+
*/
|
|
78
|
+
lineas.push("Lo que dice que ve (la REDACCIÓN no es fiable: describe mal la causa. Fíate de QUÉ", "control señala, no de su explicación, y míralo tú):");
|
|
79
|
+
for (const o of veredicto.observaciones)
|
|
80
|
+
lineas.push(`- ${o}`);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* **Un rojo tiene que ENTERAR a alguien, y el sitio fuerte es aquí.**
|
|
84
|
+
*
|
|
85
|
+
* La regla de «lo que salga vuelve a `developer-xone`» vive en el prompt del
|
|
86
|
+
* orquestador, o sea a miles de tokens del momento en que llega el veredicto. Eso es
|
|
87
|
+
* una sugerencia. Este repo ya tiene escrito que «los avisos de honestidad son código,
|
|
88
|
+
* no prompt», y el patrón para que un modelo haga lo siguiente es el de
|
|
89
|
+
* `xone_navegacion` cuando no sabe contestar: se devuelve el PASO SIGUIENTE escrito, no
|
|
90
|
+
* un consejo — a un modelo al que se le dice qué hacer sin decirle cómo se le inventa
|
|
91
|
+
* los argumentos.
|
|
92
|
+
*
|
|
93
|
+
* **Límite declarado, y es el que queda abierto**: esto no es un tope. No puede serlo
|
|
94
|
+
* desde aquí, porque la tool se construye por SESIÓN y no por turno, así que no tiene
|
|
95
|
+
* dónde contar las vueltas. Lo que acota hoy es que cada arreglo pasa por una
|
|
96
|
+
* aprobación humana. Un tope de verdad exige que el bucle lo lleve el harness
|
|
97
|
+
* (`conVerificacion`), y eso pide conducir el aparato desde código — medido: no se
|
|
98
|
+
* puede, porque llegar a una pantalla necesita abrir cajones y mirar el árbol.
|
|
99
|
+
*/
|
|
100
|
+
if (veredicto.veredicto === "rojo") {
|
|
101
|
+
lineas.push("Esto es un defecto del proyecto SIN ARREGLAR: no lo cuentes y ya está. Encárgaselo a", "`developer-xone` pasándole estas observaciones tal cual, y cuando lo haya corregido", "manda otra vez al conductor a esta misma pantalla y vuelve a llamarme con la captura", "nueva. No des la pantalla por buena hasta que yo la vea en verde.");
|
|
102
|
+
}
|
|
103
|
+
if (veredicto.necesito.length > 0) {
|
|
104
|
+
lineas.push(`Necesita ver ${veredicto.necesito.length === 1 ? "otra pantalla" : "otras pantallas"} para dictaminar: ${veredicto.necesito.join(", ")}.`, `Encarga al conductor que llegue ahí y capture, y vuelve a llamarme con esa captura (como mucho ${TOPE_DE_PETICIONES} veces).`);
|
|
105
|
+
}
|
|
106
|
+
return lineas.join("\n");
|
|
107
|
+
}, {
|
|
108
|
+
name: "xone_critica_visual",
|
|
109
|
+
description: "Enseña una captura de pantalla del aparato a un revisor visual y devuelve qué se ve " +
|
|
110
|
+
"mal: texto cortado, controles solapados o fuera de la pantalla, cosas ilegibles. " +
|
|
111
|
+
"Ve lo que ninguna comprobación estática puede ver. Puede pedir otras pantallas.",
|
|
112
|
+
schema: Entrada,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=criticaVisual.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"criticaVisual.js","sourceRoot":"","sources":["../../../src/agent/grafo/criticaVisual.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { tool } from "@langchain/core/tools";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { partirNombre } from "../../core/navegacion.js";
|
|
3
4
|
import { llamadaDeBusqueda } from "./busquedaRegex.js";
|
|
5
|
+
import { pintarEstiloDeProp } from "../../core/estilos.js";
|
|
4
6
|
/**
|
|
5
7
|
* `xone_navegacion`: preguntar por la ESTRUCTURA del proyecto sin leer ficheros.
|
|
6
8
|
*
|
|
@@ -36,16 +38,19 @@ export const LIMITES_NAVEGACION = {
|
|
|
36
38
|
};
|
|
37
39
|
const ESQUEMA = z.object({
|
|
38
40
|
operacion: z
|
|
39
|
-
.enum(["inventario", "definicion", "referencias", "campos", "detalle", "app", "problemas"])
|
|
41
|
+
.enum(["inventario", "definicion", "referencias", "campos", "detalle", "app", "problemas", "estilos"])
|
|
40
42
|
.describe("inventario: todas las colecciones. definicion: dónde se declara. referencias: quién la usa. " +
|
|
41
43
|
"campos: los campos de una colección. detalle: todo de una (campos, eventos, nodos, conexiones). " +
|
|
42
|
-
"app: por dónde arranca la aplicación, login y estilos. problemas: referencias rotas y colecciones que no usa nadie"
|
|
44
|
+
"app: por dónde arranca la aplicación, login y estilos. problemas: referencias rotas y colecciones que no usa nadie. " +
|
|
45
|
+
"estilos: qué estilo acaba aplicándosele a un control y de qué clase y fichero sale — resuelve la cascada, " +
|
|
46
|
+
"que es lo que no se puede averiguar leyendo una hoja de arriba abajo"),
|
|
43
47
|
nombre: z
|
|
44
48
|
.string()
|
|
45
49
|
.min(1)
|
|
46
50
|
.max(200)
|
|
47
51
|
.optional()
|
|
48
|
-
.describe("`Coleccion` o `Coleccion.CAMPO`. No hace falta en `inventario`"
|
|
52
|
+
.describe("`Coleccion` o `Coleccion.CAMPO`. No hace falta en `inventario`. En `estilos` tiene que ser " +
|
|
53
|
+
"`Coleccion.CONTROL`"),
|
|
49
54
|
});
|
|
50
55
|
/** Recorta y DICE lo que se dejó fuera. */
|
|
51
56
|
function recortar(lista, tope, pinta) {
|
|
@@ -61,7 +66,7 @@ const pintarReferencia = (r) => `${r.desde} --${r.por}--> ${r.hacia} ${r.ficher
|
|
|
61
66
|
* pueda decir sobre QUÉ proyecto pregunta: si pudiera, esto sería una tool que lee cualquier
|
|
62
67
|
* carpeta de la máquina.
|
|
63
68
|
*/
|
|
64
|
-
export function crearNavegacionXone(cargar, ficheros) {
|
|
69
|
+
export function crearNavegacionXone(cargar, ficheros, cargarEstilos) {
|
|
65
70
|
return tool(async (entrada) => {
|
|
66
71
|
let indice;
|
|
67
72
|
try {
|
|
@@ -127,6 +132,34 @@ export function crearNavegacionXone(cargar, ficheros) {
|
|
|
127
132
|
}
|
|
128
133
|
return recortar(campos, LIMITES_NAVEGACION.campos, pintarDeclaracion);
|
|
129
134
|
}
|
|
135
|
+
if (entrada.operacion === "estilos") {
|
|
136
|
+
/**
|
|
137
|
+
* **La capa que no tenía índice.** Medido sobre diez pasadas del mismo encargo visual:
|
|
138
|
+
* el turno se iba en 30-42 `grep` de los cuales ~22 de cada 30 eran la MISMA clase con
|
|
139
|
+
* variantes (`xnTituloHeaderC`, `.xnTituloHeader`, `xnHeader`…), más cinco ficheros CSS
|
|
140
|
+
* y varios `.xne` ajenos buscando ejemplos. Nada de eso lo contestaba este índice,
|
|
141
|
+
* porque indexa el modelo `.xne` y el arreglo visual vive en los estilos.
|
|
142
|
+
*
|
|
143
|
+
* La cascada la resuelve `xone-linter` (`Stylesheet.lookup`, fiel a
|
|
144
|
+
* `FindStylesheetByClassName`), no nosotros: ver `agent/navegacion/estilosEnDisco.ts`.
|
|
145
|
+
*/
|
|
146
|
+
if (cargarEstilos === undefined) {
|
|
147
|
+
return "Esta operación no está disponible en este montaje: no se cableó el resolvedor de estilos.";
|
|
148
|
+
}
|
|
149
|
+
const { coleccion, campo } = partirNombre(nombre);
|
|
150
|
+
if (campo === undefined || campo === "") {
|
|
151
|
+
return ("`estilos` necesita el control, no solo la colección: escríbelo como " +
|
|
152
|
+
"`Coleccion.CONTROL`. Con `detalle Coleccion` sale la lista de sus campos.");
|
|
153
|
+
}
|
|
154
|
+
const estilo = await cargarEstilos(coleccion, campo);
|
|
155
|
+
if (estilo === undefined) {
|
|
156
|
+
// Ni la coll ni el prop: se dice cómo averiguar CUÁL de las dos falta, en vez de
|
|
157
|
+
// dejar al agente probando nombres. Misma postura que `detalle`.
|
|
158
|
+
return (`No encuentro el control «${nombre}». Prueba \`detalle ${coleccion}\` para ver sus ` +
|
|
159
|
+
`campos, o búscalo como texto:\n${llamadaDeBusqueda(campo)}`);
|
|
160
|
+
}
|
|
161
|
+
return pintarEstiloDeProp(estilo);
|
|
162
|
+
}
|
|
130
163
|
if (entrada.operacion === "detalle") {
|
|
131
164
|
const d = indice.detalle(nombre);
|
|
132
165
|
// `undefined` y no un detalle vacío: «no existe» y «existe y está vacía» son dos
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navegacionXone.js","sourceRoot":"","sources":["../../../src/agent/grafo/navegacionXone.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"navegacionXone.js","sourceRoot":"","sources":["../../../src/agent/grafo/navegacionXone.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* monta su aprobación humana. Un `.md` puede cambiar el prompt de un agente; no puede
|
|
9
9
|
* concederle leer `/.env`.
|
|
10
10
|
*/
|
|
11
|
+
import { esRutaDeArtefacto, RUTA_ARTEFACTOS } from "../../core/artefactos.js";
|
|
12
|
+
import { esRutaDePlan, RUTA_PLANES } from "../../core/planes.js";
|
|
11
13
|
import { artefactoFueraDeSitio } from "../../core/artefactos.js";
|
|
12
14
|
/** Las tools de fichero que monta deepagents sobre el backend. */
|
|
13
15
|
export const TOOLS_LECTURA = ["ls", "read_file", "glob", "grep"];
|
|
@@ -54,14 +56,35 @@ export function permisosDe(perfil) {
|
|
|
54
56
|
const base = [...DENEGADO_SIEMPRE];
|
|
55
57
|
if (!perfil.soloLectura)
|
|
56
58
|
return base;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
/**
|
|
60
|
+
* **Un agente de SOLO LECTURA sí puede dejar ARTEFACTOS, y la medida que lo permite está
|
|
61
|
+
* hecha.** Este `/**` también denegaba `/artefactos/**`, así que un productor sin shell no
|
|
62
|
+
* podía dejar nada escrito —un análisis, un plan, un informe— aunque no tocara el proyecto.
|
|
63
|
+
* `device-controller` los deja por otro camino, un comando, que no pasa por aquí, y por eso
|
|
64
|
+
* su anuncio vive aparte (`proyecto.ts#anunciarArtefactosDeLaShell`).
|
|
65
|
+
*
|
|
66
|
+
* La duda que quedaba anotada era si un `allow` haría algo o sería decorativo. **Medido
|
|
67
|
+
* contra la librería**: `decidePathAccess` es *first-match-wins* con default permisivo, y su
|
|
68
|
+
* propio docstring lo dice. O sea que la excepción funciona **si va delante** del `deny`
|
|
69
|
+
* general — y por eso el orden de estas tres líneas no es estilo, es la regla.
|
|
70
|
+
*
|
|
71
|
+
* Va DETRÁS de `DENEGADO_SIEMPRE` a propósito: con first-match-wins, eso es lo que mantiene
|
|
72
|
+
* ganando a `/.env`, `/.git` y `/.xonecode`. Delante de ellas, un patrón más ancho escrito
|
|
73
|
+
* mañana los abriría.
|
|
74
|
+
*
|
|
75
|
+
* Y lo que concede está acotado por construcción: `/artefactos/` no es del proyecto, no
|
|
76
|
+
* entra en git, no sube a CloudStudio, no pasa por aprobación —por eso se ANUNCIA con su
|
|
77
|
+
* evento— y `artefactoFueraDeSitio` ya impide que nada de ahí acabe dentro de la app.
|
|
78
|
+
*/
|
|
79
|
+
return [
|
|
80
|
+
...base,
|
|
81
|
+
{ operations: ["write"], paths: [`${RUTA_ARTEFACTOS}**`], mode: "allow" },
|
|
82
|
+
// Y los PLANES, por lo mismo: quien analiza escribe el plan y quien desarrolla marca ahí
|
|
83
|
+
// lo hecho, así que los dos tienen que poder escribir — y ninguno de los dos está tocando
|
|
84
|
+
// el proyecto. `.xonecode/planes/` no entra en git ni sube a CloudStudio.
|
|
85
|
+
{ operations: ["write"], paths: [`${RUTA_PLANES}**`], mode: "allow" },
|
|
86
|
+
{ operations: ["write"], paths: ["/**"], mode: "deny" },
|
|
87
|
+
];
|
|
65
88
|
}
|
|
66
89
|
/**
|
|
67
90
|
* Las tools de fichero de un agente con EJECUCIÓN.
|
|
@@ -195,13 +218,33 @@ export function hitlDe(perfil) {
|
|
|
195
218
|
*
|
|
196
219
|
* Lo que pasa después es lo que se busca: la tool corre, el backend devuelve su `{error}` con
|
|
197
220
|
* la ruta buena, y el modelo reintenta en `/artefactos/`.
|
|
221
|
+
*
|
|
222
|
+
* **Y un SEGUNDO motivo, que es de coherencia y no de relajación**: tampoco se pregunta por lo
|
|
223
|
+
* que NO ES EL PROYECTO —`/artefactos/` y `/planes/`—. No es una excepción nueva: un perfil de
|
|
224
|
+
* SOLO LECTURA ya escribe esas dos rutas sin aprobación ninguna, porque `hitlDe` le devuelve
|
|
225
|
+
* `{}` y sus `permissions` lo confinan exactamente ahí. O sea que la misma ruta no se aprobaba
|
|
226
|
+
* para el analista y sí para el desarrollador, y eso no es una barrera: es una incoherencia que
|
|
227
|
+
* se paga cara — medido, el desarrollador fue a marcar como hecha una tarea de un plan, salió
|
|
228
|
+
* un modal, se rechazó, y el plan se quedó viejo en silencio. En una tarea de fondo no hay
|
|
229
|
+
* nadie que pulse, así que el plan NUNCA se marcaría.
|
|
230
|
+
*
|
|
231
|
+
* Ninguna de las dos rutas es la app del cliente: no entran en el commit del turno, no van a
|
|
232
|
+
* git y no suben a CloudStudio. Lo que sigue intacto es lo único que importa: **por un fichero
|
|
233
|
+
* del PROYECTO siempre se pregunta**, y el test lo comprueba ruta a ruta.
|
|
198
234
|
*/
|
|
199
235
|
export function seDetieneEn(peticion) {
|
|
200
236
|
const args = peticion?.toolCall?.args;
|
|
201
237
|
const ruta = args?.["file_path"];
|
|
202
238
|
if (typeof ruta !== "string")
|
|
203
239
|
return true;
|
|
240
|
+
// (1) Lo que el backend va a rechazar de todas formas: un modal cuyo único final posible es
|
|
241
|
+
// un rechazo enseña a aprobar sin mirar.
|
|
242
|
+
if (artefactoFueraDeSitio(ruta) !== undefined)
|
|
243
|
+
return false;
|
|
244
|
+
// (2) Lo que NO es el proyecto. Ver la cabecera: esto quita una incoherencia, no una barrera.
|
|
245
|
+
if (esRutaDeArtefacto(ruta) || esRutaDePlan(ruta))
|
|
246
|
+
return false;
|
|
204
247
|
// Ante la duda, se PREGUNTA: es la dirección conservadora de siempre.
|
|
205
|
-
return
|
|
248
|
+
return true;
|
|
206
249
|
}
|
|
207
250
|
//# sourceMappingURL=perfiles.js.map
|