chore: sync workspace state

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-04-02 13:38:38 -03:00
parent ed2052c8ec
commit 3a6a571361
38 changed files with 3978 additions and 620 deletions

1
.gitignore vendored
View file

@ -76,3 +76,4 @@ errors_utf8.txt
vault-unseal-keysdefault-vault.tar
prompts/sec-bots.md
AGENTS-PROD.md

View file

@ -0,0 +1,37 @@
- generic [ref=e5]:
- generic [ref=e6]:
- generic [ref=e7]: Chat
- generic [ref=e8]:
- button [ref=e9] [cursor=pointer]:
- img [ref=e10]
- button [ref=e11] [cursor=pointer]:
- img [ref=e12]
- button [ref=e14] [cursor=pointer]:
- img [ref=e15]
- generic [ref=e18]:
- generic [ref=e19]:
- main [ref=e20]:
- paragraph [ref=e31]: Olá! Sou o assistente virtual do Santuário Cristo Redentor. Como posso ajudá-lo hoje com informações sobre celebrações, eventos, visitação ou orações?
- contentinfo [ref=e21]:
- generic [ref=e22]:
- button "Agendar Batizado" [ref=e32] [cursor=pointer]
- button "Agendar Casamento" [ref=e33] [cursor=pointer]
- button "Agendar Missa" [ref=e34] [cursor=pointer]
- button "Agendar Peregrinação" [ref=e35] [cursor=pointer]
- button "Pedido de Oração" [ref=e36] [cursor=pointer]
- button "Uso de Imagem" [ref=e37] [cursor=pointer]
- button "Licenciamento" [ref=e38] [cursor=pointer]
- button "Evento/Iluminação" [ref=e39] [cursor=pointer]
- button "Cadastrar Guia" [ref=e40] [cursor=pointer]
- button "Fazer Doação" [ref=e41] [cursor=pointer]
- generic [ref=e23]:
- generic [ref=e24]:
- button "Agent" [ref=e25] [cursor=pointer]
- button "Chat" [ref=e26] [cursor=pointer]
- textbox "Message... (type @ to mention)" [active] [ref=e27]
- button "↑" [ref=e28] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"

View file

@ -0,0 +1,45 @@
- generic [ref=e2]:
- generic [ref=e3]:
- img "General Bots" [ref=e6]
- heading "Welcome Back" [level=1] [ref=e7]
- paragraph [ref=e8]: Sign in to your General Bots account
- generic [ref=e10]:
- generic [ref=e11]:
- generic [ref=e12]:
- generic [ref=e13]: Email Address
- generic [ref=e14]:
- img
- textbox "Email Address" [ref=e15]:
- /placeholder: you@example.com
- generic [ref=e16]:
- generic [ref=e17]: Password
- generic [ref=e18]:
- img
- textbox "Password" [ref=e19]:
- /placeholder: ••••••••
- button [ref=e20] [cursor=pointer]:
- img [ref=e21]
- generic [ref=e24]:
- generic [ref=e27] [cursor=pointer]: Remember me
- link "Forgot password?" [ref=e28] [cursor=pointer]:
- /url: /auth/forgot-password
- button "Sign In" [ref=e29] [cursor=pointer]:
- generic [ref=e30]: Sign In
- generic [ref=e32]: or continue with
- generic [ref=e33]:
- button "Google" [ref=e34] [cursor=pointer]:
- img [ref=e35]
- text: Google
- button "Microsoft" [ref=e40] [cursor=pointer]:
- img [ref=e41]
- text: Microsoft
- button "GitHub" [ref=e46] [cursor=pointer]:
- img [ref=e47]
- text: GitHub
- button "Apple" [ref=e49] [cursor=pointer]:
- img [ref=e50]
- text: Apple
- paragraph [ref=e53]:
- text: Don't have an account?
- link "Create account" [ref=e54] [cursor=pointer]:
- /url: /auth/register

View file

@ -0,0 +1,25 @@
- generic [ref=e5]:
- generic [ref=e6]:
- generic [ref=e7]: Chat
- generic [ref=e8]:
- button [ref=e9] [cursor=pointer]:
- img [ref=e10]
- button [ref=e11] [cursor=pointer]:
- img [ref=e12]
- button [ref=e14] [cursor=pointer]:
- img [ref=e15]
- generic [ref=e18]:
- generic [ref=e19]:
- main [ref=e20]
- contentinfo [ref=e21]:
- generic [ref=e23]:
- generic [ref=e24]:
- button "Agent" [ref=e25] [cursor=pointer]
- button "Chat" [ref=e26] [cursor=pointer]
- textbox "Message... (type @ to mention)" [active] [ref=e27]
- button "↑" [ref=e28] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"

View file

@ -0,0 +1,111 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic "Home" [ref=e4] [cursor=pointer]:
- img [ref=e5]
- generic "Search" [ref=e8] [cursor=pointer]:
- img [ref=e9]
- generic "Terminal" [ref=e12] [cursor=pointer]:
- img [ref=e13]
- generic "User" [ref=e15] [cursor=pointer]:
- img [ref=e16]
- generic "Apps" [ref=e19] [cursor=pointer]:
- img [ref=e20]
- generic "Settings" [ref=e25] [cursor=pointer]:
- img [ref=e26]
- generic [ref=e29]:
- generic [ref=e30]:
- generic [ref=e31]:
- generic [ref=e32] [cursor=pointer]:
- img [ref=e34]
- generic [ref=e36]: Vibe
- generic [ref=e37] [cursor=pointer]:
- img [ref=e39]
- generic [ref=e44]: CRM
- generic [ref=e45] [cursor=pointer]:
- img [ref=e47]
- generic [ref=e49]: Campaigns
- generic [ref=e50] [cursor=pointer]:
- img [ref=e52]
- generic [ref=e53]: Lists
- generic [ref=e54] [cursor=pointer]:
- img [ref=e56]
- generic [ref=e58]: Templates
- generic [ref=e59] [cursor=pointer]:
- img [ref=e61]
- generic [ref=e64]: Tasks
- generic [ref=e65] [cursor=pointer]:
- img [ref=e67]
- generic [ref=e69]: Chat
- generic [ref=e70] [cursor=pointer]:
- img [ref=e72]
- generic [ref=e74]: Terminal
- generic [ref=e75] [cursor=pointer]:
- img [ref=e77]
- generic [ref=e79]: Explorer
- generic [ref=e80] [cursor=pointer]:
- img [ref=e82]
- generic [ref=e85]: Editor
- generic [ref=e86] [cursor=pointer]:
- img [ref=e88]
- generic [ref=e93]: Designer
- generic [ref=e94] [cursor=pointer]:
- img [ref=e96]
- generic [ref=e99]: BASIC
- generic [ref=e100] [cursor=pointer]:
- img [ref=e102]
- generic [ref=e105]: Browser
- generic [ref=e106]:
- generic [ref=e107]:
- generic [ref=e108]: Chat
- generic [ref=e109]:
- button [ref=e110] [cursor=pointer]:
- img [ref=e111]
- button [ref=e112] [cursor=pointer]:
- img [ref=e113]
- button [ref=e115] [cursor=pointer]:
- img [ref=e116]
- generic [ref=e119]:
- generic [ref=e120]:
- generic:
- generic: Connecting...
- main [ref=e121]
- contentinfo [ref=e122]:
- generic [ref=e124]:
- generic [ref=e125]:
- button "Agent" [ref=e126] [cursor=pointer]
- button "Chat" [ref=e127] [cursor=pointer]
- textbox "Message... (type @ to mention)" [active] [ref=e128]
- button "↑" [ref=e129] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"
- contentinfo [ref=e130]:
- img [ref=e134] [cursor=pointer]
- generic [ref=e136]:
- combobox [ref=e138] [cursor=pointer]:
- option "🎨 Default"
- option "☀️ Light" [selected]
- option "🍊 Orange"
- option "🌃 Cyberpunk"
- option "🌴 Retrowave"
- option "💭 Vapor Dream"
- option "✨ Y2K"
- option "🔲 3D Bevel"
- option "🕹️ Arcade"
- option "🪩 Disco"
- option "🎸 Grunge"
- option "🎺 Jazz"
- option "🌻 Mellow"
- option "🏠 Mid Century"
- option "📷 Polaroid"
- option "📺 Cartoons"
- option "🏖️ Seaside"
- option "⌨️ Typewriter"
- option "📠 Xerox"
- option "📁 XTree"
- button "Sign In" [ref=e139] [cursor=pointer]
- generic [ref=e140]:
- generic [ref=e141]: 00:00
- generic [ref=e142]: 01/01/2026

View file

@ -0,0 +1,39 @@
- generic [ref=e106]:
- generic [ref=e107]:
- generic [ref=e108]: Chat
- generic [ref=e109]:
- button [ref=e110] [cursor=pointer]:
- img [ref=e111]
- button [ref=e112] [cursor=pointer]:
- img [ref=e113]
- button [ref=e115] [cursor=pointer]:
- img [ref=e116]
- generic [ref=e119]:
- generic [ref=e120]:
- main [ref=e121]:
- paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje?
- generic [ref=e158]: 🔍 Detectar Desvios na Folha
- contentinfo [ref=e122]:
- generic [ref=e123]:
- button "📄 Segunda Via de Contracheque" [ref=e146] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e147] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e148] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e149] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e150] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e151] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e152] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e153] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e154] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [active] [ref=e155] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e156] [cursor=pointer]
- generic [ref=e124]:
- generic [ref=e125]:
- button "Agent" [ref=e126] [cursor=pointer]
- button "Chat" [ref=e127] [cursor=pointer]
- textbox "Message... (type @ to mention)" [ref=e128]
- button "↑" [ref=e129] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"

View file

@ -0,0 +1,109 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic "Home" [ref=e4] [cursor=pointer]:
- img [ref=e5]
- generic "Search" [ref=e8] [cursor=pointer]:
- img [ref=e9]
- generic "Terminal" [ref=e12] [cursor=pointer]:
- img [ref=e13]
- generic "User" [ref=e15] [cursor=pointer]:
- img [ref=e16]
- generic "Apps" [ref=e19] [cursor=pointer]:
- img [ref=e20]
- generic "Settings" [ref=e25] [cursor=pointer]:
- img [ref=e26]
- generic [ref=e29]:
- generic [ref=e30]:
- generic [ref=e31]:
- generic [ref=e32] [cursor=pointer]:
- img [ref=e34]
- generic [ref=e36]: Vibe
- generic [ref=e37] [cursor=pointer]:
- img [ref=e39]
- generic [ref=e44]: CRM
- generic [ref=e45] [cursor=pointer]:
- img [ref=e47]
- generic [ref=e49]: Campaigns
- generic [ref=e50] [cursor=pointer]:
- img [ref=e52]
- generic [ref=e53]: Lists
- generic [ref=e54] [cursor=pointer]:
- img [ref=e56]
- generic [ref=e58]: Templates
- generic [ref=e59] [cursor=pointer]:
- img [ref=e61]
- generic [ref=e64]: Tasks
- generic [ref=e65] [cursor=pointer]:
- img [ref=e67]
- generic [ref=e69]: Chat
- generic [ref=e70] [cursor=pointer]:
- img [ref=e72]
- generic [ref=e74]: Terminal
- generic [ref=e75] [cursor=pointer]:
- img [ref=e77]
- generic [ref=e79]: Explorer
- generic [ref=e80] [cursor=pointer]:
- img [ref=e82]
- generic [ref=e85]: Editor
- generic [ref=e86] [cursor=pointer]:
- img [ref=e88]
- generic [ref=e93]: Designer
- generic [ref=e94] [cursor=pointer]:
- img [ref=e96]
- generic [ref=e99]: BASIC
- generic [ref=e100] [cursor=pointer]:
- img [ref=e102]
- generic [ref=e105]: Browser
- generic [ref=e106]:
- generic [ref=e107]:
- generic [ref=e108]: Chat
- generic [ref=e109]:
- button [ref=e110] [cursor=pointer]:
- img [ref=e111]
- button [ref=e112] [cursor=pointer]:
- img [ref=e113]
- button [ref=e115] [cursor=pointer]:
- img [ref=e116]
- generic [ref=e119]:
- generic [ref=e120]:
- main [ref=e121]
- contentinfo [ref=e122]:
- generic [ref=e124]:
- generic [ref=e125]:
- button "Agent" [ref=e126] [cursor=pointer]
- button "Chat" [ref=e127] [cursor=pointer]
- textbox "Message... (type @ to mention)" [active] [ref=e128]
- button "↑" [ref=e129] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"
- contentinfo [ref=e130]:
- img [ref=e134] [cursor=pointer]
- generic [ref=e136]:
- combobox [ref=e138] [cursor=pointer]:
- option "🎨 Default"
- option "☀️ Light" [selected]
- option "🍊 Orange"
- option "🌃 Cyberpunk"
- option "🌴 Retrowave"
- option "💭 Vapor Dream"
- option "✨ Y2K"
- option "🔲 3D Bevel"
- option "🕹️ Arcade"
- option "🪩 Disco"
- option "🎸 Grunge"
- option "🎺 Jazz"
- option "🌻 Mellow"
- option "🏠 Mid Century"
- option "📷 Polaroid"
- option "📺 Cartoons"
- option "🏖️ Seaside"
- option "⌨️ Typewriter"
- option "📠 Xerox"
- option "📁 XTree"
- button "Sign In" [ref=e139] [cursor=pointer]
- generic [ref=e140]:
- generic [ref=e141]: 00:00
- generic [ref=e142]: 01/01/2026

View file

@ -0,0 +1,50 @@
- generic [ref=e106]:
- generic [ref=e107]:
- generic [ref=e108]: Chat
- generic [ref=e109]:
- button [ref=e110] [cursor=pointer]:
- img [ref=e111]
- button [ref=e112] [cursor=pointer]:
- img [ref=e113]
- button [ref=e115] [cursor=pointer]:
- img [ref=e116]
- generic [ref=e119]:
- generic [ref=e120]:
- main [ref=e121]:
- paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje?
- generic [ref=e169]: 🔍 Detectar Desvios na Folha
- contentinfo [ref=e122]:
- generic [ref=e123]:
- button "📄 Segunda Via de Contracheque" [ref=e146] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e147] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e148] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e149] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e150] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e151] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e152] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e153] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e154] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [active] [ref=e155] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e156] [cursor=pointer]
- button "📄 Segunda Via de Contracheque" [ref=e157] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e158] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e159] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e160] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e161] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e162] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e163] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e164] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e165] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [ref=e166] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e167] [cursor=pointer]
- generic [ref=e124]:
- generic [ref=e125]:
- button "Agent" [ref=e126] [cursor=pointer]
- button "Chat" [ref=e127] [cursor=pointer]
- textbox "Message... (type @ to mention)" [ref=e128]
- button "↑" [ref=e129] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"

View file

@ -0,0 +1,123 @@
- generic [ref=e106]:
- generic [ref=e107]:
- generic [ref=e108]: Chat
- generic [ref=e109]:
- button [ref=e110] [cursor=pointer]:
- img [ref=e111]
- button [ref=e112] [cursor=pointer]:
- img [ref=e113]
- button [ref=e115] [cursor=pointer]:
- img [ref=e116]
- generic [ref=e119]:
- generic [ref=e120]:
- main [ref=e121]:
- paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje?
- generic [ref=e169]: 🔍 Detectar Desvios na Folha
- generic [ref=e171]:
- paragraph [ref=e172]:
- text: 🕵️‍♀️
- strong [ref=e173]: Passo a passo para detectar desvios na sua folha de pagamento
- list [ref=e174]:
- listitem [ref=e175]:
- paragraph [ref=e176]:
- strong [ref=e177]: Acesse o Contracheque Online
- list [ref=e178]:
- listitem [ref=e179]: Entre no portal SIPES/SEAD com seu login.
- listitem [ref=e180]: Visualize o contracheque do mês corrente e dos últimos 6 meses.
- listitem [ref=e181]:
- paragraph [ref=e182]:
- strong [ref=e183]: Compare os valores
- list [ref=e184]:
- listitem [ref=e185]:
- text: 📈
- strong [ref=e186]: Remuneração Base
- text: ": deve permanecer constante (exceto progressões ou promoções)."
- listitem [ref=e187]:
- text:
- strong [ref=e188]: Verbas Variáveis
- text: ": identifique adicionais, horas extras ou gratificações que não reconhece."
- listitem [ref=e189]:
- text:
- strong [ref=e190]: Descontos
- text: ": cheque descontos de INSS, IRRF, pensão alimentícia e demais deduções."
- listitem [ref=e191]:
- paragraph [ref=e192]:
- strong [ref=e193]: Solicite documentos complementares (se necessário)
- list [ref=e194]:
- listitem [ref=e195]:
- strong [ref=e196]: Histórico de contracheques
- text: tipo
- code [ref=e197]: CONTRACHEQUE_HISTORICO
- text: .
- listitem [ref=e198]:
- strong [ref=e199]: Informe de rendimentos
- text: ou
- strong [ref=e200]: Ficha financeira
- text: para cruzar com a Receita Federal.
- listitem [ref=e201]:
- paragraph [ref=e202]:
- strong [ref=e203]: Cruzamento de informações
- list [ref=e204]:
- listitem [ref=e205]:
- text: Use o
- strong [ref=e206]: Informe de Rendimentos
- text: para verificar se os valores declarados coincidem com o que consta na folha.
- listitem [ref=e207]:
- text: Compare a
- strong [ref=e208]: Ficha Financeira
- text: com o extrato bancário (se houver depósito direto).
- listitem [ref=e209]:
- paragraph [ref=e210]:
- strong [ref=e211]: Registre um protocolo na SEAD/SEPLAGSE
- list [ref=e212]:
- listitem [ref=e213]:
- text: "Caso encontre alguma divergência, abra um protocolo (ex.:"
- code [ref=e214]: CCH123456
- text: ).
- listitem [ref=e215]: Descreva claramente a diferença encontrada e anexe prints ou documentos.
- listitem [ref=e216]:
- paragraph [ref=e217]:
- strong [ref=e218]: Acompanhamento
- list [ref=e219]:
- listitem [ref=e220]: Acompanhe o status do protocolo pelo portal ou pelo telefone da Ouvidoria.
- listitem [ref=e221]: Se a resposta for insatisfatória, solicite revisão ou escalonamento.
- separator [ref=e222]
- paragraph [ref=e223]:
- text: ⚙️
- strong [ref=e224]: Posso ajudar a solicitar algum desses documentos
- text: (histórico de contracheques, informe de rendimentos, ficha financeira) ou a abrir um protocolo de auditoria. Basta informar os dados solicitados (CPF, matrícula, email, etc.).
- contentinfo [ref=e122]:
- generic [ref=e123]:
- button "📄 Segunda Via de Contracheque" [ref=e225] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e226] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e227] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e228] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e229] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e230] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e231] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e232] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e233] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [ref=e234] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e235] [cursor=pointer]
- button "📄 Segunda Via de Contracheque" [ref=e236] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e237] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e238] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e239] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e240] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e241] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e242] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e243] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e244] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [ref=e245] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e246] [cursor=pointer]
- generic [ref=e124]:
- generic [ref=e125]:
- button "Agent" [ref=e126] [cursor=pointer]
- button "Chat" [ref=e127] [cursor=pointer]
- textbox "Message... (type @ to mention)" [ref=e128]
- button "↑" [ref=e129] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"

View file

@ -0,0 +1,111 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic "Home" [ref=e4] [cursor=pointer]:
- img [ref=e5]
- generic "Search" [ref=e8] [cursor=pointer]:
- img [ref=e9]
- generic "Terminal" [ref=e12] [cursor=pointer]:
- img [ref=e13]
- generic "User" [ref=e15] [cursor=pointer]:
- img [ref=e16]
- generic "Apps" [ref=e19] [cursor=pointer]:
- img [ref=e20]
- generic "Settings" [ref=e25] [cursor=pointer]:
- img [ref=e26]
- generic [ref=e29]:
- generic [ref=e30]:
- generic [ref=e31]:
- generic [ref=e32] [cursor=pointer]:
- img [ref=e34]
- generic [ref=e36]: Vibe
- generic [ref=e37] [cursor=pointer]:
- img [ref=e39]
- generic [ref=e44]: CRM
- generic [ref=e45] [cursor=pointer]:
- img [ref=e47]
- generic [ref=e49]: Campaigns
- generic [ref=e50] [cursor=pointer]:
- img [ref=e52]
- generic [ref=e53]: Lists
- generic [ref=e54] [cursor=pointer]:
- img [ref=e56]
- generic [ref=e58]: Templates
- generic [ref=e59] [cursor=pointer]:
- img [ref=e61]
- generic [ref=e64]: Tasks
- generic [ref=e65] [cursor=pointer]:
- img [ref=e67]
- generic [ref=e69]: Chat
- generic [ref=e70] [cursor=pointer]:
- img [ref=e72]
- generic [ref=e74]: Terminal
- generic [ref=e75] [cursor=pointer]:
- img [ref=e77]
- generic [ref=e79]: Explorer
- generic [ref=e80] [cursor=pointer]:
- img [ref=e82]
- generic [ref=e85]: Editor
- generic [ref=e86] [cursor=pointer]:
- img [ref=e88]
- generic [ref=e93]: Designer
- generic [ref=e94] [cursor=pointer]:
- img [ref=e96]
- generic [ref=e99]: BASIC
- generic [ref=e100] [cursor=pointer]:
- img [ref=e102]
- generic [ref=e105]: Browser
- generic [ref=e106]:
- generic [ref=e107]:
- generic [ref=e108]: Chat
- generic [ref=e109]:
- button [ref=e110] [cursor=pointer]:
- img [ref=e111]
- button [ref=e112] [cursor=pointer]:
- img [ref=e113]
- button [ref=e115] [cursor=pointer]:
- img [ref=e116]
- generic [ref=e119]:
- generic [ref=e120]:
- generic:
- generic: Connecting...
- main [ref=e121]
- contentinfo [ref=e122]:
- generic [ref=e124]:
- generic [ref=e125]:
- button "Agent" [ref=e126] [cursor=pointer]
- button "Chat" [ref=e127] [cursor=pointer]
- textbox "Message... (type @ to mention)" [active] [ref=e128]
- button "↑" [ref=e129] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"
- contentinfo [ref=e130]:
- img [ref=e134] [cursor=pointer]
- generic [ref=e136]:
- combobox [ref=e138] [cursor=pointer]:
- option "🎨 Default"
- option "☀️ Light" [selected]
- option "🍊 Orange"
- option "🌃 Cyberpunk"
- option "🌴 Retrowave"
- option "💭 Vapor Dream"
- option "✨ Y2K"
- option "🔲 3D Bevel"
- option "🕹️ Arcade"
- option "🪩 Disco"
- option "🎸 Grunge"
- option "🎺 Jazz"
- option "🌻 Mellow"
- option "🏠 Mid Century"
- option "📷 Polaroid"
- option "📺 Cartoons"
- option "🏖️ Seaside"
- option "⌨️ Typewriter"
- option "📠 Xerox"
- option "📁 XTree"
- button "Sign In" [ref=e139] [cursor=pointer]
- generic [ref=e140]:
- generic [ref=e141]: 00:00
- generic [ref=e142]: 01/01/2026

View file

@ -0,0 +1,61 @@
- generic [ref=e106]:
- generic [ref=e107]:
- generic [ref=e108]: Chat
- generic [ref=e109]:
- button [ref=e110] [cursor=pointer]:
- img [ref=e111]
- button [ref=e112] [cursor=pointer]:
- img [ref=e113]
- button [ref=e115] [cursor=pointer]:
- img [ref=e116]
- generic [ref=e119]:
- generic [ref=e120]:
- main [ref=e121]:
- paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje?
- generic [ref=e180]: 🔍 Detectar Desvios na Folha
- contentinfo [ref=e122]:
- generic [ref=e123]:
- button "📄 Segunda Via de Contracheque" [ref=e146] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e147] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e148] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e149] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e150] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e151] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e152] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e153] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e154] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [active] [ref=e155] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e156] [cursor=pointer]
- button "📄 Segunda Via de Contracheque" [ref=e157] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e158] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e159] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e160] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e161] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e162] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e163] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e164] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e165] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [ref=e166] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e167] [cursor=pointer]
- button "📄 Segunda Via de Contracheque" [ref=e168] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e169] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e170] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e171] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e172] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e173] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e174] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e175] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e176] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [ref=e177] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e178] [cursor=pointer]
- generic [ref=e124]:
- generic [ref=e125]:
- button "Agent" [ref=e126] [cursor=pointer]
- button "Chat" [ref=e127] [cursor=pointer]
- textbox "Message... (type @ to mention)" [ref=e128]
- button "↑" [ref=e129] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"

View file

@ -0,0 +1,82 @@
- generic [ref=e106]:
- generic [ref=e107]:
- generic [ref=e108]: Chat
- generic [ref=e109]:
- button [ref=e110] [cursor=pointer]:
- img [ref=e111]
- button [ref=e112] [cursor=pointer]:
- img [ref=e113]
- button [ref=e115] [cursor=pointer]:
- img [ref=e116]
- generic [ref=e119]:
- generic [ref=e120]:
- main [ref=e121]:
- paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje?
- generic [ref=e180]: 🔍 Detectar Desvios na Folha
- generic [ref=e182]:
- paragraph [ref=e183]:
- text: 🔎
- strong [ref=e184]: Vamos analisar a sua folha de pagamento para identificar possíveis desvios.
- text: Para isso preciso que você me informe os dados cadastrais do servidor (mesmo que já os tenha usado antes). Assim consigo solicitar o histórico de contracheques e comparálos.
- blockquote [ref=e185]:
- paragraph [ref=e186]:
- strong [ref=e187]: Dados necessários
- list [ref=e188]:
- listitem [ref=e189]: CPF (com ou sem formatação)
- listitem [ref=e190]: Matrícula funcional
- listitem [ref=e191]: Nome completo do servidor
- listitem [ref=e192]: "Órgão de lotação (ex.: SEAD, SEED, SES, SSP)"
- listitem [ref=e193]: Email (para envio do documento)
- listitem [ref=e194]: Telefone com DDD
- paragraph [ref=e195]:
- text: ⚠️
- strong [ref=e196]: Confirme
- text: que está de acordo em fornecer essas informações e eu solicito o documento
- code [ref=e197]: CONTRACHEQUE_HISTORICO
- text: . Depois analisaremos juntos as variações encontradas.
- contentinfo [ref=e122]:
- generic [ref=e123]:
- button "📄 Segunda Via de Contracheque" [ref=e198] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e199] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e200] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e201] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e202] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e203] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e204] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e205] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e206] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [ref=e207] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e208] [cursor=pointer]
- button "📄 Segunda Via de Contracheque" [ref=e209] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e210] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e211] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e212] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e213] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e214] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e215] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e216] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e217] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [ref=e218] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e219] [cursor=pointer]
- button "📄 Segunda Via de Contracheque" [ref=e220] [cursor=pointer]
- button "📅 Agendar Atendimento no CEAC" [ref=e221] [cursor=pointer]
- button "🩺 Agendar Perícia Médica" [ref=e222] [cursor=pointer]
- button "📋 Solicitar Documentos Funcionais" [ref=e223] [cursor=pointer]
- button "✏️ Atualizar Dados Cadastrais" [ref=e224] [cursor=pointer]
- button "🎓 Cursos da Escola de Governo" [ref=e225] [cursor=pointer]
- button "💰 Informações sobre Benefícios" [ref=e226] [cursor=pointer]
- button "🔍 Consultar Protocolo" [ref=e227] [cursor=pointer]
- button "📞 Falar com a SEAD" [ref=e228] [cursor=pointer]
- button "🔍 Detectar Desvios na Folha" [ref=e229] [cursor=pointer]
- button "❓ Outras Solicitações" [ref=e230] [cursor=pointer]
- generic [ref=e124]:
- generic [ref=e125]:
- button "Agent" [ref=e126] [cursor=pointer]
- button "Chat" [ref=e127] [cursor=pointer]
- textbox "Message... (type @ to mention)" [ref=e128]
- button "↑" [ref=e129] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"

View file

@ -0,0 +1,27 @@
- generic [ref=e5]:
- generic [ref=e6]:
- generic [ref=e7]: Chat
- generic [ref=e8]:
- button [ref=e9] [cursor=pointer]:
- img [ref=e10]
- button [ref=e11] [cursor=pointer]:
- img [ref=e12]
- button [ref=e14] [cursor=pointer]:
- img [ref=e15]
- generic [ref=e18]:
- generic [ref=e19]:
- generic:
- generic: Connecting...
- main [ref=e20]
- contentinfo [ref=e21]:
- generic [ref=e23]:
- generic [ref=e24]:
- button "Agent" [ref=e25] [cursor=pointer]
- button "Chat" [ref=e26] [cursor=pointer]
- textbox "Message... (type @ to mention)" [active] [ref=e27]
- button "↑" [ref=e28] [cursor=pointer]
- button "Scroll to bottom":
- img
- generic:
- generic:
- button "View"

View file

@ -0,0 +1,147 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic "Home" [ref=e4] [cursor=pointer]:
- img [ref=e5]
- generic "Search" [ref=e8] [cursor=pointer]:
- img [ref=e9]
- generic "Terminal" [ref=e12] [cursor=pointer]:
- img [ref=e13]
- generic "User" [ref=e15] [cursor=pointer]:
- img [ref=e16]
- generic "Apps" [ref=e19] [cursor=pointer]:
- img [ref=e20]
- generic "Settings" [ref=e25] [cursor=pointer]:
- img [ref=e26]
- generic [ref=e29]:
- generic [ref=e30]:
- generic [ref=e31]:
- generic [ref=e32] [cursor=pointer]:
- img [ref=e34]
- generic [ref=e36]: Vibe
- generic [ref=e37] [cursor=pointer]:
- img [ref=e39]
- generic [ref=e44]: CRM
- generic [ref=e45] [cursor=pointer]:
- img [ref=e47]
- generic [ref=e49]: Campaigns
- generic [ref=e50] [cursor=pointer]:
- img [ref=e52]
- generic [ref=e53]: Lists
- generic [ref=e54] [cursor=pointer]:
- img [ref=e56]
- generic [ref=e58]: Templates
- generic [ref=e59] [cursor=pointer]:
- img [ref=e61]
- generic [ref=e64]: Tasks
- generic [ref=e65] [cursor=pointer]:
- img [ref=e67]
- generic [ref=e69]: Chat
- generic [ref=e70] [cursor=pointer]:
- img [ref=e72]
- generic [ref=e74]: Terminal
- generic [ref=e75] [cursor=pointer]:
- img [ref=e77]
- generic [ref=e79]: Explorer
- generic [ref=e80] [cursor=pointer]:
- img [ref=e82]
- generic [ref=e85]: Editor
- generic [ref=e86] [cursor=pointer]:
- img [ref=e88]
- generic [ref=e93]: Designer
- generic [ref=e94] [cursor=pointer]:
- img [ref=e96]
- generic [ref=e99]: BASIC
- generic [ref=e100] [cursor=pointer]:
- img [ref=e102]
- generic [ref=e105]: Browser
- generic [ref=e106]:
- generic [ref=e107]:
- generic [ref=e108]: Chat
- generic [ref=e109]:
- button [ref=e110] [cursor=pointer]:
- img [ref=e111]
- button [ref=e112] [cursor=pointer]:
- img [ref=e113]
- button [ref=e115] [cursor=pointer]:
- img [ref=e116]
- generic [ref=e119]:
- generic [ref=e120]:
- complementary [ref=e121]:
- button "Chat" [ref=e122]:
- img [ref=e123]
- button "Tasks" [ref=e125]:
- img [ref=e126]
- button "Terminal" [ref=e129]:
- img [ref=e130]
- button "Explorer" [ref=e132]:
- img [ref=e133]
- button "Editor" [ref=e135]:
- img [ref=e136]
- button "Browser" [ref=e139]:
- img [ref=e140]
- main
- generic [ref=e143]:
- generic [ref=e144]:
- text: // BROWSER
- textbox "No preview active" [ref=e145]
- generic [ref=e147]: Waiting for app preview...
- generic [ref=e149]: // TERMINAL
- generic [ref=e150]:
- generic [ref=e151]: "Agent #1"
- text: EVOLVED
- generic [ref=e152]: Claude Opus 4.5 — 99%
- generic [ref=e153]:
- generic [ref=e154]:
- text: Plan
- button [ref=e155]
- generic [ref=e156]:
- text: YOLO
- button [ref=e157]
- generic [ref=e158]:
- generic [ref=e159]:
- button "◄" [ref=e160]
- text: 0 / 0
- button "►" [ref=e161]
- generic [ref=e162]:
- button "💬" [ref=e163]
- button "✏️" [ref=e164]
- button "</>" [ref=e165]
- contentinfo [ref=e166]:
- generic [ref=e168]: Reference Entity
- generic [ref=e169]:
- generic [ref=e170]:
- button "Agent" [ref=e171]
- button "Chat" [ref=e172]
- textbox "Message... (type @ to mention)" [active] [ref=e173]
- button "↑" [ref=e174]
- button "Scroll to bottom" [ref=e175]:
- img [ref=e176]
- button "View" [ref=e180]
- contentinfo [ref=e181]:
- img [ref=e185] [cursor=pointer]
- generic [ref=e187]:
- combobox [ref=e189] [cursor=pointer]:
- option "🎨 Default"
- option "☀️ Light"
- option "🍊 Orange"
- option "🌃 Cyberpunk"
- option "🌴 Retrowave"
- option "💭 Vapor Dream"
- option "✨ Y2K"
- option "🔲 3D Bevel"
- option "🕹️ Arcade"
- option "🪩 Disco"
- option "🎸 Grunge"
- option "🎺 Jazz"
- option "🌻 Mellow"
- option "🏠 Mid Century"
- option "📷 Polaroid"
- option "📺 Cartoons"
- option "🏖️ Seaside"
- option "⌨️ Typewriter" [selected]
- option "📠 Xerox"
- option "📁 XTree"
- button "Sign In" [ref=e190] [cursor=pointer]
- generic [ref=e191]:
- generic [ref=e192]: 00:00
- generic [ref=e193]: 01/01/2026

View file

@ -0,0 +1,16 @@
{
"found": true,
"tag": "DIV",
"className": "suggestions-container",
"innerHTML": "",
"childCount": 0,
"display": "flex",
"visibility": "visible",
"opacity": "1",
"height": "4px",
"maxHeight": "100px",
"overflow": "auto",
"hasClass": false,
"parent": "FOOTER ",
"styleAttr": null
}

View file

@ -0,0 +1,64 @@
{
"rules": [
{
"sheet": "https://chat.pragmatismo.com.br/suite/css/app.css",
"selector": "*",
"display": "",
"maxHeight": "",
"overflow": "",
"opacity": "",
"visibility": "",
"height": "",
"padding": "0px",
"margin": "0px"
},
{
"sheet": "https://chat.pragmatismo.com.br/suite/css/app.css",
"selector": "*",
"display": "",
"maxHeight": "",
"overflow": "",
"opacity": "",
"visibility": "",
"height": "",
"padding": "",
"margin": ""
},
{
"sheet": "https://chat.pragmatismo.com.br/suite/css/base.css",
"selector": "*",
"display": "",
"maxHeight": "",
"overflow": "",
"opacity": "",
"visibility": "",
"height": "",
"padding": "0px",
"margin": "0px"
},
{
"sheet": "inline",
"selector": "*",
"display": "",
"maxHeight": "",
"overflow": "",
"opacity": "",
"visibility": "",
"height": "",
"padding": "0px",
"margin": "0px"
},
{
"sheet": "https://chat.pragmatismo.com.br/suite/chat/chat.css?v=3",
"selector": ".suggestions-container",
"display": "flex",
"maxHeight": "100px",
"overflow": "",
"opacity": "",
"visibility": "",
"height": "",
"padding": "",
"margin": ""
}
]
}

View file

@ -0,0 +1,112 @@
[
{
"tag": "DIV",
"className": "suggestions-container has-bot-suggestions",
"id": "suggestions",
"text": "Agendar BatizadoAgendar CasamentoAgendar MissaAgendar PeregrinaçãoPedido de OraçãoUso de ImagemLicen",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "FOOTER."
},
{
"tag": "BUTTON",
"className": "suggestion-chip",
"id": "",
"text": "Agendar Batizado",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "DIV.suggestions-container has-bot-suggestions"
},
{
"tag": "BUTTON",
"className": "suggestion-chip",
"id": "",
"text": "Agendar Casamento",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "DIV.suggestions-container has-bot-suggestions"
},
{
"tag": "BUTTON",
"className": "suggestion-chip",
"id": "",
"text": "Agendar Missa",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "DIV.suggestions-container has-bot-suggestions"
},
{
"tag": "BUTTON",
"className": "suggestion-chip",
"id": "",
"text": "Agendar Peregrinação",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "DIV.suggestions-container has-bot-suggestions"
},
{
"tag": "BUTTON",
"className": "suggestion-chip",
"id": "",
"text": "Pedido de Oração",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "DIV.suggestions-container has-bot-suggestions"
},
{
"tag": "BUTTON",
"className": "suggestion-chip",
"id": "",
"text": "Uso de Imagem",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "DIV.suggestions-container has-bot-suggestions"
},
{
"tag": "BUTTON",
"className": "suggestion-chip",
"id": "",
"text": "Licenciamento",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "DIV.suggestions-container has-bot-suggestions"
},
{
"tag": "BUTTON",
"className": "suggestion-chip",
"id": "",
"text": "Evento/Iluminação",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "DIV.suggestions-container has-bot-suggestions"
},
{
"tag": "BUTTON",
"className": "suggestion-chip",
"id": "",
"text": "Cadastrar Guia",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "DIV.suggestions-container has-bot-suggestions"
},
{
"tag": "BUTTON",
"className": "suggestion-chip",
"id": "",
"text": "Fazer Doação",
"display": "flex",
"visibility": "visible",
"hidden": false,
"parent": "DIV.suggestions-container has-bot-suggestions"
}
]

View file

@ -246,6 +246,7 @@ match x {
## ❌ Absolute Prohibitions
- NEVER search /target folder! It is binary compiled.
- ❌ **NEVER** hardcode passwords, tokens, API keys, or any credentials in source code — ALWAYS use `generate_random_string()` or environment variables
- ❌ **NEVER** build in release mode - ONLY debug builds allowed
- ❌ **NEVER** use `--release` flag on ANY cargo command
- ❌ **NEVER** run `cargo build` - use `cargo check` for syntax verification
@ -860,3 +861,70 @@ Continue on gb/ workspace. Follow AGENTS.md strictly:
- **VERIFY LAST** - Only compile/diagnostics after ALL fixes
- **DELETE DEAD CODE** - Don't keep unused code around
- **GIT WORKFLOW** - ALWAYS push to ALL repositories (github, pragmatismo)
---
## Deploy in Prod Workflow
### CI/CD Pipeline (Primary Method)
1. **Push to ALM** — triggers CI/CD automatically:
```bash
cd botserver
git push alm main
git push origin main
cd ..
git add botserver
git commit -m "Update botserver: <description>"
git push alm main
git push origin main
```
2. **Wait for CI** — build takes ~3-4 minutes. Check status:
```bash
# Via web: https://alm.pragmatismo.com.br/GeneralBots/botserver/actions
# Or check binary timestamp after ~4 min sleep:
sleep 240
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \
"sudo incus exec system -- stat -c '%y' /opt/gbo/bin/botserver"
```
3. **Restart in prod** — after binary updates:
```bash
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \
"sudo incus exec system -- pkill -f botserver || true"
sleep 2
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \
"sudo incus exec system -- bash -c 'cd /opt/gbo/bin && sudo -u gbuser RUST_LOG=info ./botserver --noconsole > /opt/gbo/logs/botserver-output.log 2>&1 &'"
```
4. **Verify deployment**:
```bash
# Wait for bootstrap (~2 min)
sleep 120
# Check health
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \
"sudo incus exec system -- curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/health"
# Check logs
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \
"sudo incus exec system -- tail -30 /opt/gbo/logs/botserver-output.log"
```
### Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| `valkey-cli ping` hangs indefinitely | Valkey requires password auth | Install `nc` or `ss` in container for health checks |
| `nc: command not found` | Prod container lacks netcat | `sudo incus exec system -- apt-get install -y netcat-openbsd` |
| Cache connection timeout | iptables DROP rule on port 6379 | `sudo incus exec system -- iptables -I INPUT -i lo -j ACCEPT` |
| `AUTH failed` on Valkey | Valkey runs without password but Vault has one | Code tries no-password URL first, then with password |
| `Cannot start a runtime from within a runtime` | `block_on()` called from async context | Use `.await` directly, never `runtime.block_on()` in async functions |
| Secret not found in Vault | Path mismatch between seeding and reading | Seeding: `secret/gbo/cache`, Reading: `gbo/cache` (kv2 prepends `secret/`) |
| CI completed but binary not updated | Deploy step fails silently (SSH/transfer issue) | Build locally and transfer, or re-push to trigger CI again |
### Critical Paths in Vault
- **Seeding writes to**: `secret/gbo/{service}` (e.g., `secret/gbo/cache`)
- **Code reads via**: `SecretPaths::{SERVICE}` which maps to `gbo/{service}`
- **kv2::read** prepends `secret/` automatically and looks up `secret/data/gbo/{service}`
- **All paths must match**: `gbo/cache`, `gbo/drive`, `gbo/tables`, `gbo/directory`, `gbo/llm`, `gbo/meet`, `gbo/alm`, `gbo/vectordb`, `gbo/encryption`, `gbo/email`

2
Cargo.lock generated
View file

@ -1275,7 +1275,7 @@ dependencies = [
[[package]]
name = "botapp"
version = "6.3.0"
version = "6.1.0"
dependencies = [
"anyhow",
"botlib",

View file

@ -9,6 +9,7 @@ members = [
"bottest",
"botui",
]
exclude = ["backup-to-s3"]
[workspace.lints.rust]

View file

@ -0,0 +1,7 @@
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_REGION=us-east-1
S3_BUCKET=prod-gbo-backup
INCUS_PROJECT=PROD-GBO1
CONTAINER_NAME=
KEEP_COUNT=10

1818
backup-to-s3/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

15
backup-to-s3/Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "backup-to-s3"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }
rusoto_s3 = "0.48"
rusoto_core = "0.48"
rusoto_credential = "0.48"
dotenv = "0.15"
chrono = "0.4"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2"

213
backup-to-s3/src/main.rs Normal file
View file

@ -0,0 +1,213 @@
use chrono::Utc;
use rusoto_core::Region;
use rusoto_credential::StaticProvider;
use rusoto_s3::{S3, S3Client};
use std::env;
use tracing::{error, info, warn};
#[derive(Debug)]
struct Config {
access_key: String,
secret_key: String,
bucket: String,
project: String,
keep_count: usize,
region: Region,
}
impl Config {
fn from_env() -> Result<Self, String> {
dotenv::from_path(".env").ok();
let access_key = env::var("AWS_ACCESS_KEY_ID").map_err(|_| "AWS_ACCESS_KEY_ID not set")?;
let secret_key = env::var("AWS_SECRET_ACCESS_KEY").map_err(|_| "AWS_SECRET_ACCESS_KEY not set")?;
let bucket = env::var("S3_BUCKET").map_err(|_| "S3_BUCKET not set")?;
let project = env::var("INCUS_PROJECT").unwrap_or_else(|_| "PROD-GBO1".to_string());
let keep_count: usize = env::var("KEEP_COUNT").unwrap_or_else(|_| "10".to_string()).parse().unwrap_or(10);
let region_name = env::var("AWS_REGION").unwrap_or_else(|_| "us-east-1".to_string());
let region = region_name.parse().unwrap_or(Region::UsEast1);
Ok(Config {
access_key,
secret_key,
bucket,
project,
keep_count,
region,
})
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_env_filter("info")
.init();
let config = Config::from_env()?;
info!("Starting backup to S3 bucket: {}", config.bucket);
let credential_provider = StaticProvider::new_minimal(
config.access_key.clone(),
config.secret_key.clone(),
);
let s3_client = S3Client::new_with(
rusoto_core::HttpClient::new().map_err(|e| format!("HTTP client: {}", e))?,
credential_provider,
config.region.clone(),
);
let timestamp = Utc::now().format("%Y-%m-%d").to_string();
let container_name = env::var("CONTAINER_NAME").unwrap_or_default();
let containers = if container_name.is_empty() {
list_inc_containers(&config.project).await?
} else {
vec![container_name]
};
for container in containers {
info!("Backing up container: {}", container);
match backup_container(&container, &config, &s3_client, &timestamp).await {
Ok(_) => info!("Successfully backed up {}", container),
Err(e) => {
error!("Failed to backup {}: {}", container, e);
continue;
}
}
}
cleanup_old_backups(&config, &s3_client).await?;
info!("Backup completed!");
Ok(())
}
async fn list_inc_containers(project: &str) -> Result<Vec<String>, String> {
let output = tokio::process::Command::new("incus")
.args(["list", "--project", project, "--format", "csv", "-c", "n"])
.output()
.await
.map_err(|e| format!("Failed to list containers: {}", e))?;
if !output.status.success() {
return Err("incus list failed".to_string());
}
let stdout = String::from_utf8_lossy(&output.stdout);
let containers: Vec<String> = stdout.lines()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
Ok(containers)
}
async fn backup_container(
container: &str,
config: &Config,
s3_client: &S3Client,
timestamp: &str,
) -> Result<(), String> {
let temp_dir = std::env::temp_dir();
let backup_file = temp_dir.join(format!("{}.tar.gz", container));
info!("Exporting {} to {:?}", container, backup_file);
let export_output = tokio::process::Command::new("incus")
.args([
"export",
container,
backup_file.to_str().unwrap(),
"--instance-only",
"--project",
&config.project,
])
.output()
.await
.map_err(|e| format!("Failed to export container: {}", e))?;
if !export_output.status.success() {
let stderr = String::from_utf8_lossy(&export_output.stderr);
return Err(format!("incus export failed: {}", stderr));
}
let s3_key = format!("{}/{}.tar.gz", timestamp, container);
info!("Uploading to s3://{}/{}", config.bucket, s3_key);
let file_data = tokio::fs::read(&backup_file)
.await
.map_err(|e| format!("Failed to read backup file: {}", e))?;
let put_request = rusoto_s3::PutObjectRequest {
body: Some(file_data.into()),
bucket: config.bucket.clone(),
key: s3_key,
..Default::default()
};
s3_client.put_object(put_request)
.await
.map_err(|e| format!("Failed to upload to S3: {}", e))?;
tokio::fs::remove_file(&backup_file)
.await
.map_err(|e| warn!("Failed to delete temp file: {}", e)).ok();
info!("Uploaded {}", container);
Ok(())
}
async fn cleanup_old_backups(config: &Config, s3_client: &S3Client) -> Result<(), String> {
info!("Cleaning up old backups, keeping {} most recent", config.keep_count);
let list_output = s3_client
.list_objects_v2(rusoto_s3::ListObjectsV2Request {
bucket: config.bucket.clone(),
delimiter: Some("/".to_string()),
..Default::default()
})
.await
.map_err(|e| format!("Failed to list S3: {}", e))?;
let mut prefixes: Vec<String> = list_output
.common_prefixes
.unwrap_or_default()
.iter()
.map(|p| p.prefix.clone().unwrap_or_default().trim_end_matches('/').to_string())
.filter(|p| !p.is_empty())
.collect();
prefixes.sort_by(|a, b| b.cmp(a));
if prefixes.len() > config.keep_count {
for prefix in prefixes.iter().skip(config.keep_count) {
info!("Deleting old backup: {}", prefix);
let objects = s3_client
.list_objects_v2(rusoto_s3::ListObjectsV2Request {
bucket: config.bucket.clone(),
prefix: Some(format!("{}/", prefix)),
..Default::default()
})
.await
.map_err(|e| format!("Failed to list objects: {}", e))?;
for obj in objects.contents.unwrap_or_default() {
if let Some(key) = obj.key {
s3_client
.delete_object(rusoto_s3::DeleteObjectRequest {
bucket: config.bucket.clone(),
key,
..Default::default()
})
.await
.ok();
}
}
}
}
Ok(())
}

@ -1 +1 @@
Subproject commit dae0feb6a567dd1b7e66a382786deef4e7f1fb8a
Subproject commit 7b4753af0d1c8b2ce763e5171676e8116ddd5fb4

View file

Binary file not shown.

5
ping_google.cmd Normal file
View file

@ -0,0 +1,5 @@
@echo off
:loop
ping google.com
timeout /t 18000 /nobreak >nul
goto loop

View file

@ -1,85 +1,29 @@
# Plan: Migrate LXC Containers to Incus (COMPLETED)
Migrate 10 LXC containers from source (root@82.29.59.188 - LXD/ZFS) to destination (administrator@63.141.255.9 - Incus/Btrfs) using streaming method.
Instructions
- Use Btrfs storage on destination (loop1 mounted at /mnt/btrfs)
- Use streaming method: tar | zstd | ssh (no local storage)
- Migrate one by one: stop → copy → create → start → delete
- DO NOT delete containers before copying data
Discoveries
- Source: LXD with ZFS backend (default/containers/*), containers at /var/snap/lxd/common/lxd/storage-pools/default/containers/
- Destination: Incus 6.23, Btrfs pool PROD-GBO on loop1, project PROD-GBO1
- SSH: Works from source root → destination administrator
- MISTAKE MADE: Deleted all Incus containers with cleanup command before data was properly inside them
Accomplished
- ✅ Created Btrfs storage pool PROD-GBO on destination
- ✅ SSH access configured from source to destination
- ❌ Containers were deleted during cleanup - they need to be recreated
- ❌ Data is outside containers, needs to be copied INTO containers properly
## Summary
✅ All containers migrated from LXD (pragmatismo.com.br) to Incus (63.141.255.9)
✅ All data synced from host /opt/gbo/tenants/ to containers
✅ All binaries copied from source to containers
✅ All services configured and running
Next steps
1. Create empty Incus containers: incus create --empty --project PROD-GBO1 -s PROD-GBO
2. Migrate from source: tar -C rootfs -cf - . | zstd | ssh dest 'sudo tar -I zstd -xf - -C /mnt/btrfs/containers/PROD-GBO1_<name>/rootfs'
3. Start containers: incus start <name>
4. Delete from source: lxc delete --force
## Container & Service Status
| Container | Service | Status |
|-----------|---------|--------|
| dns | coredns | ✅ RUNNING |
| email | stalwart-mail | ✅ RUNNING |
| webmail | php built-in server (:5252) | ✅ RUNNING |
| alm | forgejo | ✅ RUNNING |
| drive | minio | ✅ RUNNING |
| tables | postgresql | ✅ RUNNING |
| system | botserver | ✅ RUNNING |
## Service Files Location
All service files in `/etc/systemd/system/` inside containers:
- `dns.service` - coredns (User=root)
- `email.service` - stalwart-mail (User=root)
- `alm.service` - forgejo (User=alm, Group=alm)
- `minio.service` - minio (User=root)
## Binary Locations
| Service | Binary Path |
|---------|-------------|
| coredns | /opt/gbo/bin/coredns |
| stalwart | /opt/gbo/bin/stalwart |
| forgejo | /opt/gbo/bin/forgejo |
| minio | /usr/local/bin/minio |
## Key Paths Inside Containers
- **Binaries**: /opt/gbo/bin/
- **Data**: /opt/gbo/data/
- **Config**: /opt/gbo/conf/
- **Logs**: /opt/gbo/logs/
## IPS (Destination)
- dns: 10.107.115.155
- email: 10.107.115.200
- webmail: 10.107.115.208
- alm: 10.107.115.4
- drive: 10.107.115.114
- tables: 10.107.115.33
- system: 10.107.115.229
- alm-ci: 10.107.115.190
- table-editor: 10.107.115.73
## Port Forwarding (iptables NAT)
```
# DNS
sudo iptables -t nat -A PREROUTING -p tcp --dport 53 -j DNAT --to-destination 10.107.115.155:53
sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination 10.107.115.155:53
# Email
sudo iptables -t nat -A PREROUTING -p tcp --dport 25 -j DNAT --to-destination 10.107.115.200:25
sudo iptables -t nat -A PREROUTING -p tcp --dport 587 -j DNAT --to-destination 10.107.115.200:587
sudo iptables -t nat -A PREROUTING -p tcp --dport 465 -j DNAT --to-destination 10.107.115.200:465
sudo iptables -t nat -A PREROUTING -p tcp --dport 143 -j DNAT --to-destination 10.107.115.200:143
sudo iptables -t nat -A PREROUTING -p tcp --dport 993 -j DNAT --to-destination 10.107.115.200:993
sudo iptables -t nat -A PREROUTING -p tcp --dport 110 -j DNAT --to-destination 10.107.115.200:110
sudo iptables -t nat -A PREROUTING -p tcp --dport 995 -j DNAT --to-destination 10.107.115.200:995
sudo iptables -t nat -A PREROUTING -p tcp --dport 4190 -j DNAT --to-destination 10.107.115.200:4190
# Webmail
sudo iptables -t nat -A PREROUTING -p tcp --dport 5252 -j DNAT --to-destination 10.107.115.208:5252
# ALM (forgejo)
sudo iptables -t nat -A PREROUTING -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747
# Caddy (80, 443) - already exists for proxy container
```
## Workflow (PRODUCTION TESTED)
1. Copy container: `incus copy --instance-only lxd-source:<source> <dest>`
2. Add eth0 network: `incus config device add <c> eth0 nic name=eth0 network=PROD-GBO`
3. Sync data: `incus file push --recursive /opt/gbo/tenants/pragmatismo/<container>/ <container>/opt/gbo/`
4. Copy binaries: from source via `lxc file pull` → scp to dest → `incus file push`
5. Create service file: `cat > /tmp/<svc>.service && incus file push <svc>.service <c>/etc/systemd/system/`
6. Enable/start: `incus exec <c> -- systemctl enable <svc> && systemctl start <svc>`
Accomplished
- Recreated Btrfs storage pool PROD-GBO on /mnt/btrfs
- Created 10 empty Incus containers
- Migrated all containers using tar | zstd | ssh
- All 10 containers running on Btrfs
- Used ~24GB of Btrfs storage

461
prompts/c3.md Normal file
View file

@ -0,0 +1,461 @@
# Fast Migration Plan - LXC/LXD to Incus (ONE BY ONE)
## Strategy: STREAMING DATA TRANSFER (NO DISK I/O ON SOURCE)
Transfer container data directly via compressed stream, NO intermediate tarballs on source.
Use `pv` (pipe viewer) for colorful progress bars and rate monitoring.
---
## Why This is FASTEST
**NO disk writes on source** (99% full) - streaming only
**zstd compression** - 3-5x faster than gzip
**pv monitoring** - colorful progress, ETA, rate
**Parallel transfers** - migrate multiple containers simultaneously
**Immediate deletion** - frees space for next transfer
---
## Prerequisites
### On Source (82.29.59.188)
```bash
# Install required tools
sudo apt update && sudo apt install -y zstd pv
# List all containers
lxc list
```
### On Destination (63.141.255.9)
```bash
# Incus is already installed and clean (LXC/LXD removed)
# Create PROD-GBO network bridge
sudo incus network create PROD-GBO --type bridge ipv4.address=10.107.115.1/24
# Create PROD-GBO1 project
sudo incus project create PROD-GBO1
sudo incus project switch PROD-GBO1
# Configure project
sudo incus project set PROD-GBO1 features.storage.volumes=true
sudo incus project set PROD-GBO1 features.profiles=true
```
---
## Migration Workflow (Per Container)
### Step 1: Stop Container on Source
```bash
# On source server
lxc stop pragmatismo-alm
```
### Step 2: Stream Data to Destination (FASTEST - NO DISK I/O)
```bash
# On source server - stream compressed data directly
lxc exec pragmatismo-alm -- tar -cf - /opt/gbo/ | \
zstd -3 -q | \
pv -s $(du -sb /opt/gbo | awk '{print $1}') | \
ssh administrator@63.141.255.9 "zstd -d | tar -xf - -C ~/gbo/pragmatismo-alm/"
```
**What happens:**
1. `tar` reads /opt/gbo from container (streamed, no disk write)
2. `zstd -3` compresses on-the-fly (fast compression)
3. `pv` shows colorful progress with ETA and rate
4. `ssh` streams to destination
5. Destination decompresses and extracts directly to ~/gbo/pragmatismo-alm/
6. **NO tarball on source disk** - solves 99% full problem!
**pv Options:**
- `-s` : Total size (from `du -sb` for accurate progress)
- Colorful output with ETA, rate, elapsed time
### Step 3: Delete Container from Source
```bash
# Free space immediately for next transfer
lxc delete pragmatismo-alm
```
### Step 4: Create Fresh Incus Container
```bash
# On destination server
sudo incus launch images:debian/12/cloud pragmatismo-alm
# Add network
sudo incus config device add pragmatismo-alm eth0 nic name=eth0 network=PROD-GBO
# Set static IP
sudo incus config set pragmatismo-alm ipv4.address=10.107.115.4
# Create gbuser inside container
sudo incus exec pragmatismo-alm -- useradd -m -s /bin/bash gbuser
```
### Step 5: Push Data to Container
```bash
# Create directory structure
sudo incus exec pragmatismo-alm -- mkdir -p /opt/gbo
# Push data (recursive)
sudo incus file push -r ~/gbo/pragmatismo-alm/* pragmatismo-alm/opt/gbo/
# Fix ownership
sudo incus exec pragmatismo-alm -- chown -R gbuser:gbuser /opt/gbo
# Make binaries executable
sudo incus exec pragmatismo-alm -- chmod +x /opt/gbo/bin/*
```
### Step 6: Start Container and Verify Service
```bash
sudo incus start pragmatismo-alm
# Check service status
sudo incus exec pragmatismo-alm -- systemctl status alm
# Check logs
sudo incus exec pragmatismo-alm -- journalctl -u alm -f
```
### Step 7: Configure NAT Rules (ON HOST, NOT IN CONTAINER)
```bash
# On destination host (63.141.255.9)
# For ALM (example):
sudo iptables -t nat -A PREROUTING -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747
sudo iptables -t nat -A OUTPUT -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747
sudo iptables -A FORWARD -p tcp -d 10.107.115.4 --dport 4747 -j ACCEPT
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.4 -j MASQUERADE
# Save rules
sudo sh -c 'iptables-save > /etc/iptables/rules.v4'
```
---
## Full Migration Script (ALL CONTAINERS)
### Run Multiple Containers in Parallel (for speed)
```bash
#!/bin/bash
# MIGRATE-ALL.sh - Fast parallel migration
set -e
# Container list with IPs
declare -A CONTAINERS=(
["dns"]="10.107.115.155"
["email"]="10.107.115.200"
["webmail"]="10.107.115.208"
["alm"]="10.107.115.4"
["drive"]="10.107.115.114"
["tables"]="10.107.115.33"
["system"]="10.107.115.229"
["alm-ci"]="10.107.115.190"
["table-editor"]="10.107.115.73"
)
for container in "${!CONTAINERS[@]}"; do
ip="${CONTAINERS[$container]}"
echo -e "\e[1;32m=== Migrating $container ($ip) ===\e[0m"
# Step 1: Stop
echo -e "\e[1;33mStopping $container...\e[0m"
ssh root@82.29.59.188 "lxc stop $container" || true
# Step 2: Get size for pv
echo -e "\e[1;33mGetting size of /opt/gbo...\e[0m"
size=$(ssh root@82.29.59.188 "lxc exec $container -- du -sb /opt/gbo | awk '{print \$1}'")
# Step 3: Stream data (FAST!)
echo -e "\e[1;33mStreaming data to destination...\e[0m"
ssh root@82.29.59.188 "lxc exec $container -- tar -cf - /opt/gbo/" | \
zstd -3 -q | \
pv -s $size | \
ssh administrator@63.141.255.9 "mkdir -p ~/gbo/$container && zstd -d | tar -xf - -C ~/gbo/$container/"
# Step 4: Delete from source
echo -e "\e[1;33mDeleting $container from source...\e[0m"
ssh root@82.29.59.188 "lxc delete $container"
# Step 5: Create fresh container
echo -e "\e[1;33mCreating Incus container...\e[0m"
ssh administrator@63.141.255.9 "sudo incus launch images:debian/12/cloud $container && \
sudo incus config device add $container eth0 nic name=eth0 network=PROD-GBO && \
sudo incus config set $container ipv4.address=$ip && \
sudo incus exec $container -- useradd -m -s /bin/bash gbuser"
# Step 6: Push data
echo -e "\e[1;33mPushing data to container...\e[0m"
ssh administrator@63.141.255.9 "sudo incus exec $container -- mkdir -p /opt/gbo && \
sudo incus file push -r ~/gbo/$container/* $container:/opt/gbo/ && \
sudo incus exec $container -- chown -R gbuser:gbuser /opt/gbo && \
sudo incus exec $container -- chmod +x /opt/gbo/bin/*"
# Step 7: Start
echo -e "\e[1;33mStarting $container...\e[0m"
ssh administrator@63.141.255.9 "sudo incus start $container"
echo -e "\e[1;32m✓ $container migrated successfully!\e[0m"
echo ""
done
echo -e "\e[1;32m=== ALL MIGRATIONS COMPLETE ===\e[0m"
```
---
## Single Container Migration (Quick Test)
```bash
#!/bin/bash
# migrate-one.sh - Migrate single container
CONTAINER=$1
if [ -z "$CONTAINER" ]; then
echo "Usage: $0 <container-name>"
exit 1
fi
echo -e "\e[1;32m=== Migrating $CONTAINER ===\e[0m"
# Stop on source
echo -e "\e[1;33m[1/7] Stopping container...\e[0m"
ssh root@82.29.59.188 "lxc stop $CONTAINER"
# Get size
echo -e "\e[1;33m[2/7] Getting data size...\e[0m"
SIZE=$(ssh root@82.29.59.188 "lxc exec $CONTAINER -- du -sb /opt/gbo | awk '{print \$1}'")
# Stream data (NO DISK WRITE!)
echo -e "\e[1;33m[3/7] Streaming data (pv will show progress)...\e[0m"
ssh root@82.29.59.188 "lxc exec $CONTAINER -- tar -cf - /opt/gbo/" | \
zstd -3 -q | \
pv -s $SIZE | \
ssh administrator@63.141.255.9 "mkdir -p ~/gbo/$CONTAINER && zstd -d | tar -xf - -C ~/gbo/$CONTAINER/"
# Delete from source
echo -e "\e[1;33m[4/7] Deleting from source...\e[0m"
ssh root@82.29.59.188 "lxc delete $CONTAINER"
# Create container
echo -e "\e[1;33m[5/7] Creating Incus container...\e[0m"
ssh administrator@63.141.255.9 "sudo incus launch images:debian/12/cloud $CONTAINER && \
sudo incus config device add $CONTAINER eth0 nic name=eth0 network=PROD-GBO && \
sudo incus exec $CONTAINER -- useradd -m -s /bin/bash gbuser"
# Push data
echo -e "\e[1;33m[6/7] Pushing data...\e[0m"
ssh administrator@63.141.255.9 "sudo incus exec $CONTAINER -- mkdir -p /opt/gbo && \
sudo incus file push -r ~/gbo/$CONTAINER/* $CONTAINER:/opt/gbo/ && \
sudo incus exec $CONTAINER -- chown -R gbuser:gbuser /opt/gbo && \
sudo incus exec $CONTAINER -- chmod +x /opt/gbo/bin/*"
# Start
echo -e "\e[1;33m[7/7] Starting container...\e[0m"
ssh administrator@63.141.255.9 "sudo incus start $CONTAINER"
echo -e "\e[1;32m✓ Migration complete! Check with: incus list\e[0m"
```
---
## Port Forwarding (iptables NAT) - Complete Rules
```bash
# Enable IP forwarding (persistent)
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-ipforward.conf
sudo sysctl -w net.ipv4.ip_forward=1
# Enable route_localnet
echo "net.ipv4.conf.all.route_localnet = 1" | sudo tee /etc/sysctl.d/99-localnet.conf
sudo sysctl -w net.ipv4.conf.all.route_localnet=1
# DNS (53)
sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination 10.107.115.155:53
sudo iptables -t nat -A PREROUTING -p tcp --dport 53 -j DNAT --to-destination 10.107.115.155:53
sudo iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 10.107.115.155:53
sudo iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to-destination 10.107.115.155:53
# Email (25,465,587,993,995,143,110,4190)
sudo iptables -t nat -A PREROUTING -p tcp --dport 25 -j DNAT --to-destination 10.107.115.200:25
sudo iptables -t nat -A PREROUTING -p tcp --dport 465 -j DNAT --to-destination 10.107.115.200:465
sudo iptables -t nat -A PREROUTING -p tcp --dport 587 -j DNAT --to-destination 10.107.115.200:587
sudo iptables -t nat -A PREROUTING -p tcp --dport 993 -j DNAT --to-destination 10.107.115.200:993
sudo iptables -t nat -A PREROUTING -p tcp --dport 995 -j DNAT --to-destination 10.107.115.200:995
sudo iptables -t nat -A PREROUTING -p tcp --dport 143 -j DNAT --to-destination 10.107.115.200:143
sudo iptables -t nat -A PREROUTING -p tcp --dport 110 -j DNAT --to-destination 10.107.115.200:110
sudo iptables -t nat -A PREROUTING -p tcp --dport 4190 -j DNAT --to-destination 10.107.115.200:4190
sudo iptables -t nat -A OUTPUT -p tcp --dport 25 -j DNAT --to-destination 10.107.115.200:25
sudo iptables -t nat -A OUTPUT -p tcp --dport 465 -j DNAT --to-destination 10.107.115.200:465
sudo iptables -t nat -A OUTPUT -p tcp --dport 587 -j DNAT --to-destination 10.107.115.200:587
sudo iptables -t nat -A OUTPUT -p tcp --dport 993 -j DNAT --to-destination 10.107.115.200:993
sudo iptables -t nat -A OUTPUT -p tcp --dport 995 -j DNAT --to-destination 10.107.115.200:995
sudo iptables -t nat -A OUTPUT -p tcp --dport 143 -j DNAT --to-destination 10.107.115.200:143
sudo iptables -t nat -A OUTPUT -p tcp --dport 110 -j DNAT --to-destination 10.107.115.200:110
sudo iptables -t nat -A OUTPUT -p tcp --dport 4190 -j DNAT --to-destination 10.107.115.200:4190
# Webmail (5252)
sudo iptables -t nat -A PREROUTING -p tcp --dport 5252 -j DNAT --to-destination 10.107.115.208:5252
sudo iptables -t nat -A OUTPUT -p tcp --dport 5252 -j DNAT --to-destination 10.107.115.208:5252
# ALM (4747)
sudo iptables -t nat -A PREROUTING -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747
sudo iptables -t nat -A OUTPUT -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747
# Tables/PostgreSQL (5432)
sudo iptables -t nat -A PREROUTING -p tcp --dport 5432 -j DNAT --to-destination 10.107.115.33:5432
sudo iptables -t nat -A OUTPUT -p tcp --dport 5432 -j DNAT --to-destination 10.107.115.33:5432
# Proxy/Caddy (80, 443)
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.107.115.236:80
sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 10.107.115.236:443
sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 10.107.115.236:80
sudo iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 10.107.115.236:443
# FORWARD rules (allow traffic to containers)
sudo iptables -A FORWARD -p tcp -d 10.107.115.155 -j ACCEPT
sudo iptables -A FORWARD -p udp -d 10.107.115.155 -j ACCEPT
sudo iptables -A FORWARD -p tcp -d 10.107.115.200 -j ACCEPT
sudo iptables -A FORWARD -p tcp -s 10.107.115.200 -j ACCEPT
sudo iptables -A FORWARD -p tcp -d 10.107.115.208 -j ACCEPT
sudo iptables -A FORWARD -p tcp -d 10.107.115.4 -j ACCEPT
sudo iptables -A FORWARD -p tcp -d 10.107.115.33 -j ACCEPT
sudo iptables -A FORWARD -p tcp -d 10.107.115.236 -j ACCEPT
sudo iptables -A FORWARD -p tcp -s 10.107.115.236 -j ACCEPT
# POSTROUTING MASQUERADE (return traffic)
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.155 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -p udp -d 10.107.115.155 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.200 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.208 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.4 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.33 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.236 -j MASQUERADE
# Save rules
sudo sh -c 'iptables-save > /etc/iptables/rules.v4'
```
---
## Benefits of This Approach
**FASTEST** - No intermediate tarballs, pure streaming
**COLORFUL** - pv shows beautiful progress bars
**EFFICIENT** - zstd compression (3-5x gzip)
**SAFE** - One container at a time, verify before next
**CLEAN** - Immediate deletion frees disk space
**MONITORED** - Real-time transfer rate and ETA
**PARALLEL** - Can migrate multiple containers at once
**Estimated time:**
- Small containers (<1GB): ~5-10 minutes each
- Medium containers (1-5GB): ~10-20 minutes each
- Large containers (>5GB): ~20-40 minutes each
- Total (parallel): ~1-2 hours for all containers
vs lxc export method: ~4-8 hours total
---
## Quick Start
```bash
# 1. Install pv on source
ssh root@82.29.59.188 "apt install -y pv zstd"
# 2. Save migration scripts
# Copy migrate-one.sh and MIGRATE-ALL.sh
# 3. Run single test
./migrate-one.sh pragmatismo-alm
# 4. If successful, run all
./MIGRATE-ALL.sh
```
---
## Troubleshooting
### pv not showing progress?
```bash
# Check pv is installed
pv --version
# Check size detection
ssh root@82.29.59.188 "lxc exec pragmatismo-alm -- du -sb /opt/gbo"
```
### Container not starting?
```bash
# Check logs
sudo incus list
sudo incus info pragmatismo-alm
sudo incus logs pragmatismo-alm --show-log
# Check network
sudo incus exec pragmatismo-alm -- ip addr
```
### Service not running?
```bash
# Inside container
sudo incus exec pragmatismo-alm -- bash
systemctl status alm
journalctl -u alm -n 50
```
### NAT not working?
```bash
# Check rules
sudo iptables -t nat -L -n -v
# Check forwarding
sudo iptables -L FORWARD -n -v
# Test from host
nc -zv 127.0.0.1 4747
```
---
## Summary
This plan achieves:
**FASTEST possible migration** - streaming, no disk I/O
**Colorful progress** - pv with ETA, rate, elapsed
**Immediate space cleanup** - delete as we go
**Parallel migration** - do multiple containers at once
**Clean Incus** - fresh start, no LXC/LXD trash
**Proper NAT** - iptables only, no socat/proxy devices
**No mbuffer needed** - pv does the job with colorful output!
---
## Verification Checklist
After each migration:
- [ ] Container running: `sudo incus list`
- [ ] Service active: `sudo incus exec $c -- systemctl status <service>`
- [ ] Data intact: `sudo incus exec $c -- ls -la /opt/gbo`
- [ ] Port accessible: `nc -zv 127.0.0.1 <port>`
- [ ] Source deleted: `ssh root@82.29.59.188 lxc list` (should not show container)
- [ ] NAT rules added: `sudo iptables -t nat -L -n | grep <ip>`
After all migrations:
- [ ] All containers running on Incus
- [ ] All services active
- [ ] All NAT rules configured
- [ ] External access works
- [ ] Source server: no containers left

View file

@ -6,6 +6,33 @@ This document describes how to improve `installer.rs` to automate the deployment
---
# rust incus install base
sudo apt install -y curl gnupg
sudo mkdir -p /etc/apt/keyrings
sudo curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc
sudo sh -c 'cat > /etc/apt/sources.list.d/zabbly-incus-stable.sources << EOF
Enabled: yes
Types: deb
URIs: https://pkgs.zabbly.com/incus/stable
Suites: bookworm
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/zabbly.asc
EOF'
sudo apt update
sudo apt install incus
## What Was Done Manually (Reference Implementation)
### Migration Summary (pragmatismo tenant)
@ -449,8 +476,6 @@ mail IN A <HOST_IP>
---
## Container Cleanup (BEFORE Setting Up NAT)
**ALWAYS remove socat and Incus proxy devices before configuring iptables NAT:**
```bash

View file

@ -1,11 +0,0 @@
#!/bin/bash
# fail2ban startup script for Incus containers
# Usage: Place in /opt/gbo/bin/ and run as root in container
LOGFILE=/opt/gbo/logs/fail2ban.log
mkdir -p /opt/gbo/logs
nohup /usr/bin/fail2ban-server -x -f > $LOGFILE 2>&1 &
sleep 2
fail2ban-client reload
echo "Fail2ban started - check status with: fail2ban-client status"

View file

@ -1,15 +0,0 @@
# Production Smoke Test Plan
## Status: Caddy running ✅
## URLs to Test (YOLO)
1. https://webmail.pragmatismo.com.br/
2. https://chat.pragmatismo.com.br/cristo
3. https://alm.pragmatismo.com.br/
4. https://tables.pragmatismo.com.br/
## Steps
1. Navigate to each URL
2. Take screenshot
3. Check for errors in console/network
4. Report pass/fail per URL

View file

@ -1,18 +0,0 @@
# LXC to Incus Migration Script
```bash
#!/bin/bash
# Export all LXC containers and transfer to destination
containers="pragmatismo-alm pragmatismo-alm-ci pragmatismo-dns pragmatismo-drive pragmatismo-email pragmatismo-proxy pragmatismo-system pragmatismo-table-editor pragmatismo-tables pragmatismo-webmail"
for name in $containers; do
echo "=== Migrating $name ==="
ssh root@pragmatismo.com.br "lxc export $name /tmp/$name.tar.gz"
scp root@pragmatismo.com.br:/tmp/$name.tar.gz administrator@63.141.255.9:~/
ssh root@pragmatismo.com.br "rm /tmp/$name.tar.gz"
echo "✓ $name transferred"
done
echo "Migration complete. All .tar.gz files in ~/ on destination."
```

View file

@ -1,244 +0,0 @@
# General Bots — Plano de Execução no Windows
## Pré-requisitos
- Windows 10/11 (x64)
- Visual Studio Build Tools (com C++ workload) ou Visual Studio
- Rust toolchain (`rustup` com `stable-x86_64-pc-windows-msvc`)
- Git para Windows
---
## 1. Dependências de Build
### 1.1 PostgreSQL (libpq.lib para Diesel ORM)
O BotServer usa Diesel com backend Postgres. No Windows, o linker precisa de `libpq.lib`.
```powershell
# Baixar binários do PostgreSQL (não precisa instalar o serviço)
Invoke-WebRequest -Uri "https://get.enterprisedb.com/postgresql/postgresql-17.4-1-windows-x64-binaries.zip" -OutFile "$env:TEMP\pgsql.zip" -UseBasicParsing
# Extrair para C:\pgsql
Expand-Archive -Path "$env:TEMP\pgsql.zip" -DestinationPath "C:\pgsql" -Force
# Configurar variável de ambiente permanente
[System.Environment]::SetEnvironmentVariable("PQ_LIB_DIR", "C:\pgsql\pgsql\lib", "User")
$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib"
$env:PATH = "C:\pgsql\pgsql\bin;$env:PATH"
```
Ou simplesmente execute:
```powershell
.\DEPENDENCIES.ps1
```
### 1.2 sccache (opcional)
O `.cargo/config.toml` referencia `sccache`. Se não estiver instalado, comente a linha:
```toml
# [build]
# rustc-wrapper = "sccache"
```
---
## 2. Compilação
```powershell
# Garantir que PQ_LIB_DIR está configurado
$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib"
# Build do botserver
cargo build -p botserver
# Build do botui (interface desktop Tauri)
cargo build -p botui
```
### Correções de compilação aplicadas
| Arquivo | Problema | Correção |
|---------|----------|----------|
| `bootstrap.rs` | `use std::os::unix::fs::PermissionsExt` | Envolvido com `#[cfg(unix)]` |
| `antivirus.rs` | `error!` macro não importada | Adicionado `use tracing::error` |
| `installer.rs` | `check_root()` só existe no Unix | Adicionado `check_admin()` para Windows com `#[cfg(windows)]` |
| `installer.rs` | `SUDOERS_CONTENT` não usado no Windows | Envolvido com `#[cfg(not(windows))]` |
| `facade.rs` | `make_executable` param `path` não usado | Renomeado para `_path` no Windows |
---
## 3. Compatibilidade de Runtime (3rdparty.toml)
O `3rdparty.toml` agora tem entradas `_windows` para cada componente:
| Componente | Linux | Windows |
|------------|-------|---------|
| `drive` | `minio` (linux-amd64) | `minio.exe` (windows-amd64) |
| `tables` | `postgresql-*-linux-gnu.tar.gz` | `postgresql-*-windows-x64-binaries.zip` |
| `cache` | `valkey-*-jammy-x86_64.tar.gz` | `memurai-developer.msi` |
| `llm` | `llama-*-ubuntu-x64.zip` | `llama-*-win-cpu-x64.zip` (ou CUDA/Vulkan) |
| `email` | `stalwart-mail-*-linux.tar.gz` | `stalwart-mail-*-windows.zip` |
| `proxy` | `caddy_*_linux_amd64.tar.gz` | `caddy_*_windows_amd64.zip` |
| `directory` | `zitadel-linux-amd64.tar.gz` | `zitadel-windows-amd64.zip` |
| `alm` | `forgejo-*-linux-amd64` | `forgejo-*-windows-amd64.exe` |
| `alm_ci` | `forgejo-runner-*-linux-amd64` | `forgejo-runner-*-windows-amd64.exe` |
| `dns` | `coredns_*_linux_amd64.tgz` | `coredns_*_windows_amd64.tgz` |
| `meet` | `livekit_*_linux_amd64.tar.gz` | `livekit_*_windows_amd64.zip` |
| `table_editor` | `nocodb-linux-x64` | `nocodb-win-x64.exe` |
| `vector_db` | `qdrant-*-linux-gnu.tar.gz` | `qdrant-*-windows-msvc.zip` |
| `timeseries_db` | `influxdb2-*-linux-amd64.tar.gz` | `influxdb2-*-windows-amd64.zip` |
| `vault` | `vault_*_linux_amd64.zip` | `vault_*_windows_amd64.zip` |
| `observability` | `vector-*-linux-gnu.tar.gz` | `vector-*-windows-msvc.zip` |
A seleção é automática via `get_component_url()` em `installer.rs` que busca `{name}_windows` primeiro.
---
## 4. Adaptações de Código para Windows
### 4.1 Execução de Comandos Shell
| Contexto | Linux | Windows |
|----------|-------|---------|
| `run_commands_with_password()` | `bash -c "{cmd}"` | `powershell -NoProfile -Command "{cmd}"` |
| `start()` (spawnar processos) | `sh -c "{exec_cmd}"` | `powershell -NoProfile -Command "{exec_cmd}"` |
| `safe_sh_command()` | `sh -c` | `powershell -NoProfile -Command` |
### 4.2 Gerenciamento de Processos
| Ação | Linux | Windows |
|------|-------|---------|
| Matar processos | `pkill -9 -f {name}` | `taskkill /F /IM {name}*` |
| Verificar processo | `pgrep -f {name}` | `Get-Process \| Where-Object { $_.ProcessName -like '*{name}*' }` |
### 4.3 Health Checks (fallback quando curl não está disponível)
| Check | Linux | Windows |
|-------|-------|---------|
| Porta aberta | `nc -z -w 1 127.0.0.1 {port}` | `(Test-NetConnection -ComputerName 127.0.0.1 -Port {port}).TcpTestSucceeded` |
### 4.4 Extração de Arquivos
| Formato | Linux | Windows |
|---------|-------|---------|
| `.zip` | `unzip -o -q {file}` | `Expand-Archive -Path '{file}' -DestinationPath '{dest}' -Force` |
| `.tar.gz` | `tar -xzf {file}` | `tar -xzf {file}` (Windows 10+ tem tar nativo) |
---
## 5. Execução
### 5.1 Modo CLI (gerenciar componentes)
```powershell
$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib"
$env:PATH = "C:\pgsql\pgsql\bin;$env:PATH"
# Instalar um componente específico
.\target\debug\botserver.exe install vault
# Listar componentes
.\target\debug\botserver.exe list
# Iniciar componentes
.\target\debug\botserver.exe start
# Ver status
.\target\debug\botserver.exe status
```
### 5.2 Modo Servidor (bootstrap completo + HTTP)
```powershell
$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib"
$env:PATH = "C:\pgsql\pgsql\bin;$env:PATH"
# Executa bootstrap (baixa/instala todos os componentes) + inicia servidor HTTP
.\target\debug\botserver.exe
```
O bootstrap automático:
1. Baixa e instala Vault, PostgreSQL, Valkey, MinIO, Zitadel, LLM, etc.
2. Gera certificados TLS
3. Inicializa o banco de dados
4. Inicia o servidor HTTP na porta **5858**
Acesse: **http://localhost:5858**
### 5.3 Via restart.ps1
```powershell
.\restart.ps1
```
---
## 6. Detecção Automática de GPU (LLM)
O sistema detecta automaticamente:
- **CUDA 13.x**`llama-*-win-cuda-13.1-x64.zip`
- **CUDA 12.x**`llama-*-win-cuda-12.4-x64.zip`
- **Vulkan SDK**`llama-*-win-vulkan-x64.zip`
- **CPU only**`llama-*-win-cpu-x64.zip`
- **ARM64**`llama-*-win-cpu-arm64.zip`
---
## 7. Estrutura de Diretórios
```
botserver-stack/
├── bin/ # Binários dos componentes
│ ├── vault/
│ ├── tables/ # PostgreSQL
│ ├── cache/ # Valkey/Memurai
│ ├── drive/ # MinIO
│ ├── directory/ # Zitadel
│ ├── llm/ # llama.cpp
│ └── ...
├── conf/ # Configurações
│ ├── vault/
│ ├── system/certificates/
│ └── ...
├── data/ # Dados persistentes
│ ├── vault/
│ ├── tables/pgdata/
│ └── ...
└── logs/ # Logs de cada componente
├── vault/
├── tables/
└── ...
botserver-installers/ # Cache de downloads (reutilizado)
```
---
## 8. Troubleshooting
### Erro: `LNK1181: libpq.lib não encontrado`
```powershell
$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib"
# Ou execute .\DEPENDENCIES.ps1
```
### Erro: `sccache não encontrado`
Comente no `.cargo/config.toml`:
```toml
# [build]
# rustc-wrapper = "sccache"
```
### Erro: `Path traversal detected`
Limpe o cache e recompile:
```powershell
Remove-Item -Path ".\botserver-stack" -Recurse -Force
Remove-Item -Path ".\botserver-installers" -Recurse -Force
cargo clean -p botserver
cargo build -p botserver
```
### Componentes baixam versão Linux
Recompile o botserver para que o `3rdparty.toml` embutido seja atualizado:
```powershell
cargo clean -p botserver
cargo build -p botserver
```

View file

@ -1,242 +0,0 @@
# BASIC Workflow Engine Plan
## Current State
- `workflow_executions` and `workflow_events` tables exist in DB
- `WorkflowExecution` / `WorkflowEvent` models exist in `core/shared/models/workflow_models.rs`
- `ORCHESTRATE WORKFLOW` keyword exists in `basic/keywords/orchestration.rs` (stub)
- `STEP` keyword registered but not durable
- Compiler (`basic/mod.rs`) produces Rhai AST and runs it in one shot via `engine.eval_ast_with_scope`
- `HEAR` currently blocks a thread (new) — works but not crash-safe
---
## Goal
BASIC scripts run as **durable step sequences**. Each keyword is a step. On crash/restart, execution resumes from the last completed step. No re-run. No Rhai for control flow.
```basic
' ticket.bas
TALK "Describe the issue" ← Step 1
HEAR description ← Step 2 (suspends, waits)
SET ticket = CREATE(description) ← Step 3
TALK "Ticket #{ticket} created" ← Step 4
```
---
## Two Execution Modes
The compiler serves both modes via a pragma at the top of the `.bas` file:
```basic
' Default: Rhai mode (current behavior, fast, no durability)
TALK "Hello"
' Workflow mode (durable, crash-safe)
#workflow
TALK "Hello"
HEAR name
```
`ScriptService::compile()` detects `#workflow` and returns either:
- `ExecutionPlan::Rhai(AST)` — current path, unchanged
- `ExecutionPlan::Workflow(Vec<Step>)` — new path
`ScriptService::run()` dispatches accordingly.
---
## Architecture
### 1. Compiler changes (`basic/mod.rs`, `basic/compiler/`)
Add `compile_to_steps(script: &str) -> Result<Vec<Step>>`:
```rust
pub enum Step {
Talk { template: String },
Hear { variable: String, input_type: String },
Set { variable: String, expression: String },
If { condition: String, then_steps: Vec<Step>, else_steps: Vec<Step> },
Call { function: String, args: Vec<String>, result_var: Option<String> },
// ... one variant per keyword
}
```
Expressions inside steps (`condition`, `expression`, `template`) are still evaluated by Rhai — but only as **pure expression evaluator**, no custom syntax, no side effects. This keeps Rhai as a math/string engine only.
### 2. WorkflowEngine (`basic/workflow/engine.rs`)
```rust
pub struct WorkflowEngine {
state: Arc<AppState>,
session: UserSession,
}
impl WorkflowEngine {
/// Start a new workflow or resume existing one for this session+script
pub async fn run(&self, script_path: &str, steps: Vec<Step>) -> Result<()>
/// Execute one step, persist result, return next action
async fn execute_step(&self, exec_id: Uuid, step: &Step, vars: &mut Variables) -> StepResult
/// Load execution state from DB
async fn load_state(&self, exec_id: Uuid) -> (usize, Variables)
/// Persist step completion
async fn save_state(&self, exec_id: Uuid, step_index: usize, vars: &Variables, status: &str)
}
pub enum StepResult {
Continue, // go to next step
Suspend, // HEAR — save state, return, wait for next message
Done, // script finished
}
```
### 3. HEAR in workflow mode
No thread blocking. Instead:
1. `execute_step(Hear)` saves state to `workflow_executions` with `status = "waiting"`, `current_step = N`
2. Returns `StepResult::Suspend` → engine returns to caller
3. Next user message → `stream_response` checks `workflow_executions` for `session_id` with `status = "waiting"`
4. Loads variables, sets `variables["description"] = user_input`, advances `current_step`, resumes
### 4. `stream_response` dispatch (`core/bot/mod.rs`)
```rust
// At top of stream_response, before LLM:
if let Some(exec) = WorkflowEngine::find_waiting(state, session_id).await {
WorkflowEngine::resume(state, exec, message_content).await?;
return Ok(());
}
```
### 5. DB schema (already exists, minor additions)
```sql
-- Already exists:
workflow_executions (id, bot_id, workflow_name, current_step, state_json, status, ...)
-- Add:
ALTER TABLE workflow_executions ADD COLUMN session_id UUID;
ALTER TABLE workflow_executions ADD COLUMN script_path TEXT;
-- state_json stores: { "variables": {...}, "step_index": N }
```
---
## Migration Path
### Phase 1 — Parallel mode (no breaking changes)
- Add `compile_to_steps()` alongside existing `compile()`
- Add `WorkflowEngine` as new struct
- `#workflow` pragma routes to new path
- All existing `.bas` files unchanged, run via Rhai as before
### Phase 2 — Keyword parity
Implement step variants for all keywords used in practice:
`TALK`, `HEAR`, `SET`, `IF/ELSE/END IF`, `CALL` (HTTP, LLM, tool), `SEND MAIL`, `SCHEDULE`
### Phase 3 — Default for new scripts
New `.bas` files default to workflow mode. Rhai mode kept for backwards compat and tool scripts (short-lived, no HEAR).
### Phase 4 — Rhai scope reduction
Remove Rhai custom syntax registrations. Keep Rhai only as expression evaluator:
```rust
engine.eval_expression::<Dynamic>(&expr, &scope)
```
---
## File Map
```
basic/
mod.rs ← add compile_to_steps(), ExecutionPlan enum
compiler/
mod.rs ← existing Rhai compiler, unchanged
step_compiler.rs ← NEW: BASIC → Vec<Step>
workflow/
mod.rs ← NEW: WorkflowEngine
engine.rs ← NEW: execute_step, load/save state
variables.rs ← NEW: Variables (HashMap<String, Dynamic>)
steps.rs ← NEW: Step enum
keywords/ ← existing, unchanged in Phase 1
```
---
## Keyword Compatibility
### Category A — Workflow steps (implement as `Step` variants)
`TALK`, `HEAR`, `SET`, `IF/ELSE/END IF`, `SEND MAIL`, `SEND TEMPLATE`, `SCHEDULE`,
`SAVE`/`INSERT`/`UPDATE`, `GET`, `FIND`, `SEARCH`, `USE KB`, `USE TOOL`, `REMEMBER`,
`HTTP GET/POST/PUT/DELETE`, `WAIT`, `TRANSFER TO HUMAN`, `CREATE TASK`, `BOOK`, `SCORE LEAD`
### Category B — Pure expressions (Rhai as calculator, no step boundary)
`math/*`, `datetime/*`, `string_functions`, `arrays/*`, `core_functions`, `validation/*`, `FORMAT`
→ Stored as expression strings in Step, evaluated at runtime via `engine.eval_expression_with_scope()`
### Category C — Rhai-only (scripts using these stay in Rhai mode, no `#workflow`)
`code_sandbox`, `use_website`, `face_api`, `on_change`, `on_email`, `webhook`,
`procedures` (FUNCTION/SUB/CALL), `for_next` (FOR EACH loops), `switch_case`, `events`, `orchestration`
A script with any Category C keyword cannot use `#workflow`. The compiler detects this and errors early.
---
## How Compilation Works Without Rhai
Workflow compiler is a **line-by-line parser**, not a Rhai AST walk:
```
Input line → Step variant
─────────────────────────────────────────────────────
TALK "Hello ${name}" → Step::Talk { template }
HEAR description → Step::Hear { var, input_type }
SET x = score + 1 → Step::Set { var, expr: "score + 1" }
IF score > 10 THEN → Step::If { cond: "score > 10", then_steps, else_steps }
SEND MAIL to, s, b → Step::SendMail { to, subject, body }
USE TOOL path → Step::UseTool { path, args }
```
Expressions (`score + 1`, `score > 10`) are stored as **raw strings** in the Step struct.
At runtime, Rhai evaluates them as pure expressions — no custom syntax, no side effects:
```rust
let mut engine = Engine::new(); // no register_custom_syntax calls
let mut scope = Scope::new();
for (k, v) in &variables { scope.push_dynamic(k, v.clone()); }
let result = engine.eval_expression_with_scope::<Dynamic>(&mut scope, expr)?;
```
Rhai remains a dependency but is used only as a math/string expression evaluator (~5 lines of code at runtime). All custom keyword machinery is bypassed entirely.
---
| Engine | Lang | Latency | RAM | Rust SDK | Verdict |
|--------|------|---------|-----|----------|---------|
| **Custom (this plan)** | Rust | ~1ms | 0 extra | Native | ✅ Best fit |
| **[Restate](https://restate.dev)** | Rust server | ~5ms | ~50MB | ✅ official | Fallback option |
| **[Rhythm](https://github.com/maxnorth/rhythm)** | Rust | ~2ms | ~10MB | Native | Experimental |
| **Temporal** | Go+Java | ~20ms | ~500MB | ❌ | Too heavy |
| **Windmill** | Rust+TS | ~10ms | ~200MB | ❌ | Wrong abstraction |
**Why custom over Restate:** Restate requires its own server as a proxy between HTTP requests and handlers — adds a network hop and an extra process. The custom plan uses PostgreSQL already running in the stack, zero extra infrastructure.
**Escape hatch:** The `Step` enum in this plan maps 1:1 to Restate workflow steps. If the custom engine proves too complex to maintain, migration to Restate is mechanical — swap `WorkflowEngine::execute_step` internals, keep the compiler and Step enum unchanged.
---
- **No re-run ever.** Steps before current_step are skipped on resume.
- **Rhai never removed entirely** — used for expression eval only.
- **Backwards compatible** — no `#workflow` = Rhai mode, existing bots unaffected.
- **HEAR in workflow mode = zero threads held.** State in DB, not RAM.
- **Tool scripts** (called by LLM) stay in Rhai mode — they're short-lived, no HEAR needed.

View file

@ -4,21 +4,38 @@ set -e
echo "Stopping..."
pkill -f botserver || true
pkill -f botui || true
pkill -f botmodels || true
pkill -f rustc || true
echo "Cleaning..."
rm -f botserver.log botui.log
rm -f botserver.log botui.log botmodels.log
echo "Building..."
cargo build -p botserver
cargo build -p botui
echo "Starting botmodels..."
cd botmodels
source venv/bin/activate
uvicorn src.main:app --host 0.0.0.0 --port 8085 > ../botmodels.log 2>&1 &
echo " PID: $!"
cd ..
echo "Waiting for botmodels..."
for i in $(seq 1 30); do
if curl -s http://localhost:8085/api/health > /dev/null 2>&1; then
echo " botmodels ready"
break
fi
sleep 1
done
echo "Starting botserver..."
RUST_LOG=debug ./target/debug/botserver --noconsole > botserver.log 2>&1 &
BOTMODELS_HOST="http://localhost:8085" BOTMODELS_API_KEY="starter" RUST_LOG=debug ./target/debug/botserver --noconsole > botserver.log 2>&1 &
echo " PID: $!"
echo "Starting botui..."
BOTSERVER_URL="http://localhost:8080" ./target/debug/botui > botui.log 2>&1 &
echo " PID: $!"
echo "Done. Logs: tail -f botserver.log botui.log"
echo "Done. Logs: tail -f botserver.log botui.log botmodels.log"

154
usekb2.md Normal file
View file

@ -0,0 +1,154 @@
# USE KB 2.0: Group-Based Knowledge Base Access
## Overview
Modify the USE KB keyword to respect user group permissions, ensuring that THINK KB queries only return answers from knowledge base folders that belong to groups the logged-in user is a member of.
## Current Architecture
### USE KB Flow
1. User executes `USE KB "kb_name"` in BASIC script
2. `use_kb.rs:add_kb_to_session()` checks if KB exists in `kb_collections`
3. Creates default KB entry if not found
4. Adds association to `session_kb_associations` table
5. KB becomes active for the session
### THINK KB Flow
1. User executes `THINK KB "query"`
2. `think_kb.rs:think_kb_search()` gets all active KBs from `session_kb_associations`
3. For each active KB, calls `KnowledgeBaseManager.search()` on its Qdrant collection
4. Returns combined results from all active KBs
### Group System
- Groups stored in `rbac_groups` table
- User membership in `rbac_user_groups` table
- Group permissions via `rbac_group_roles` table
## Proposed Changes
### 1. Database Schema Changes
Add new table `kb_group_associations`:
```sql
CREATE TABLE kb_group_associations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
kb_id UUID NOT NULL REFERENCES kb_collections(id) ON DELETE CASCADE,
group_id UUID NOT NULL REFERENCES rbac_groups(id) ON DELETE CASCADE,
granted_by UUID REFERENCES users(id) ON DELETE SET NULL,
granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(kb_id, group_id)
);
```
Migration file: `botserver/migrations/6.2.0-01-kb-groups/up.sql`
### 2. Backend Logic Changes
#### Modify `think_kb_search()` in `think_kb.rs`
- Add user group lookup before searching
- Filter active KBs to only those accessible by user's groups
- Allow access if KB has no group associations (public KBs) OR user is in associated groups
```rust
async fn think_kb_search(
kb_manager: Arc<KnowledgeBaseManager>,
db_pool: DbPool,
session_id: Uuid,
bot_id: Uuid,
user_id: Uuid, // Add user_id parameter
query: &str,
) -> Result<serde_json::Value, String> {
// Get user's groups
let user_groups = get_user_groups(&db_pool, user_id)?;
// Get active KBs filtered by groups
let accessible_kbs = get_accessible_kbs_for_session(&db_pool, session_id, &user_groups)?;
// Search only accessible KBs
// ... rest of search logic
}
```
#### Add `get_accessible_kbs_for_session()` function
```rust
fn get_accessible_kbs_for_session(
conn_pool: &DbPool,
session_id: Uuid,
user_groups: &[String],
) -> Result<Vec<(String, String, String)>, String> {
// Query that joins session_kb_associations with kb_group_associations
// Returns KBs where group_id IS NULL (public) OR group_id IN user_groups
}
```
#### Modify `add_kb_to_session()` in `use_kb.rs`
- Add optional group access check
- Allow USE KB if user has access to the KB's groups
### 3. API Changes
Add new endpoints in `rbac.rs` for KB-group management:
```rust
// Assign KB to group
POST /api/rbac/kbs/{kb_id}/groups/{group_id}
// Remove KB from group
DELETE /api/rbac/kbs/{kb_id}/groups/{group_id}
// Get groups for KB
GET /api/rbac/kbs/{kb_id}/groups
// Get KBs accessible by user
GET /api/rbac/users/{user_id}/accessible-kbs
```
### 4. Frontend Changes
#### Update `botui/ui/suite/admin/groups.html`
- Add "Knowledge Bases" tab to group detail panel
- Show list of KBs assigned to the group
- Allow adding/removing KB assignments
#### Update `botui/ui/suite/drive/drive.html`
- Add group visibility indicators for KB folders
- Show which groups have access to each KB
### 5. Migration Strategy
1. Create new migration for `kb_group_associations` table
2. Run migration to create table
3. Assign existing KBs to default groups (e.g., "all_users" group)
4. Update application code
5. Deploy and test
### 6. Backward Compatibility
- Existing KBs without group associations remain public
- Existing USE KB calls continue to work
- THINK KB will filter results based on new permissions
## Implementation Steps
1. ✅ Database migration for kb_group_associations
2. ✅ Modify think_kb_search to accept user_id and filter by groups
3. ✅ Update THINK KB keyword registration to pass user_id
4. ✅ Add group access check to USE KB
5. ✅ Add API endpoints for KB-group management
6. ✅ Update admin UI for group-KB assignment
7. ✅ Update drive UI to show group access
8. ✅ Add tests for group-based access control
## Security Considerations
- All KB access checks must happen at the database level
- No client-side filtering of search results
- Group membership verified on each request
- Audit logging for KB access attempts
## Testing
- Unit tests for group access functions
- Integration tests for THINK KB with group filtering
- UI tests for admin group-KB management
- End-to-end tests with different user group scenarios