xonecode 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -7
- package/THIRD_PARTY_NOTICES.md +128 -0
- package/apps/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/apps/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/apps/web/dist/assets/c-BIGW1oBm.js +1 -0
- package/apps/web/dist/assets/cpp-DddhVWhK.js +1 -0
- package/apps/web/dist/assets/csharp-DSvCPggb.js +1 -0
- package/apps/web/dist/assets/css-CLj8gQPS.js +1 -0
- package/apps/web/dist/assets/go-C27-OAKa.js +1 -0
- package/apps/web/dist/assets/html-CfGypltT.js +1 -0
- package/apps/web/dist/assets/index-BB3SamhR.css +1 -0
- package/apps/web/dist/assets/index-CFYAoHdw.js +477 -0
- package/apps/web/dist/assets/ini-BEwlwnbL.js +1 -0
- package/apps/web/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/apps/web/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/apps/web/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/apps/web/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/apps/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/apps/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/apps/web/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/apps/web/dist/assets/java-CylS5w8V.js +1 -0
- package/apps/web/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/apps/web/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/apps/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/apps/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/apps/web/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/apps/web/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/apps/web/dist/assets/less-B1dDrJ26.js +1 -0
- package/apps/web/dist/assets/lua-BaeVxFsk.js +1 -0
- package/apps/web/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/apps/web/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/apps/web/dist/assets/php-D_2--9WK.js +1 -0
- package/apps/web/dist/assets/python-B6aJPvgy.js +1 -0
- package/apps/web/dist/assets/ruby-DSHSDbk1.js +1 -0
- package/apps/web/dist/assets/rust-B1yitclQ.js +1 -0
- package/apps/web/dist/assets/scss-D5BDwBP9.js +1 -0
- package/apps/web/dist/assets/sql-CRqJ_cUM.js +1 -0
- package/apps/web/dist/assets/swift-C2oV4EkX.js +1 -0
- package/apps/web/dist/assets/toml-vGWfd6FD.js +1 -0
- package/apps/web/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/apps/web/dist/assets/yaml-Buea-lGh.js +1 -0
- package/apps/web/dist/iconos/xonecode.png +0 -0
- package/apps/web/dist/index.html +20 -0
- package/dist/agent/agentesEnDisco.js +539 -0
- package/dist/agent/agentesEnDisco.js.map +1 -0
- package/dist/agent/arbolDeProyecto.js +245 -0
- package/dist/agent/arbolDeProyecto.js.map +1 -0
- package/dist/agent/artefactosEnDisco.js +139 -0
- package/dist/agent/artefactosEnDisco.js.map +1 -0
- package/dist/agent/aumentador.js +175 -0
- package/dist/agent/aumentador.js.map +1 -0
- package/dist/agent/authEnDisco.js +76 -1
- package/dist/agent/authEnDisco.js.map +1 -1
- package/dist/agent/busquedaRegex.js +92 -0
- package/dist/agent/busquedaRegex.js.map +1 -0
- package/dist/agent/catalogoModelos.js +345 -0
- package/dist/agent/catalogoModelos.js.map +1 -0
- package/dist/agent/checkpointer.js +127 -0
- package/dist/agent/checkpointer.js.map +1 -0
- package/dist/agent/cloudstudioClient.js +194 -0
- package/dist/agent/cloudstudioClient.js.map +1 -0
- package/dist/agent/cloudstudioMcp.js +722 -0
- package/dist/agent/cloudstudioMcp.js.map +1 -0
- package/dist/agent/configEnDisco.js +309 -11
- package/dist/agent/configEnDisco.js.map +1 -1
- package/dist/agent/consumoExterno.js +99 -0
- package/dist/agent/consumoExterno.js.map +1 -0
- package/dist/agent/descarga.js +149 -0
- package/dist/agent/descarga.js.map +1 -0
- package/dist/agent/diagnosticoDeTools.js +51 -0
- package/dist/agent/diagnosticoDeTools.js.map +1 -0
- package/dist/agent/dispositivosEnMaquina.js +596 -0
- package/dist/agent/dispositivosEnMaquina.js.map +1 -0
- package/dist/agent/escrituraDeCodex.js +134 -0
- package/dist/agent/escrituraDeCodex.js.map +1 -0
- package/dist/agent/escrituraDeOpencode.js +80 -0
- package/dist/agent/escrituraDeOpencode.js.map +1 -0
- package/dist/agent/escrituraExterna.js +742 -0
- package/dist/agent/escrituraExterna.js.map +1 -0
- package/dist/agent/gemini.js +81 -0
- package/dist/agent/gemini.js.map +1 -0
- package/dist/agent/git.js +83 -0
- package/dist/agent/git.js.map +1 -0
- package/dist/agent/gitSync.js +441 -0
- package/dist/agent/gitSync.js.map +1 -0
- package/dist/agent/hotswap.js +448 -0
- package/dist/agent/hotswap.js.map +1 -0
- package/dist/agent/instalacionEnMaquina.js +252 -0
- package/dist/agent/instalacionEnMaquina.js.map +1 -0
- package/dist/agent/instantanea.js +7 -10
- package/dist/agent/instantanea.js.map +1 -1
- package/dist/agent/interrupts.js +11 -2
- package/dist/agent/interrupts.js.map +1 -1
- package/dist/agent/juezDeTarea.js +345 -0
- package/dist/agent/juezDeTarea.js.map +1 -0
- package/dist/agent/lanzamientoEnMaquina.js +377 -0
- package/dist/agent/lanzamientoEnMaquina.js.map +1 -0
- package/dist/agent/manifiesto.js +59 -0
- package/dist/agent/manifiesto.js.map +1 -0
- package/dist/agent/memoriaDeProyecto.js +60 -0
- package/dist/agent/memoriaDeProyecto.js.map +1 -0
- package/dist/agent/modelos.js +105 -17
- package/dist/agent/modelos.js.map +1 -1
- package/dist/agent/modelosDeMotor.js +204 -0
- package/dist/agent/modelosDeMotor.js.map +1 -0
- package/dist/agent/paqueteDelProyecto.js +181 -0
- package/dist/agent/paqueteDelProyecto.js.map +1 -0
- package/dist/agent/perfiles.js +92 -31
- package/dist/agent/perfiles.js.map +1 -1
- package/dist/agent/persona.js +40 -0
- package/dist/agent/persona.js.map +1 -0
- package/dist/agent/procesosEnMaquina.js +204 -0
- package/dist/agent/procesosEnMaquina.js.map +1 -0
- package/dist/agent/proyecto.js +311 -13
- package/dist/agent/proyecto.js.map +1 -1
- package/dist/agent/puente.js +63 -6
- package/dist/agent/puente.js.map +1 -1
- package/dist/agent/resumenDeContexto.js +30 -0
- package/dist/agent/resumenDeContexto.js.map +1 -0
- package/dist/agent/resumenDeTool.js +64 -18
- package/dist/agent/resumenDeTool.js.map +1 -1
- package/dist/agent/sesionGit.js +481 -0
- package/dist/agent/sesionGit.js.map +1 -0
- package/dist/agent/settingsEnDisco.js +189 -0
- package/dist/agent/settingsEnDisco.js.map +1 -0
- package/dist/agent/subagenteCodex.js +368 -0
- package/dist/agent/subagenteCodex.js.map +1 -0
- package/dist/agent/subagenteExterno.js +423 -0
- package/dist/agent/subagenteExterno.js.map +1 -0
- package/dist/agent/subagenteOpencode.js +389 -0
- package/dist/agent/subagenteOpencode.js.map +1 -0
- package/dist/agent/subida.js +202 -0
- package/dist/agent/subida.js.map +1 -0
- package/dist/agent/tareasEnDisco.js +466 -0
- package/dist/agent/tareasEnDisco.js.map +1 -0
- package/dist/agent/turnoReal.js +663 -50
- package/dist/agent/turnoReal.js.map +1 -1
- package/dist/agent/versionEnDisco.js +48 -0
- package/dist/agent/versionEnDisco.js.map +1 -0
- package/dist/agent/xoneAgent.js +290 -49
- package/dist/agent/xoneAgent.js.map +1 -1
- package/dist/agent/zip.js +52 -0
- package/dist/agent/zip.js.map +1 -0
- package/dist/cli/acuseDeModelo.js +30 -0
- package/dist/cli/acuseDeModelo.js.map +1 -0
- package/dist/cli/aprobar.js +55 -2
- package/dist/cli/aprobar.js.map +1 -1
- package/dist/cli/config.js +9 -13
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/consola.js +750 -19
- package/dist/cli/consola.js.map +1 -1
- package/dist/cli/main.js +740 -81
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/markdown.js +188 -2
- package/dist/cli/markdown.js.map +1 -1
- package/dist/cli/run.js +32 -4
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/stdio.js +46 -19
- package/dist/cli/stdio.js.map +1 -1
- package/dist/cli/tema.js +96 -8
- package/dist/cli/tema.js.map +1 -1
- package/dist/cli/tokens.js +29 -0
- package/dist/cli/tokens.js.map +1 -0
- package/dist/cli/tui/app.js +171 -0
- package/dist/cli/tui/app.js.map +1 -0
- package/dist/cli/tui/aprobarTui.js +80 -0
- package/dist/cli/tui/aprobarTui.js.map +1 -0
- package/dist/cli/tui/barra.js +37 -0
- package/dist/cli/tui/barra.js.map +1 -0
- package/dist/cli/tui/correrTui.js +478 -0
- package/dist/cli/tui/correrTui.js.map +1 -0
- package/dist/cli/tui/entrada.js +166 -0
- package/dist/cli/tui/entrada.js.map +1 -0
- package/dist/cli/tui/filas.js +24 -0
- package/dist/cli/tui/filas.js.map +1 -0
- package/dist/cli/tui/logo.js +49 -0
- package/dist/cli/tui/logo.js.map +1 -0
- package/dist/cli/tui/pielTui.js +14 -0
- package/dist/cli/tui/pielTui.js.map +1 -0
- package/dist/cli/tui/raton.js +126 -0
- package/dist/cli/tui/raton.js.map +1 -0
- package/dist/cli/tui/sidebar.js +86 -0
- package/dist/cli/tui/sidebar.js.map +1 -0
- package/dist/cli/tui/store.js +143 -0
- package/dist/cli/tui/store.js.map +1 -0
- package/dist/cli/tui/tarjeta.js +19 -0
- package/dist/cli/tui/tarjeta.js.map +1 -0
- package/dist/cli/tui/temaInk.js +114 -0
- package/dist/cli/tui/temaInk.js.map +1 -0
- package/dist/cli/tui/transcript.js +254 -0
- package/dist/cli/tui/transcript.js.map +1 -0
- package/dist/cli/wizardInicial.js +240 -0
- package/dist/cli/wizardInicial.js.map +1 -0
- package/dist/core/actos.js +113 -0
- package/dist/core/actos.js.map +1 -0
- package/dist/core/adjuntos.js +97 -0
- package/dist/core/adjuntos.js.map +1 -0
- package/dist/core/agentes.js +227 -0
- package/dist/core/agentes.js.map +1 -0
- package/dist/core/artefactos.js +149 -0
- package/dist/core/artefactos.js.map +1 -0
- package/dist/core/cloudstudio.js +7 -0
- package/dist/core/cloudstudio.js.map +1 -0
- package/dist/core/config.js +212 -5
- package/dist/core/config.js.map +1 -1
- package/dist/core/contextos.js +53 -1
- package/dist/core/contextos.js.map +1 -1
- package/dist/core/descargas.js +106 -0
- package/dist/core/descargas.js.map +1 -0
- package/dist/core/descriptoresDeApp.js +279 -0
- package/dist/core/descriptoresDeApp.js.map +1 -0
- package/dist/core/dispositivos.js +466 -0
- package/dist/core/dispositivos.js.map +1 -0
- package/dist/core/entrega.js +141 -0
- package/dist/core/entrega.js.map +1 -0
- package/dist/core/entrelazar.js +90 -0
- package/dist/core/entrelazar.js.map +1 -0
- package/dist/core/esqueleto.js +10 -0
- package/dist/core/esqueleto.js.map +1 -1
- package/dist/core/modelos.js +235 -3
- package/dist/core/modelos.js.map +1 -1
- package/dist/core/notify.js +27 -9
- package/dist/core/notify.js.map +1 -1
- package/dist/core/planDeSubida.js +127 -0
- package/dist/core/planDeSubida.js.map +1 -0
- package/dist/core/ports.js +189 -1
- package/dist/core/ports.js.map +1 -1
- package/dist/core/puedeLanzarse.js +239 -0
- package/dist/core/puedeLanzarse.js.map +1 -0
- package/dist/core/rutaVirtual.js +42 -0
- package/dist/core/rutaVirtual.js.map +1 -0
- package/dist/core/settings.js +246 -0
- package/dist/core/settings.js.map +1 -0
- package/dist/core/tareas.js +230 -0
- package/dist/core/tareas.js.map +1 -0
- package/dist/core/textos.js +36 -0
- package/dist/core/textos.js.map +1 -0
- package/dist/core/turno.js +56 -8
- package/dist/core/turno.js.map +1 -1
- package/dist/core/version.js +31 -0
- package/dist/core/version.js.map +1 -0
- package/dist/vendor/tokenTracking.js +22 -1
- package/dist/vendor/tokenTracking.js.map +1 -1
- package/dist/web/servidor/arranque.js +3964 -0
- package/dist/web/servidor/arranque.js.map +1 -0
- package/dist/web/servidor/consolaDeTarea.js +161 -0
- package/dist/web/servidor/consolaDeTarea.js.map +1 -0
- package/dist/web/servidor/consolaWeb.js +389 -0
- package/dist/web/servidor/consolaWeb.js.map +1 -0
- package/dist/web/servidor/corredorDeTareas.js +1119 -0
- package/dist/web/servidor/corredorDeTareas.js.map +1 -0
- package/dist/web/servidor/pielWeb.js +269 -0
- package/dist/web/servidor/pielWeb.js.map +1 -0
- package/dist/web/servidor/servidor.js +257 -0
- package/dist/web/servidor/servidor.js.map +1 -0
- package/dist/web/servidor/sesiones.js +440 -0
- package/dist/web/servidor/sesiones.js.map +1 -0
- package/dist/web/servidor/transporte.js +169 -0
- package/dist/web/servidor/transporte.js.map +1 -0
- package/dist/web/servidor/vestibulo.js +1276 -0
- package/dist/web/servidor/vestibulo.js.map +1 -0
- package/package.json +30 -8
- package/skills/archify/LICENSE +22 -0
- package/skills/archify/SKILL.md +109 -0
- package/skills/archify/THIRD_PARTY_NOTICES.md +56 -0
- package/skills/archify/assets/template.html +14787 -0
- package/skills/archify/bin/archify.mjs +1990 -0
- package/skills/archify/bin/open-artifact.mjs +86 -0
- package/skills/archify/bin/preview.mjs +648 -0
- package/skills/archify/bin/visual-check.mjs +829 -0
- package/skills/archify/brand-marks/README.md +31 -0
- package/skills/archify/brand-marks/catalog.json +131 -0
- package/skills/archify/delta/architecture-delta.mjs +1214 -0
- package/skills/archify/examples/agent-run.lifecycle.json +71 -0
- package/skills/archify/examples/agent-tool-call.workflow.json +94 -0
- package/skills/archify/examples/async-job-roundtrip.sequence.json +61 -0
- package/skills/archify/examples/brand-aware-delivery.architecture.json +47 -0
- package/skills/archify/examples/cache-miss-request.sequence.json +81 -0
- package/skills/archify/examples/checkout-platform.base.architecture.json +31 -0
- package/skills/archify/examples/checkout-platform.head.architecture.json +31 -0
- package/skills/archify/examples/dataflow-product-analytics.html +14898 -0
- package/skills/archify/examples/deployment-release.lifecycle.json +49 -0
- package/skills/archify/examples/event-stream.dataflow.json +57 -0
- package/skills/archify/examples/incident-response.workflow.json +64 -0
- package/skills/archify/examples/lifecycle-agent-run.html +14847 -0
- package/skills/archify/examples/product-analytics.dataflow.json +76 -0
- package/skills/archify/examples/production-deployment.architecture.json +71 -0
- package/skills/archify/examples/release-delivery.workflow.json +62 -0
- package/skills/archify/examples/sequence-cache-miss-request.html +14913 -0
- package/skills/archify/examples/web-app-rendered.html +14862 -0
- package/skills/archify/examples/web-app.architecture.json +46 -0
- package/skills/archify/examples/workflow-agent-tool-call-rendered.html +14904 -0
- package/skills/archify/migrations/workflow-v2.mjs +279 -0
- package/skills/archify/package.json +17 -0
- package/skills/archify/recipes/scenarios.mjs +391 -0
- package/skills/archify/references/authoring-contract.md +198 -0
- package/skills/archify/references/brand-marks.md +69 -0
- package/skills/archify/references/delivery-contract.md +116 -0
- package/skills/archify/references/viewer-runtime.md +45 -0
- package/skills/archify/renderers/architecture/grid.mjs +62 -0
- package/skills/archify/renderers/architecture/render-architecture.mjs +1078 -0
- package/skills/archify/renderers/dataflow/README.md +104 -0
- package/skills/archify/renderers/dataflow/render-dataflow.mjs +483 -0
- package/skills/archify/renderers/lifecycle/README.md +115 -0
- package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
- package/skills/archify/renderers/sequence/README.md +114 -0
- package/skills/archify/renderers/sequence/render-sequence.mjs +453 -0
- package/skills/archify/renderers/shared/brand-marks.mjs +563 -0
- package/skills/archify/renderers/shared/cli.mjs +218 -0
- package/skills/archify/renderers/shared/desktop-readability.mjs +26 -0
- package/skills/archify/renderers/shared/diagnostics.mjs +127 -0
- package/skills/archify/renderers/shared/engineering-profiles.mjs +157 -0
- package/skills/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
- package/skills/archify/renderers/shared/generated-validators.mjs +13 -0
- package/skills/archify/renderers/shared/geometry.mjs +1423 -0
- package/skills/archify/renderers/shared/i18n.mjs +594 -0
- package/skills/archify/renderers/shared/layout-report.mjs +40 -0
- package/skills/archify/renderers/shared/legend.mjs +217 -0
- package/skills/archify/renderers/shared/output-path.mjs +321 -0
- package/skills/archify/renderers/shared/repository-evidence.mjs +235 -0
- package/skills/archify/renderers/shared/text-fit.mjs +49 -0
- package/skills/archify/renderers/shared/utils.mjs +232 -0
- package/skills/archify/renderers/shared/validator.mjs +86 -0
- package/skills/archify/renderers/workflow/README.md +223 -0
- package/skills/archify/renderers/workflow/render-workflow.mjs +35 -0
- package/skills/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- package/skills/archify/schemas/README.md +211 -0
- package/skills/archify/schemas/architecture.schema.json +176 -0
- package/skills/archify/schemas/common.schema.json +115 -0
- package/skills/archify/schemas/dataflow.schema.json +243 -0
- package/skills/archify/schemas/lifecycle.schema.json +266 -0
- package/skills/archify/schemas/sequence.schema.json +223 -0
- package/skills/archify/schemas/workflow.schema.json +428 -0
- package/skills/archify/scripts/check-render-output.mjs +835 -0
- package/skills/archify/scripts/check-update.mjs +1667 -0
- package/skills/archify/scripts/render-examples.mjs +26 -0
- package/skills/archify/scripts/update-contract.mjs +182 -0
- package/skills/archify/skill-release.json +10 -0
- package/skills/artifacts-builder/SKILL.md +120 -0
- package/skills/artifacts-builder/reference/diagramas.md +123 -0
- package/skills/artifacts-builder/reference/estilo.md +123 -0
- package/skills/artifacts-builder/reference/exportar-pdf.md +56 -0
- package/skills/artifacts-builder/reference/graficas.md +22 -0
- package/skills/artifacts-builder/scripts/bundle_artifact.py +109 -0
- package/skills/artifacts-builder/scripts/validate_artifact.py +345 -0
- package/skills/xone-hotswap/SKILL.md +185 -0
- package/skills/xone-hotswap/references/comandos.md +706 -0
- package/skills/xone-hotswap/references/conexion-y-despliegue.md +226 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conexión MCP de CloudStudio.
|
|
3
|
+
*
|
|
4
|
+
* El endpoint protege sus tools con OAuth del IDS. Este adaptador realiza el flujo
|
|
5
|
+
* Authorization Code + PKCE que espera MCP Streamable HTTP: abre el navegador, recibe
|
|
6
|
+
* el callback en loopback y guarda el resultado SOLO en ~/.xonecode, nunca en el repo.
|
|
7
|
+
*/
|
|
8
|
+
import { createServer } from "node:http";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
15
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
16
|
+
import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
17
|
+
import { motivoDeEndpointInaceptable } from "../core/modelos.js";
|
|
18
|
+
const NOMBRE_CARPETA = ".xonecode";
|
|
19
|
+
const NOMBRE_AUTH = "cloudstudio-oauth.json";
|
|
20
|
+
const VERSION = "0.5.0";
|
|
21
|
+
/**
|
|
22
|
+
* Debe ser estable: el IDS registra el redirect_uri del cliente OAuth.
|
|
23
|
+
*
|
|
24
|
+
* Se exporta porque `cli/main.ts` necesita el mismo número para rechazar `--puerto 7634`
|
|
25
|
+
* en el servidor web (el callback de OAuth y la web no pueden compartir puerto). Antes
|
|
26
|
+
* había una copia literal en `main.ts` porque este valor era privado; una sola fuente
|
|
27
|
+
* evita que las dos constantes diverjan si el puerto cambia algún día.
|
|
28
|
+
*/
|
|
29
|
+
export const PUERTO_CALLBACK = 7634;
|
|
30
|
+
/**
|
|
31
|
+
* El CloudStudio oficial. Vive aquí y no en `cli/consola.ts` (de donde se movió) porque
|
|
32
|
+
* es la dirección de un SERVIDOR, no un detalle de la piel de consola, y
|
|
33
|
+
* `adoptarLegadoSiProcede` —de este mismo fichero— la necesita para decidir si el fichero
|
|
34
|
+
* plano de antes de los entornos pertenece al oficial: `agent/` no puede importar de
|
|
35
|
+
* `cli/` (convención documentada en `agent/turnoReal.ts`), así que si esto se hubiera
|
|
36
|
+
* quedado en `consola.ts` la migración habría tenido que duplicar el literal.
|
|
37
|
+
*/
|
|
38
|
+
export const URL_CLOUDSTUDIO_POR_OMISION = "https://mcp.xonewebstudio.com/mcp";
|
|
39
|
+
/** Permisos mínimos para descubrir el catálogo MCP; no otorgan escritura ni ejecución. */
|
|
40
|
+
export const SCOPES_CLOUDSTUDIO = ["openid", "profile", "email", "offline_access", "mcp.read"];
|
|
41
|
+
export const SCOPES_CLOUDSTUDIO_ESCRITURA = [...SCOPES_CLOUDSTUDIO, "mcp.write"];
|
|
42
|
+
/** Un único consentimiento para el trabajo normal del agente; jamás incluye mcp.admin. */
|
|
43
|
+
export const SCOPES_CLOUDSTUDIO_AGENTE = [
|
|
44
|
+
"openid", "profile", "email", "offline_access", "xonewebstudioapi",
|
|
45
|
+
"mcp.read", "mcp.write", "mcp.execute", "mcp.branch",
|
|
46
|
+
];
|
|
47
|
+
/** El id reservado para el fichero plano de antes de los entornos: ver `leerEstado`. */
|
|
48
|
+
const CLAVE_LEGADO = "legado";
|
|
49
|
+
const ESTADO_VACIO = { version: 2, porEntorno: {} };
|
|
50
|
+
/**
|
|
51
|
+
* El listado de proyectos por orden de preferencia.
|
|
52
|
+
*
|
|
53
|
+
* El servidor real de XOne CloudStudio la publica como `studio_list_projects`; los dos
|
|
54
|
+
* alias siguientes son endpoints anteriores. NO se codifica un solo nombre: cuando el
|
|
55
|
+
* servidor renombra su catálogo, el arranque entero se queda sin proyectos, y el usuario
|
|
56
|
+
* ve «no publicó la herramienta» en vez de su lista.
|
|
57
|
+
*/
|
|
58
|
+
const NOMBRES_LISTAR_PROYECTOS = ["studio_list_projects", "project_list", "list_projects", "projects_list"];
|
|
59
|
+
/** Una tool que exige argumentos no se puede invocar con `{}` en el arranque. */
|
|
60
|
+
function pideArgumentos(definicion) {
|
|
61
|
+
const esquema = definicion.inputSchema;
|
|
62
|
+
if (typeof esquema !== "object" || esquema === null)
|
|
63
|
+
return false;
|
|
64
|
+
const obligatorios = esquema.required;
|
|
65
|
+
return Array.isArray(obligatorios) && obligatorios.length > 0;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Qué tool lista los proyectos. Primero los nombres conocidos, en orden; si ninguno está,
|
|
69
|
+
* una heurística conservadora: tiene que hablar de listar Y de proyectos, y no pedir
|
|
70
|
+
* argumentos. `studio_open_project` o `studio_download_project` no la superan a propósito
|
|
71
|
+
* — abrir el proyecto equivocado en el arranque es peor que no encontrar la tool.
|
|
72
|
+
*/
|
|
73
|
+
export function herramientaDeProyectos(tools) {
|
|
74
|
+
for (const nombre of NOMBRES_LISTAR_PROYECTOS) {
|
|
75
|
+
const exacta = tools.find((t) => t.name === nombre);
|
|
76
|
+
if (exacta !== undefined)
|
|
77
|
+
return exacta;
|
|
78
|
+
}
|
|
79
|
+
return tools.find((t) => {
|
|
80
|
+
const nombre = t.name.toLowerCase();
|
|
81
|
+
return /list/.test(nombre) && /project|proyecto/.test(nombre) && !pideArgumentos(t);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export function proyectosDeResultado(valor) {
|
|
85
|
+
if (typeof valor === "string") {
|
|
86
|
+
try {
|
|
87
|
+
return proyectosDeResultado(JSON.parse(valor));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Medido contra el servidor real: `studio_list_projects` NO devuelve una lista, sino un
|
|
94
|
+
// MAPA indexado por id bajo «recents». Por eso cada clave admite las dos formas y la
|
|
95
|
+
// clave del mapa se conserva: es el id cuando la entrada no lo repite en «pid».
|
|
96
|
+
const entradas = (candidato) => Array.isArray(candidato)
|
|
97
|
+
? candidato
|
|
98
|
+
: typeof candidato === "object" && candidato !== null
|
|
99
|
+
? Object.entries(candidato).map(([clave, entrada]) => typeof entrada === "object" && entrada !== null && !Array.isArray(entrada)
|
|
100
|
+
? { ...entrada, __clave: clave }
|
|
101
|
+
: entrada)
|
|
102
|
+
: [];
|
|
103
|
+
const lista = Array.isArray(valor)
|
|
104
|
+
? valor
|
|
105
|
+
: typeof valor === "object" && valor !== null
|
|
106
|
+
? ["projects", "proyectos", "items", "data", "recents"].flatMap((clave) => entradas(valor[clave]))
|
|
107
|
+
: [];
|
|
108
|
+
// Los SDK MCP devuelven el resultado textual dentro de `content`; de todo eso no se
|
|
109
|
+
// conserva más que el id, el nombre y si el proyecto está compartido contigo. El wrapper
|
|
110
|
+
// LangChain de abajo serializa ese resultado para poder usar `.invoke({})` sin traducir
|
|
111
|
+
// los esquemas ajenos.
|
|
112
|
+
if (lista.length === 0 && typeof valor === "object" && valor !== null) {
|
|
113
|
+
const contenido = valor.content;
|
|
114
|
+
if (Array.isArray(contenido)) {
|
|
115
|
+
return contenido.flatMap((bloque) => typeof bloque === "object" && bloque !== null && bloque.type === "text"
|
|
116
|
+
? proyectosDeResultado(bloque.text)
|
|
117
|
+
: []);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const vistos = new Set();
|
|
121
|
+
return lista.flatMap((candidato) => {
|
|
122
|
+
if (typeof candidato !== "object" || candidato === null || Array.isArray(candidato))
|
|
123
|
+
return [];
|
|
124
|
+
const dato = candidato;
|
|
125
|
+
const id = [dato.id, dato.pid, dato.projectId, dato.project_id, dato.__clave].find((v) => typeof v === "string" && v.trim() !== "");
|
|
126
|
+
const nombre = [dato.name, dato.nombre, dato.title].find((v) => typeof v === "string" && v.trim() !== "");
|
|
127
|
+
if (id === undefined || nombre === undefined || vistos.has(id))
|
|
128
|
+
return [];
|
|
129
|
+
vistos.add(id);
|
|
130
|
+
// SOLO si de verdad vino un booleano. Este servidor es flojo con los tipos —en la misma
|
|
131
|
+
// respuesta, `rights` es un objeto serializado como cadena—, así que ni «"true"» ni
|
|
132
|
+
// deducirlo de que `suser` no esté vacío: la ausencia se propaga como ausencia y la
|
|
133
|
+
// interfaz no pinta nada, que es la respuesta honesta para un endpoint que no lo dice.
|
|
134
|
+
return [{ id, nombre, ...(typeof dato.shared === "boolean" ? { compartido: dato.shared } : {}) }];
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Dónde vive el estado OAuth cuando nadie inyecta otra ruta. Exportada porque
|
|
138
|
+
* `web/servidor/vestibulo.ts` la necesita para `adoptarLegadoSiProcede`: una segunda copia
|
|
139
|
+
* del literal es cómo divergen las rutas el día que una se corrige y la otra no. */
|
|
140
|
+
export function rutaAuthPorDefecto() {
|
|
141
|
+
return join(homedir(), NOMBRE_CARPETA, NOMBRE_AUTH);
|
|
142
|
+
}
|
|
143
|
+
/** Un candidato de `porEntorno` que no es objeto (fichero tocado a mano) no puede colar
|
|
144
|
+
* un valor no indexable: `leerEstado` filtra antes de devolverlo, no cada llamador. */
|
|
145
|
+
function porEntornoValido(bruto) {
|
|
146
|
+
if (typeof bruto !== "object" || bruto === null || Array.isArray(bruto))
|
|
147
|
+
return {};
|
|
148
|
+
const salida = {};
|
|
149
|
+
for (const [id, valor] of Object.entries(bruto)) {
|
|
150
|
+
if (typeof valor === "object" && valor !== null && !Array.isArray(valor)) {
|
|
151
|
+
salida[id] = valor;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return salida;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Lee el fichero entero. Un formato SIN `version` es el plano de antes de los entornos:
|
|
158
|
+
* se envuelve como `{ version: 2, porEntorno: { legado: <plano> } }` **solo en memoria**
|
|
159
|
+
* — un arranque de solo lectura (`xonecode config`, `describe`) no puede reescribir el
|
|
160
|
+
* disco del usuario, así que la migración se materializa recién en la primera escritura
|
|
161
|
+
* (`guardarEstadoDeEntorno`, `adoptarLegadoSiProcede`), nunca aquí.
|
|
162
|
+
*/
|
|
163
|
+
export function leerEstado(ruta) {
|
|
164
|
+
if (!existsSync(ruta))
|
|
165
|
+
return ESTADO_VACIO;
|
|
166
|
+
try {
|
|
167
|
+
const bruto = JSON.parse(readFileSync(ruta, "utf8"));
|
|
168
|
+
if (typeof bruto !== "object" || bruto === null || Array.isArray(bruto))
|
|
169
|
+
return ESTADO_VACIO;
|
|
170
|
+
const objeto = bruto;
|
|
171
|
+
if (objeto.version === undefined) {
|
|
172
|
+
// El formato plano no tenía `version`; el objeto entero ES el juego de un entorno.
|
|
173
|
+
return { version: 2, porEntorno: { [CLAVE_LEGADO]: objeto } };
|
|
174
|
+
}
|
|
175
|
+
if (objeto.version !== 2)
|
|
176
|
+
return ESTADO_VACIO; // formato futuro/desconocido: fail-closed, no se inventa
|
|
177
|
+
return { version: 2, porEntorno: porEntornoValido(objeto.porEntorno) };
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Un fichero corrupto no impide volver a autenticar; nunca se imprime su contenido.
|
|
181
|
+
return ESTADO_VACIO;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Un fichero de una versión futura y desconocida no se puede sobrescribir a ciegas: este
|
|
186
|
+
* código no sabe qué juegos de credenciales lleva dentro, y `leerEstado` lo trata como
|
|
187
|
+
* vacío para no tumbar un arranque de solo lectura. Escribir SOBRE ese vacío destruiría
|
|
188
|
+
* lo que hubiera. Un JSON corrupto (parse roto) es harina de otro costal —ya se
|
|
189
|
+
* sobrescribía así antes de que existieran los entornos, es el comportamiento actual que
|
|
190
|
+
* el enunciado pide no tocar— así que solo se bloquea la versión reconocible pero
|
|
191
|
+
* distinta de 2, nunca el texto irrecuperable.
|
|
192
|
+
*/
|
|
193
|
+
export class EstadoOAuthVersionIncompatible extends Error {
|
|
194
|
+
}
|
|
195
|
+
function comprobarVersionEscribible(ruta) {
|
|
196
|
+
if (!existsSync(ruta))
|
|
197
|
+
return;
|
|
198
|
+
let bruto;
|
|
199
|
+
try {
|
|
200
|
+
bruto = JSON.parse(readFileSync(ruta, "utf8"));
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return; // corrupto: se sobrescribe, como ya hacía antes de los entornos.
|
|
204
|
+
}
|
|
205
|
+
if (typeof bruto !== "object" || bruto === null || Array.isArray(bruto))
|
|
206
|
+
return;
|
|
207
|
+
const version = bruto.version;
|
|
208
|
+
if (version !== undefined && version !== 2) {
|
|
209
|
+
throw new EstadoOAuthVersionIncompatible(`${ruta}: versión de estado OAuth desconocida (${JSON.stringify(version)}); no se sobrescribe`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function guardarEstado(ruta, estado) {
|
|
213
|
+
mkdirSync(dirname(ruta), { recursive: true, mode: 0o700 });
|
|
214
|
+
const temporal = `${ruta}.${randomUUID()}.tmp`;
|
|
215
|
+
let fd;
|
|
216
|
+
try {
|
|
217
|
+
fd = openSync(temporal, "wx", 0o600);
|
|
218
|
+
writeFileSync(fd, JSON.stringify(estado, null, 2) + "\n", "utf8");
|
|
219
|
+
closeSync(fd);
|
|
220
|
+
fd = undefined;
|
|
221
|
+
renameSync(temporal, ruta);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
if (fd !== undefined)
|
|
225
|
+
closeSync(fd);
|
|
226
|
+
try {
|
|
227
|
+
unlinkSync(temporal);
|
|
228
|
+
}
|
|
229
|
+
catch { /* no llegó a crearse */ }
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Sustituye el juego de credenciales de UN entorno y deja los demás tal cual estaban:
|
|
235
|
+
* lee el fichero entero, reemplaza solo `porEntorno[id]` y reescribe con la misma
|
|
236
|
+
* mecánica de temporal + `rename` de `guardarEstado` — es la que asegura 0600/0700 y que
|
|
237
|
+
* un fallo a mitad de escritura no deja el fichero truncado.
|
|
238
|
+
*/
|
|
239
|
+
export function guardarEstadoDeEntorno(ruta, id, datos) {
|
|
240
|
+
comprobarVersionEscribible(ruta);
|
|
241
|
+
const estado = leerEstado(ruta);
|
|
242
|
+
guardarEstado(ruta, { version: 2, porEntorno: { ...estado.porEntorno, [id]: datos } });
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Cierra sesión en UN entorno. Si no tenía nada guardado no hay nada que tocar: escribir
|
|
246
|
+
* de todos modos reescribiría el fichero entero (y su mtime) sin que haya cambiado nada
|
|
247
|
+
* observable.
|
|
248
|
+
*/
|
|
249
|
+
export function olvidarEntorno(ruta, id) {
|
|
250
|
+
comprobarVersionEscribible(ruta);
|
|
251
|
+
const estado = leerEstado(ruta);
|
|
252
|
+
if (!(id in estado.porEntorno))
|
|
253
|
+
return;
|
|
254
|
+
const { [id]: _omitido, ...resto } = estado.porEntorno;
|
|
255
|
+
guardarEstado(ruta, { version: 2, porEntorno: resto });
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Adopta `legado` como el juego del entorno recién registrado, y SOLO si su URL es la
|
|
259
|
+
* oficial por omisión — es la única URL que el fichero plano pudo haber usado sin que el
|
|
260
|
+
* usuario tecleara una distinta a mano (ver el comentario de `EstadoOAuth`). Si `legado`
|
|
261
|
+
* no existe, o ya hay algo guardado bajo `entorno.id`, no hace nada: no hay nada que
|
|
262
|
+
* migrar en el primer caso, y pisar un juego ya autenticado sería peor que dejar el
|
|
263
|
+
* plano huérfano en el segundo.
|
|
264
|
+
*/
|
|
265
|
+
export function adoptarLegadoSiProcede(ruta, entorno) {
|
|
266
|
+
if (entorno.url !== URL_CLOUDSTUDIO_POR_OMISION)
|
|
267
|
+
return;
|
|
268
|
+
comprobarVersionEscribible(ruta);
|
|
269
|
+
const estado = leerEstado(ruta);
|
|
270
|
+
const legado = estado.porEntorno[CLAVE_LEGADO];
|
|
271
|
+
if (legado === undefined || entorno.id in estado.porEntorno)
|
|
272
|
+
return;
|
|
273
|
+
const { [CLAVE_LEGADO]: _omitido, ...resto } = estado.porEntorno;
|
|
274
|
+
guardarEstado(ruta, { version: 2, porEntorno: { ...resto, [entorno.id]: legado } });
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Abre una URL en el navegador del sistema. Exportada porque la consola web hace lo mismo
|
|
278
|
+
* al arrancar (`web/servidor/arranque.ts`) y tres `spawn` con la misma tabla de comandos
|
|
279
|
+
* en dos ficheros es cómo divergen.
|
|
280
|
+
*
|
|
281
|
+
* El `on("error")` no es decorativo: `spawn` de un comando que no existe —un contenedor
|
|
282
|
+
* sin `xdg-open`, un SSH sin escritorio— no lanza, EMITE `error`, y sin escucha eso es una
|
|
283
|
+
* excepción no capturada que se lleva el proceso por delante. Aquí abrir el navegador es
|
|
284
|
+
* siempre lo ACCESORIO: la URL ya está impresa, y el servidor tiene que seguir en pie.
|
|
285
|
+
*/
|
|
286
|
+
export function abrirEnSistema(url) {
|
|
287
|
+
const [comando, args] = process.platform === "darwin"
|
|
288
|
+
? ["open", [url.toString()]]
|
|
289
|
+
: process.platform === "win32"
|
|
290
|
+
? ["cmd", ["/c", "start", "", url.toString()]]
|
|
291
|
+
: ["xdg-open", [url.toString()]];
|
|
292
|
+
const proceso = spawn(comando, args, { detached: true, stdio: "ignore" });
|
|
293
|
+
proceso.on("error", () => { });
|
|
294
|
+
proceso.unref();
|
|
295
|
+
}
|
|
296
|
+
export const AVISO_DE_URL_DE_MCP = "la URL del MCP debe ser HTTPS —solo se admite http:// en 127.0.0.1 o localhost, para un on-premise en desarrollo— y no puede incluir credenciales";
|
|
297
|
+
/**
|
|
298
|
+
* La ÚNICA regla de qué URL de MCP vale, y por eso vive aquí y no en cada gate.
|
|
299
|
+
*
|
|
300
|
+
* Había tres puertas con dos criterios: el wizard del navegador aceptaba loopback,
|
|
301
|
+
* `vestibulo.ts#urlDeEntornoValida` exigía HTTPS y lo rechazaba, y esta función —la última,
|
|
302
|
+
* la que de verdad conecta— también. Registrar un entorno on-premise de desarrollo era
|
|
303
|
+
* imposible por la segunda, y aflojar solo la segunda habría dejado registrar uno que
|
|
304
|
+
* después fallaba al primer `conectarCloudStudio`: un alta que se puede escribir y no se
|
|
305
|
+
* puede usar es peor que el rechazo claro de hoy.
|
|
306
|
+
*
|
|
307
|
+
* Se resuelve por el lado PERMISIVO porque el caso existe —un CloudStudio on-premise
|
|
308
|
+
* levantado en local durante el desarrollo— y porque en loopback el texto plano no cruza
|
|
309
|
+
* ninguna red: es el mismo trato que ya se da al `redirect_uri` del callback de OAuth
|
|
310
|
+
* (`http://127.0.0.1:7634`) y a la propia consola web. Fuera de loopback, HTTPS y nada más.
|
|
311
|
+
* La copia del cliente (`apps/web/src/componentes/Wizard.tsx#urlDeEntornoAceptable`) dice
|
|
312
|
+
* lo mismo y no puede importar esta: lo prohíbe `src/web/frontera.test.ts`.
|
|
313
|
+
*/
|
|
314
|
+
export function urlDeMcpAceptable(valor) {
|
|
315
|
+
// Delega en `core/`, que es donde vive la regla desde que los proveedores de modelos
|
|
316
|
+
// personalizados necesitaron la misma: HTTPS fuera de la máquina, `http://` solo en
|
|
317
|
+
// loopback, y nunca credenciales dentro de la URL (acabaría en `settings.json` y en cada
|
|
318
|
+
// traza que la enseñe). Dos copias de esto habrían divergido el día que una de las dos
|
|
319
|
+
// aflojara — que es exactamente cómo llegó a haber tres puertas con dos criterios.
|
|
320
|
+
return motivoDeEndpointInaceptable(valor) === undefined;
|
|
321
|
+
}
|
|
322
|
+
function urlSegura(valor) {
|
|
323
|
+
if (!urlDeMcpAceptable(valor))
|
|
324
|
+
throw new Error(AVISO_DE_URL_DE_MCP);
|
|
325
|
+
return new URL(valor);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Qué se le contesta al navegador que vuelve del IDS.
|
|
329
|
+
*
|
|
330
|
+
* Sale del servidor y se prueba aparte porque montarlo exige el puerto 7634 —fijo, porque
|
|
331
|
+
* el IDS registra ese `redirect_uri`— y ahí no se puede escoger uno efímero: un test que
|
|
332
|
+
* atara el puerto real fallaría en cuanto alguien tuviera una autorización en curso.
|
|
333
|
+
*/
|
|
334
|
+
export function respuestaDeCallback(codigo, error, redirigirA) {
|
|
335
|
+
if (error || !codigo) {
|
|
336
|
+
// Sin redirección tampoco en modo web: la web aún no sabe del fallo (se entera por la
|
|
337
|
+
// promesa rechazada) y mandarla allí escondería el motivo. El texto no nombra «la
|
|
338
|
+
// consola» porque quien llega aquí puede estar usando el navegador.
|
|
339
|
+
return {
|
|
340
|
+
estado: 400,
|
|
341
|
+
cabeceras: { "content-type": "text/html; charset=utf-8" },
|
|
342
|
+
cuerpo: "<h1>No se pudo completar el acceso a CloudStudio</h1><p>Vuelve a xonecode para ver el detalle.</p>",
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (redirigirA !== undefined) {
|
|
346
|
+
return { estado: 302, cabeceras: { location: redirigirA }, cuerpo: "" };
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
estado: 200,
|
|
350
|
+
cabeceras: { "content-type": "text/html; charset=utf-8" },
|
|
351
|
+
cuerpo: "<h1>CloudStudio conectado</h1><p>Ya puedes volver a xonecode.</p>",
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* La URL de callback es determinista —`PUERTO_CALLBACK` es fijo, porque el IDS registra
|
|
356
|
+
* ese `redirect_uri`— así que conocerla no exige que el servidor esté escuchando. El
|
|
357
|
+
* `provider` la necesita SIEMPRE, la escuche `auth()` o no: la lleva en su
|
|
358
|
+
* `clientMetadata` para el registro/descubrimiento aunque el token guardado sea válido y
|
|
359
|
+
* `auth()` nunca llegue a redirigir. Separada de `escucharCallback` (abajo) justo por
|
|
360
|
+
* eso: construir la URL no es un efecto colateral, EMPEZAR A ESCUCHAR sí lo es.
|
|
361
|
+
*/
|
|
362
|
+
function urlDeCallback() {
|
|
363
|
+
return `http://127.0.0.1:${PUERTO_CALLBACK}/oauth/callback`;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Arranca el servidor HTTP local que recibe el `code` de la redirección, con su
|
|
367
|
+
* temporizador de espera. Se llama solo cuando `auth()` YA dijo `REDIRECT` —antes se
|
|
368
|
+
* llamaba SIEMPRE, incluso con un token guardado y válido que no iba a redirigir nunca—:
|
|
369
|
+
* MEDIDO, eso ocupaba el puerto 7634 fijo y armaba un temporizador de cinco minutos en
|
|
370
|
+
* cada llamada a `abrirCliente`, y dos llamadas seguidas o solapadas (dos conexiones del
|
|
371
|
+
* vestíbulo listando proyectos del mismo entorno, por ejemplo) chocaban por el mismo
|
|
372
|
+
* puerto sin que hiciera falta ningún login de verdad.
|
|
373
|
+
*/
|
|
374
|
+
function escucharCallback(timeoutMs, redirigirA) {
|
|
375
|
+
return new Promise((resolver, rechazar) => {
|
|
376
|
+
let resolverCodigo;
|
|
377
|
+
let rechazarCodigo;
|
|
378
|
+
const codigo = new Promise((ok, fallo) => {
|
|
379
|
+
resolverCodigo = ok;
|
|
380
|
+
rechazarCodigo = fallo;
|
|
381
|
+
});
|
|
382
|
+
const servidor = createServer((peticion, respuesta) => {
|
|
383
|
+
const recibida = new URL(peticion.url ?? "/", "http://127.0.0.1");
|
|
384
|
+
const codigoRecibido = recibida.searchParams.get("code");
|
|
385
|
+
const error = recibida.searchParams.get("error");
|
|
386
|
+
const contestacion = respuestaDeCallback(codigoRecibido, error, redirigirA);
|
|
387
|
+
respuesta.writeHead(contestacion.estado, contestacion.cabeceras);
|
|
388
|
+
respuesta.end(contestacion.cuerpo);
|
|
389
|
+
limpiar();
|
|
390
|
+
if (error || !codigoRecibido)
|
|
391
|
+
rechazarCodigo(new Error(`el IDS rechazó el acceso${error ? `: ${error}` : ""}`));
|
|
392
|
+
else
|
|
393
|
+
resolverCodigo(codigoRecibido);
|
|
394
|
+
});
|
|
395
|
+
const temporizador = setTimeout(() => {
|
|
396
|
+
limpiar();
|
|
397
|
+
rechazarCodigo(new Error("se agotó la espera del login de CloudStudio"));
|
|
398
|
+
}, timeoutMs);
|
|
399
|
+
const limpiar = () => {
|
|
400
|
+
clearTimeout(temporizador);
|
|
401
|
+
servidor.close();
|
|
402
|
+
};
|
|
403
|
+
servidor.once("error", (error) => {
|
|
404
|
+
limpiar();
|
|
405
|
+
rechazar(new Error(`no se pudo abrir el callback local de OAuth: ${error.message}`));
|
|
406
|
+
});
|
|
407
|
+
servidor.listen(PUERTO_CALLBACK, "127.0.0.1", () => {
|
|
408
|
+
resolver({ codigo, cerrar: limpiar });
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/** Provider OAuth persistente, deliberadamente pequeño y compatible con el SDK MCP. */
|
|
413
|
+
/**
|
|
414
|
+
* Exportada SOLO para poder probar `invalidateCredentials` —el gancho del que depende que
|
|
415
|
+
* el SDK sepa recuperarse de un token muerto— sin red ni navegador. Nadie fuera de este
|
|
416
|
+
* módulo la construye para usarla: quien abre sesión llama a `sesionCloudStudio` o a
|
|
417
|
+
* `conectarCloudStudio`, que la montan por dentro.
|
|
418
|
+
*/
|
|
419
|
+
export class ProviderCloudStudio {
|
|
420
|
+
ruta;
|
|
421
|
+
entornoId;
|
|
422
|
+
alRedirigir;
|
|
423
|
+
scopes;
|
|
424
|
+
estado;
|
|
425
|
+
metadata;
|
|
426
|
+
constructor(ruta,
|
|
427
|
+
/** El entorno cuyo juego de credenciales lee y escribe este provider: opera SOLO
|
|
428
|
+
* sobre `porEntorno[entornoId]`, nunca sobre el fichero entero, para que abrir
|
|
429
|
+
* sesión en un entorno no pueda pisar el juego de otro. */
|
|
430
|
+
entornoId, redirectUrl, alRedirigir, scopes) {
|
|
431
|
+
this.ruta = ruta;
|
|
432
|
+
this.entornoId = entornoId;
|
|
433
|
+
this.alRedirigir = alRedirigir;
|
|
434
|
+
this.scopes = scopes;
|
|
435
|
+
this.estado = leerEstado(ruta).porEntorno[entornoId] ?? {};
|
|
436
|
+
// Un refresh token de solo lectura no sirve para elevar privilegios. Borrarlo aquí
|
|
437
|
+
// fuerza Authorization Code + consentimiento nuevo en lugar de fingir una elevación.
|
|
438
|
+
const concedidos = new Set(this.estado.scopes ?? (this.estado.tokens?.scope ?? "").split(/\s+/));
|
|
439
|
+
if (this.estado.tokens !== undefined && !this.scopes.every((scope) => concedidos.has(scope))) {
|
|
440
|
+
delete this.estado.tokens;
|
|
441
|
+
this.guardar();
|
|
442
|
+
}
|
|
443
|
+
this.metadata = {
|
|
444
|
+
client_name: "xonecode",
|
|
445
|
+
client_uri: "https://github.com/xonecode/xonecode",
|
|
446
|
+
redirect_uris: [redirectUrl],
|
|
447
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
448
|
+
response_types: ["code"],
|
|
449
|
+
// Mínimo privilegio: conectar y descubrir tools no necesita escribir ni ejecutar.
|
|
450
|
+
scope: scopes.join(" "),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/** Solo esta clave se toca en el fichero, nunca `porEntorno` entero: es lo que hace
|
|
454
|
+
* que abrir sesión en un entorno deje intactos los tokens de los demás. */
|
|
455
|
+
guardar() { guardarEstadoDeEntorno(this.ruta, this.entornoId, this.estado); }
|
|
456
|
+
get redirectUrl() { return this.metadata.redirect_uris[0]; }
|
|
457
|
+
get clientMetadata() { return this.metadata; }
|
|
458
|
+
clientInformation() { return this.estado.clientInformation; }
|
|
459
|
+
saveClientInformation(info) { this.estado.clientInformation = info; this.guardar(); }
|
|
460
|
+
tokens() { return this.estado.tokens; }
|
|
461
|
+
saveTokens(tokens) {
|
|
462
|
+
this.estado.tokens = tokens;
|
|
463
|
+
this.estado.scopes = [...this.scopes];
|
|
464
|
+
this.guardar();
|
|
465
|
+
}
|
|
466
|
+
redirectToAuthorization(url) { this.alRedirigir(url); }
|
|
467
|
+
/**
|
|
468
|
+
* El gancho de recuperación del SDK, y no un extra: `auth()` (`client/auth.js`) atrapa
|
|
469
|
+
* `InvalidGrantError` —el refresh token muerto— e `InvalidClientError`, llama a esto y
|
|
470
|
+
* REINTENTA el flujo entero. Sin implementarlo, esa llamada es un no-op: el SDK reintenta
|
|
471
|
+
* con las mismas credenciales podridas, vuelve a fallar y lanza. O sea que un token
|
|
472
|
+
* caducado sin refresco válido era un fallo duro —«hay que borrar el fichero a mano»—
|
|
473
|
+
* cuando el propio SDK sabía volver a autenticar solo.
|
|
474
|
+
*
|
|
475
|
+
* Cada alcance borra LO SUYO y nada más, y se escribe en el acto: lo que se está
|
|
476
|
+
* arreglando es justo un estado a medias, y dejarlo en memoria sin guardar significaría
|
|
477
|
+
* que el siguiente arranque lo vuelve a leer del fichero.
|
|
478
|
+
*/
|
|
479
|
+
invalidateCredentials(alcance) {
|
|
480
|
+
if (alcance === "all") {
|
|
481
|
+
// Todo menos el hueco: el entorno sigue existiendo, lo que se va son sus credenciales.
|
|
482
|
+
this.estado = {};
|
|
483
|
+
}
|
|
484
|
+
else if (alcance === "tokens") {
|
|
485
|
+
delete this.estado.tokens;
|
|
486
|
+
// Los scopes concedidos van CON el token: conservarlos haría creer al constructor
|
|
487
|
+
// que ya hay permiso concedido para unos tokens que ya no existen.
|
|
488
|
+
delete this.estado.scopes;
|
|
489
|
+
}
|
|
490
|
+
else if (alcance === "client") {
|
|
491
|
+
delete this.estado.clientInformation;
|
|
492
|
+
}
|
|
493
|
+
else if (alcance === "verifier") {
|
|
494
|
+
delete this.estado.codeVerifier;
|
|
495
|
+
}
|
|
496
|
+
// `discovery` no tiene nada que borrar aquí: este provider no cachea el descubrimiento
|
|
497
|
+
// (no implementa `discoveryState`), así que el SDK lo rehace solo.
|
|
498
|
+
this.guardar();
|
|
499
|
+
}
|
|
500
|
+
saveCodeVerifier(verifier) { this.estado.codeVerifier = verifier; this.guardar(); }
|
|
501
|
+
codeVerifier() {
|
|
502
|
+
if (!this.estado.codeVerifier)
|
|
503
|
+
throw new Error("no hay verificador PKCE para el login de CloudStudio");
|
|
504
|
+
return this.estado.codeVerifier;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* El servidor pierde el proyecto abierto al caducar la sesión; lo dice con este texto
|
|
509
|
+
* (medido). Duplicado a propósito del mismo patrón en `agent/cloudstudioClient.ts`: es
|
|
510
|
+
* la señal, no un detalle de transporte, y ninguno de los dos módulos importa del otro.
|
|
511
|
+
*/
|
|
512
|
+
const SESION_PERDIDA = /no project is open/i;
|
|
513
|
+
/** Tools cuyo `isError` puede traer de vuelta nuestro propio contenido: un rechazo de
|
|
514
|
+
* `studio_edit_file` en modo `patch`, o de `studio_upload_file`, puede incluir el
|
|
515
|
+
* fragmento o fichero que se intentó mandar. Nunca se reenvía ese cuerpo. */
|
|
516
|
+
const TOOLS_DE_ESCRITURA = new Set(["studio_edit_file", "studio_upload_file"]);
|
|
517
|
+
/** Cuánto de la primera línea de un error se conserva. Basta para «File extension
|
|
518
|
+
* '.jpg' is not allowed…»; no basta para un volcado de fichero entero. */
|
|
519
|
+
const TOPE_MENSAJE_ERROR = 200;
|
|
520
|
+
/** El texto de un error de TOOL (`isError`), tal cual lo manda el servidor. Puede
|
|
521
|
+
* incluir identidad («…not found for user 'sleiva@xone.es'») o, en una tool de
|
|
522
|
+
* escritura, el propio contenido rechazado: por eso `mensajeDeErrorDeTool` decide
|
|
523
|
+
* cuánto de esto sale de aquí, nunca esta función. */
|
|
524
|
+
function textoDeErrorDeTool(resultado) {
|
|
525
|
+
const contenido = typeof resultado === "object" && resultado !== null
|
|
526
|
+
? resultado.content
|
|
527
|
+
: undefined;
|
|
528
|
+
if (Array.isArray(contenido)) {
|
|
529
|
+
const texto = contenido
|
|
530
|
+
.filter((b) => typeof b === "object" && b !== null && b.type === "text")
|
|
531
|
+
.map((b) => b.text)
|
|
532
|
+
.join(" ")
|
|
533
|
+
.trim();
|
|
534
|
+
if (texto !== "")
|
|
535
|
+
return texto;
|
|
536
|
+
}
|
|
537
|
+
return "CloudStudio devolvió un error sin detalle";
|
|
538
|
+
}
|
|
539
|
+
/** La ruta del fichero de la llamada, cuando la tool la lleva en `filePath`. Solo para
|
|
540
|
+
* nombrar el error; nunca su contenido. */
|
|
541
|
+
function rutaDe(argumentos) {
|
|
542
|
+
return typeof argumentos.filePath === "string" ? argumentos.filePath : undefined;
|
|
543
|
+
}
|
|
544
|
+
/** La primera línea, acotada. Un texto de una sola línea corta pasa intacto. */
|
|
545
|
+
function primeraLineaAcotada(texto) {
|
|
546
|
+
const primeraLinea = texto.split(/\r?\n/, 1)[0] ?? "";
|
|
547
|
+
return primeraLinea.length > TOPE_MENSAJE_ERROR
|
|
548
|
+
? `${primeraLinea.slice(0, TOPE_MENSAJE_ERROR)}…`
|
|
549
|
+
: primeraLinea;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* El mensaje final de la excepción: nombra siempre la tool, para que el error siga
|
|
553
|
+
* sirviendo para diagnosticar, pero decide cuánto del cuerpo del servidor deja pasar.
|
|
554
|
+
*
|
|
555
|
+
* La caducidad de sesión es la excepción a la excepción: es un texto fijo y corto —
|
|
556
|
+
* nunca contenido de fichero—, y es la señal de la que depende toda la reapertura en
|
|
557
|
+
* `clienteCloudStudio`, incluida la de las tools de escritura. Redactar el cuerpo ahí
|
|
558
|
+
* dejaría un `escribirTexto`/`subirBinario` con la sesión caducada sin forma de
|
|
559
|
+
* reabrir y reintentar.
|
|
560
|
+
*/
|
|
561
|
+
function mensajeDeErrorDeTool(nombre, argumentos, resultado) {
|
|
562
|
+
const detalle = textoDeErrorDeTool(resultado);
|
|
563
|
+
if (SESION_PERDIDA.test(detalle))
|
|
564
|
+
return `${nombre}: ${primeraLineaAcotada(detalle)}`;
|
|
565
|
+
if (TOOLS_DE_ESCRITURA.has(nombre)) {
|
|
566
|
+
const ruta = rutaDe(argumentos);
|
|
567
|
+
// Sin el cuerpo: en una tool de escritura ese cuerpo puede ser nuestro propio
|
|
568
|
+
// fichero (o el fragmento de un patch) rebotando.
|
|
569
|
+
return `CloudStudio rechazó ${nombre}${ruta !== undefined ? ` (${ruta})` : ""}`;
|
|
570
|
+
}
|
|
571
|
+
return `${nombre}: ${primeraLineaAcotada(detalle)}`;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Envuelve un `callTool` crudo del SDK en la función `invocar` que espera
|
|
575
|
+
* `clienteCloudStudio`. Separada de `sesionCloudStudio` para poder probar la conversión
|
|
576
|
+
* `isError → excepción` con un `callTool` falso, sin tocar OAuth ni el transporte: es la
|
|
577
|
+
* pieza de la que depende toda la reapertura de sesión en `agent/cloudstudioClient.ts`,
|
|
578
|
+
* y una implementación que se limitara a `return callTool(...)` pasaría los tests de
|
|
579
|
+
* `clienteCloudStudio` igual de mal que los de aquí bien, si esto no se probara aparte.
|
|
580
|
+
*/
|
|
581
|
+
export function invocarSobre(callTool) {
|
|
582
|
+
return async (nombre, argumentos) => {
|
|
583
|
+
const resultado = await callTool({ name: nombre, arguments: argumentos });
|
|
584
|
+
// El SDK MCP no lanza cuando la TOOL falla (`isError: true` es una respuesta RPC
|
|
585
|
+
// válida, con el motivo dentro de `content`); sin convertirlo en excepción, «No
|
|
586
|
+
// project is open» nunca llegaría a un catch y `clienteCloudStudio` no podría
|
|
587
|
+
// reabrir la sesión.
|
|
588
|
+
if (typeof resultado === "object" && resultado !== null && resultado.isError) {
|
|
589
|
+
throw new Error(mensajeDeErrorDeTool(nombre, argumentos, resultado));
|
|
590
|
+
}
|
|
591
|
+
return resultado;
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Abre (o reutiliza) OAuth y conecta el transporte MCP. Es la base común de
|
|
596
|
+
* `sesionCloudStudio` y `conectarCloudStudio`: la segunda necesita además
|
|
597
|
+
* `listTools()`, que no es una tool invocable y por eso no cabe en `{ invocar, cerrar }`.
|
|
598
|
+
*
|
|
599
|
+
* En error se cierra el transporte aquí mismo, porque nadie más llega a recibir el
|
|
600
|
+
* `cerrar()` que lo liberaría; en éxito la sesión queda VIVA a propósito, y es quien
|
|
601
|
+
* llama quien decide cuándo cerrarla.
|
|
602
|
+
*/
|
|
603
|
+
async function abrirCliente(urlTexto, opciones) {
|
|
604
|
+
const url = urlSegura(urlTexto);
|
|
605
|
+
const scopes = opciones.scopes ?? SCOPES_CLOUDSTUDIO_AGENTE;
|
|
606
|
+
const ruta = opciones.rutaAuth ?? rutaAuthPorDefecto();
|
|
607
|
+
const provider = new ProviderCloudStudio(ruta, opciones.entornoId ?? CLAVE_LEGADO, urlDeCallback(), (autorizacion) => {
|
|
608
|
+
// La URL de autorización no aporta nada al transcript normal y puede tener parámetros
|
|
609
|
+
// sensibles de OAuth. El navegador se abre sin volcarla; quien use SSH puede inyectar
|
|
610
|
+
// `abrirNavegador` y decidir cómo presentarla.
|
|
611
|
+
(opciones.abrirNavegador ?? abrirEnSistema)(autorizacion);
|
|
612
|
+
}, scopes);
|
|
613
|
+
let transporte;
|
|
614
|
+
// Solo se rellena si `auth()` de verdad redirige — ver `escucharCallback` arriba.
|
|
615
|
+
let callback;
|
|
616
|
+
try {
|
|
617
|
+
// No se usa `client.connect()` para iniciar OAuth: ese transporte queda marcado como
|
|
618
|
+
// iniciado incluso cuando redirige y no admite un segundo connect. `auth()` realiza
|
|
619
|
+
// primero el descubrimiento/PKCE y solo se crea el transporte DESPUÉS del callback.
|
|
620
|
+
const opcionesAuth = {
|
|
621
|
+
serverUrl: url,
|
|
622
|
+
resourceMetadataUrl: new URL("/.well-known/oauth-protected-resource", url.origin),
|
|
623
|
+
scope: scopes.join(" "),
|
|
624
|
+
};
|
|
625
|
+
const inicial = await auth(provider, opcionesAuth);
|
|
626
|
+
if (inicial === "REDIRECT") {
|
|
627
|
+
callback = await escucharCallback(opciones.timeoutMs ?? 5 * 60_000, opciones.redirigirA);
|
|
628
|
+
const codigo = await callback.codigo;
|
|
629
|
+
const final = await auth(provider, { ...opcionesAuth, authorizationCode: codigo });
|
|
630
|
+
if (final !== "AUTHORIZED")
|
|
631
|
+
throw new Error("el IDS no completó la autorización de CloudStudio");
|
|
632
|
+
}
|
|
633
|
+
const cliente = new Client({ name: "xonecode", version: VERSION });
|
|
634
|
+
transporte = new StreamableHTTPClientTransport(url, { authProvider: provider });
|
|
635
|
+
await cliente.connect(transporte);
|
|
636
|
+
const transporteVivo = transporte;
|
|
637
|
+
return { cliente, url, cerrar: () => transporteVivo.close() };
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
await transporte?.close();
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
finally {
|
|
644
|
+
callback?.cerrar();
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Sesión MCP viva contra CloudStudio, para `clienteCloudStudio`
|
|
649
|
+
* (`agent/cloudstudioClient.ts`): descarga, escribe y cambia de rama necesitan una
|
|
650
|
+
* conexión que sobreviva a más de una llamada, cosa que `conectarCloudStudio` no ofrece
|
|
651
|
+
* porque cierra el transporte en cuanto termina de mirar el catálogo.
|
|
652
|
+
*/
|
|
653
|
+
export async function sesionCloudStudio(urlTexto, opciones = {}) {
|
|
654
|
+
const { cliente, cerrar } = await abrirCliente(urlTexto, opciones);
|
|
655
|
+
return {
|
|
656
|
+
invocar: invocarSobre((peticion) => cliente.callTool(peticion)),
|
|
657
|
+
cerrar,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
/** Lo más largo que se acepta de un nombre que viene del OTRO lado. Cabe cualquier nombre
|
|
661
|
+
* real de servidor y no cabe un párrafo. */
|
|
662
|
+
const TOPE_NOMBRE_DE_SERVIDOR = 60;
|
|
663
|
+
/**
|
|
664
|
+
* El nombre del servidor MCP, saneado, o `undefined` si no publica ninguno usable.
|
|
665
|
+
*
|
|
666
|
+
* Es texto que llega de fuera y acaba en `settings.json` y en la barra lateral, así que
|
|
667
|
+
* pasa por la misma disciplina que cualquier otro dato remoto de este fichero: se quita lo
|
|
668
|
+
* que no es imprimible (un `\n` o un `\r` en un nombre parte la línea de un log y disfraza
|
|
669
|
+
* lo que venga detrás), se colapsan los espacios y se acota. Lo que NUNCA sale de aquí es
|
|
670
|
+
* un identificador: el id del entorno se deduce del host y no de esto, porque el id es un
|
|
671
|
+
* segmento de ruta y el servidor no puede elegir carpetas en el disco de nadie.
|
|
672
|
+
*
|
|
673
|
+
* `title` antes que `name` porque es la convención de MCP: `title` es el nombre para leer y
|
|
674
|
+
* `name` el programático (`xone-cloudstudio`). Si no hay `title`, `name` es mejor que nada.
|
|
675
|
+
*/
|
|
676
|
+
export function servidorDeImplementacion(info) {
|
|
677
|
+
const crudo = [info?.title, info?.name].find((v) => typeof v === "string" && v.trim() !== "");
|
|
678
|
+
if (typeof crudo !== "string")
|
|
679
|
+
return undefined;
|
|
680
|
+
const nombre = crudo
|
|
681
|
+
// eslint-disable-next-line no-control-regex
|
|
682
|
+
.replace(/[\u0000-\u001f\u007f]+/g, " ")
|
|
683
|
+
.replace(/\s+/g, " ")
|
|
684
|
+
.trim()
|
|
685
|
+
.slice(0, TOPE_NOMBRE_DE_SERVIDOR);
|
|
686
|
+
if (nombre === "")
|
|
687
|
+
return undefined;
|
|
688
|
+
const version = typeof info?.version === "string" ? info.version.trim().slice(0, TOPE_NOMBRE_DE_SERVIDOR) : "";
|
|
689
|
+
return { nombre, ...(version === "" ? {} : { version }) };
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Inicia (o reutiliza) OAuth, completa initialize y lista las tools disponibles.
|
|
693
|
+
* La conexión de tools al grafo vendrá después: esta función prueba la conexión sin
|
|
694
|
+
* incorporar los esquemas de todas las tools al prompt del agente.
|
|
695
|
+
*/
|
|
696
|
+
export async function conectarCloudStudio(urlTexto, opciones = {}) {
|
|
697
|
+
const scopes = opciones.scopes ?? SCOPES_CLOUDSTUDIO_AGENTE;
|
|
698
|
+
const { cliente, url, cerrar } = await abrirCliente(urlTexto, opciones);
|
|
699
|
+
try {
|
|
700
|
+
const listado = await cliente.listTools();
|
|
701
|
+
const definicion = herramientaDeProyectos(listado.tools);
|
|
702
|
+
if (definicion === undefined) {
|
|
703
|
+
const disponibles = listado.tools.map((tool) => tool.name).filter(Boolean).join(", ");
|
|
704
|
+
throw new Error(`CloudStudio no publicó ninguna herramienta de listado de proyectos${disponibles ? ` (disponibles: ${disponibles})` : ""}`);
|
|
705
|
+
}
|
|
706
|
+
const proyectos = proyectosDeResultado(await cliente.callTool({ name: definicion.name, arguments: {} }));
|
|
707
|
+
// El `serverInfo` del initialize, ya saneado. Se lee DESPUÉS de `connect`, que es
|
|
708
|
+
// cuando el SDK lo tiene; antes devuelve `undefined`.
|
|
709
|
+
const servidor = servidorDeImplementacion(cliente.getServerVersion());
|
|
710
|
+
return {
|
|
711
|
+
url: url.toString(),
|
|
712
|
+
scopes,
|
|
713
|
+
herramientas: listado.tools.map((tool) => ({ nombre: tool.name, descripcion: tool.description ?? "" })),
|
|
714
|
+
proyectos,
|
|
715
|
+
...(servidor === undefined ? {} : { servidor }),
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
finally {
|
|
719
|
+
await cerrar();
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
//# sourceMappingURL=cloudstudioMcp.js.map
|