OpenClaw concepts on HIX

mod_harbour is an Apache module that allows to run PRGs directly on the web !!!
Post Reply
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

OpenClaw concepts on HIX

Post by Antonio Linares »

HIX Cron/Task Testing - Walkthrough

🎯 Objetivo

Crear y probar ejemplos completos de la nueva funcionalidad de gestión de tareas (cron) de HIX, que permite ejecutar threads de Harbour en segundo plano.

📦 Archivos Creados

He creado 6 archivos de prueba en c:\hix para demostrar diferentes capacidades de la funcionalidad de tareas:

1. [test_init.prg](file:///c:/hix/test_init.prg) ⏰

Propósito: Ejemplo básico - Reloj que muestra la hora cada segundo

URL: http://localhost/task/test_init.prg

Código:

Code: Select all | Expand

function main()
	? 'Init Thread Task: ', HIX_AddTask( 'TIME', hb_threadStart( @zTime() ) )	
retu nil 

function zTime()
	while .t. 
		_d( time() )
		hb_idleSleep(1)
	end 
retu nil

Qué hace:

  • Inicia una tarea llamada 'TIME'
  • Muestra la hora actual cada segundo
  • Perfecto para verificar que la funcionalidad básica funciona

2. [test_counter.prg](file:///c:/hix/test_counter.prg) 🔢

Propósito: Contador incremental que demuestra persistencia de variables

URL: http://localhost/task/test_counter.prg

Características:

  • Contador que incrementa cada 2 segundos
  • Muestra cómo mantener estado en un thread
  • Útil para verificar que las variables persisten

3. [test_logger.prg](file:///c:/hix/test_logger.prg) 📝

Propósito: Sistema de logging con timestamps

URL: http://localhost/task/test_logger.prg

Características:

  • Escribe logs cada 5 segundos
  • Archivo de salida: .log\task_logger.txt
  • Incluye fecha y hora en cada entrada
  • Demuestra I/O de archivos desde threads

4. [test_multiple.prg](file:///c:/hix/test_multiple.prg) 🔀

Propósito: Múltiples tareas concurrentes

URL: http://localhost/task/test_multiple.prg

Tareas que inicia:

  1. RELOJ: Hora cada segundo
  2. STATS: Estadísticas del sistema cada 10 segundos
  3. HEARTBEAT: Señal de vida cada 3 segundos

Código relevante:

Code: Select all | Expand

HIX_AddTask( 'RELOJ', hb_threadStart( @zClock() ) )
HIX_AddTask( 'STATS', hb_threadStart( @zStats() ) )
HIX_AddTask( 'HEARTBEAT', hb_threadStart( @zHeartBeat() ) )

Qué demuestra:

  • Ejecución paralela de múltiples threads
  • Diferentes intervalos de ejecución
  • Monitoreo concurrente

5. [test_status.prg](file:///c:/hix/test_status.prg) 📊

Propósito: Monitor de estado de tareas (placeholder)

URL: http://localhost/task/test_status.prg

Nota: Este archivo es un esqueleto para futuras implementaciones que requieren funciones adicionales como:

  • HIX_GetTasks() - Para listar tareas activas
  • HIX_GetTaskStatus() - Para obtener estado de una tarea
  • HIX_ListTasks() - Para enumerar todas las tareas

6. [test_scheduled.prg](file:///c:/hix/test_scheduled.prg) ⏲️

Propósito: Tareas programadas estilo cron

URL: http://localhost/task/test_scheduled.prg

Características:

  • Tarea horaria automática (cada vez que cambia la hora)
  • Tareas específicas en horarios definidos:
    • 09:00 - Inicio del día
    • 12:00 - Mediodía
    • 18:00 - Fin del día
    • 00:00 - Mantenimiento nocturno
  • Archivo de salida: .log\scheduled_tasks.txt
  • Verificación cada 30 segundos

Uso ideal:

  • Backups programados
  • Reportes periódicos
  • Limpieza automática
  • Sincronización de datos

🧪 Cómo Probar

Método 1: Navegador (Recomendado)

Simplemente navega en tu navegador a cualquiera de estas URLs mientras HIX.exe está corriendo:

  • http://localhost/task/test_init.prg
  • http://localhost/task/test_counter.prg
  • http://localhost/task/test_logger.prg
  • http://localhost/task/test_multiple.prg
  • http://localhost/task/test_status.prg
  • http://localhost/task/test_scheduled.prg

Método 2: Curl (Línea de comandos)

Code: Select all | Expand

curl http://localhost/task/test_init.prg
curl http://localhost/task/test_counter.prg
curl http://localhost/task/test_logger.prg

Método 3: PowerShell

Code: Select all | Expand

Invoke-WebRequest -Uri "http://localhost/task/test_init.prg"

🔍 Verificación

Después de ejecutar las pruebas, verifica:

  1. Salida en consola/logs de HIX:

    • Busca mensajes como [RELOJ], [STATS], [HEARTBEAT]
    • Verifica timestamps actualizándose
  2. Archivos de log creados:

    • c:\hix\.log\task_logger.txt - Del test_logger
    • c:\hix\.log\scheduled_tasks.txt - Del test_scheduled
  3. Comportamiento esperado:

    • Las tareas deben seguir ejecutándose después de la inicialización
    • Los contadores deben incrementar
    • Los logs deben actualizarse periódicamente

💡 Sugerencias de Casos de Uso Reales

Basándome en estos ejemplos, aquí hay casos de uso prácticos:

1. Backup Automático de Bases de Datos

Code: Select all | Expand

function zBackupDB()
    while .t.
        if Time() >= '02:00:00' .and. Time() < '02:05:00'
            // Ejecutar backup a las 2 AM
            BackupDatabase()
        endif
        hb_idleSleep(300) // Check cada 5 minutos
    end
retu nil

2. Monitor de Integridad de DBF

Code: Select all | Expand

function zDbIntegrityCheck()
    while .t.
        CheckAllDatabases()
        ReportIssues()
        hb_idleSleep(3600) // Cada hora
    end
retu nil

3. Limpieza de Archivos Temporales

Code: Select all | Expand

function zCleanupTemp()
    while .t.
        CleanOldFiles('.tmp', 7) // Archivos > 7 días
        hb_idleSleep(86400) // Una vez al día
    end
retu nil

4. Sincronización con API Externa

Code: Select all | Expand

function zApiSync()
    while .t.
        SyncWithExternalAPI()
        LogSyncStatus()
        hb_idleSleep(600) // Cada 10 minutos
    end
retu nil

5. Generación de Reportes Programados

Code: Select all | Expand

function zDailyReports()
    while .t.
        if Time() >= '17:00:00' .and. Time() < '17:01:00'
            GenerateDailyReport()
            EmailReport()
        endif
        hb_idleSleep(60)
    end
retu nil

🔧 Funciones Útiles de Harbour

Estas funciones son particularmente útiles en el contexto de tareas:

FunciónDescripción
hb_threadStart(@func)Inicia un thread con la función especificada
hb_idleSleep(nSec)Pausa el thread N segundos
Time()Hora actual (HH:MM:SS)
Date()Fecha actual
DToC(date)Convierte fecha a string
Memory(0)Memoria en uso (bytes)
MemoRead(file)Lee archivo de texto completo
MemoWrit(file, content)Escribe archivo de texto
hb_valToStr(val)Convierte cualquier valor a string

❓ Preguntas para Investigar

  1. Gestión de tareas:

    • ¿Cómo detener una tarea? (HIX_RemoveTask(), HIX_StopTask()?)
    • ¿Cómo listar tareas activas? (HIX_ListTasks()?)
    • ¿Cómo obtener estado de una tarea?
  2. Límites:

    • ¿Cuántas tareas concurrentes soporta HIX?
    • ¿Hay límite de memoria por tarea?
  3. Persistencia:

    • ¿Las tareas sobreviven un reinicio de HIX?
    • ¿Se pueden auto-iniciar tareas al arrancar el servidor?
  4. Comunicación:

    • ¿Pueden las tareas compartir datos entre sí?
    • ¿Hay mecanismos de sincronización disponibles?
  5. Monitoreo:

    • ¿Existe un dashboard para ver tareas activas?
    • ¿Se puede ver el output de las tareas en tiempo real?

📋 Checklist de Pruebas

  • Ejecutar test_init.prg y verificar logs
  • Ejecutar test_counter.prg y ver incremento
  • Ejecutar test_logger.prg y revisar archivo .log\task_logger.txt
  • Ejecutar test_multiple.prg y verificar 3 tareas concurrentes
  • Ejecutar test_scheduled.prg y esperar al cambio de hora
  • Verificar que las tareas siguen corriendo después de minutos
  • Intentar detener tareas (si existe la funcionalidad)
  • Intentar listar tareas activas
  • Monitorear uso de CPU/memoria con múltiples tareas
  • Probar qué pasa si se reinicia HIX.exe

🎓 Conclusión

He creado una suite completa de ejemplos que cubren:

✅ Inicialización básica de tareas
✅ Persistencia de estado con variables
✅ I/O de archivos desde threads
✅ Múltiples tareas concurrentes
✅ Tareas programadas estilo cron
✅ Casos de uso prácticos documentados

Los archivos están listos para probar. Simplemente navega a las URLs correspondientes mientras HIX.exe está corriendo en localhost.


📁 Resumen de Archivos

ArchivoLíneasPropósito
[test_init.prg](file:///c:/hix/test_init.prg)15Reloj básico
[test_counter.prg](file:///c:/hix/test_counter.prg)17Contador incremental
[test_logger.prg](file:///c:/hix/test_logger.prg)22Sistema de logging
[test_multiple.prg](file:///c:/hix/test_multiple.prg)47Tareas concurrentes
[test_status.prg](file:///c:/hix/test_status.prg)16Monitor de estado
[test_scheduled.prg](file:///c:/hix/test_scheduled.prg)68Tareas programadas
TOTAL1856 ejemplos completos

¡Listo para probar! 🚀

regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: OpenClaw concepts on HIX

Post by Antonio Linares »

Guía de Pruebas - Funcionalidad Cron/Tasks de HIX

📋 Resumen

Esta guía documenta los ejemplos creados para probar la nueva funcionalidad de gestión de tareas (cron) en HIX. La función HIX_AddTask() permite iniciar threads de Harbour que se ejecutan en segundo plano de forma continua.

🚀 Archivos de Prueba Creados

1. test_init.prg - Reloj Básico

  • URL: http://localhost/task/test_init.prg
  • Descripción: Muestra la hora actual cada segundo
  • Uso: Ejemplo básico de inicialización de tareas
  • Código clave:

    Code: Select all | Expand

      HIX_AddTask( 'TIME', hb_threadStart( @zTime() ) )

2. test_counter.prg - Contador Incremental

  • URL: http://localhost/task/test_counter.prg
  • Descripción: Contador que incrementa cada 2 segundos
  • Uso: Demuestra variables persistentes en threads
  • Características:
    • Contador numérico incremental
    • Intervalo: 2 segundos
    • Muestra el progreso en tiempo real

3. test_logger.prg - Sistema de Logging

  • URL: http://localhost/task/test_logger.prg
  • Descripción: Escribe logs con timestamp cada 5 segundos
  • Uso: Demuestra persistencia de datos y escritura de archivos
  • Archivo de salida: .log\task_logger.txt
  • Características:
    • Registro con fecha y hora
    • Escritura en archivo de texto
    • Ideal para monitoreo de procesos largos

4. test_multiple.prg - Tareas Concurrentes

  • URL: http://localhost/task/test_multiple.prg
  • Descripción: Inicia 3 tareas simultáneas
  • Tareas:
    1. RELOJ: Muestra hora cada segundo
    2. STATS: Estadísticas del sistema cada 10 segundos
    3. HEARTBEAT: Señal de vida cada 3 segundos
  • Uso: Demuestra múltiples threads corriendo en paralelo

5. test_status.prg - Monitor de Estado

  • URL: http://localhost/task/test_status.prg
  • Descripción: Placeholder para monitoreo de tareas activas
  • Uso: Base para implementar dashboard de tareas
  • Nota: Requiere funciones adicionales como HIX_GetTasks() o HIX_ListTasks()

6. test_scheduled.prg - Tareas Programadas (Cron-like)

  • URL: http://localhost/task/test_scheduled.prg
  • Descripción: Ejecuta tareas en horarios específicos
  • Características:
    • Tarea horaria automática
    • Tareas específicas a las 09:00, 12:00, 18:00, 00:00
    • Sistema de verificación cada 30 segundos
  • Archivo de salida: .log\scheduled_tasks.txt
  • Uso: Perfecto para backups, reportes, mantenimiento

🧪 Plan de Pruebas Sugerido

Prueba 1: Inicialización Básica

  1. Asegurarse que HIX.exe está corriendo
  2. Navegar a: http://localhost/task/test_init.prg
  3. Verificar que se muestra el mensaje de inicialización
  4. Revisar logs del servidor para ver la salida del tiempo

Prueba 2: Contador Persistente

  1. Ejecutar: http://localhost/task/test_counter.prg
  2. Monitorear los logs para ver el contador incrementando
  3. Verificar que el contador persiste y no se reinicia

Prueba 3: Sistema de Logging

  1. Ejecutar: http://localhost/task/test_logger.prg
  2. Esperar al menos 30 segundos
  3. Revisar el archivo: c:\hix\.log\task_logger.txt
  4. Verificar que contiene múltiples entradas con timestamps

Prueba 4: Tareas Concurrentes

  1. Ejecutar: http://localhost/task/test_multiple.prg
  2. Monitorear los logs del servidor
  3. Verificar que se ven mensajes de las 3 tareas intercalados:
    • [RELOJ] cada 1 segundo
    • [HEARTBEAT] cada 3 segundos
    • [STATS] cada 10 segundos

Prueba 5: Tareas Programadas

  1. Ejecutar: http://localhost/task/test_scheduled.prg
  2. Esperar al cambio de hora
  3. Revisar: c:\hix\.log\scheduled_tasks.txt
  4. Verificar que se ejecutan las tareas horarias
  5. Si es posible, probar en las horas especiales (09:00, 12:00, 18:00, 00:00)

💡 Ideas Adicionales para Probar

1. Sistema de Backup Automático

Code: Select all | Expand

function zBackupTask()
    while .t.
        // Ejecutar backup cada 6 horas
        DoBackup()
        hb_idleSleep(21600) // 6 horas en segundos
    end
retu nil

2. Monitor de Base de Datos

Code: Select all | Expand

function zDbMonitor()
    while .t.
        // Verificar integridad de DBF cada 15 minutos
        CheckDatabaseIntegrity()
        hb_idleSleep(900)
    end
retu nil

3. Limpieza de Archivos Temporales

Code: Select all | Expand

function zCleanupTask()
    while .t.
        // Limpiar archivos temporales cada día
        CleanTempFiles()
        hb_idleSleep(86400) // 24 horas
    end
retu nil

4. Envío de Reportes por Email

Code: Select all | Expand

function zEmailReports()
    while .t.
        if Time() >= '17:00:00' .and. Time() < '17:01:00'
            SendDailyReport()
        endif
        hb_idleSleep(60)
    end
retu nil

5. Monitor de API Externa

Code: Select all | Expand

function zApiMonitor()
    while .t.
        // Verificar disponibilidad de API cada 5 minutos
        CheckApiStatus()
        hb_idleSleep(300)
    end
retu nil

📊 Preguntas para Investigar

  1. ¿Cómo detener una tarea?

    • ¿Existe HIX_RemoveTask('TASKNAME')?
    • ¿Hay un HIX_StopTask()?
  2. ¿Cómo listar tareas activas?

    • ¿Existe HIX_ListTasks() o HIX_GetTasks()?
    • ¿Devuelve un array con información de las tareas?
  3. ¿Límite de tareas concurrentes?

    • ¿Cuántas tareas puede manejar HIX simultáneamente?
  4. ¿Persistencia de tareas?

    • ¿Las tareas sobreviven un reinicio de HIX?
    • ¿Se pueden guardar/cargar configuraciones de tareas?
  5. ¿Comunicación entre tareas?

    • ¿Pueden las tareas compartir datos?
    • ¿Hay mecanismos de sincronización?

🔧 Funciones Útiles de Harbour para Tasks

  • hb_threadStart(@function) - Inicia un thread
  • hb_idleSleep(nSeconds) - Pausa el thread
  • _d(cMessage) - Debug output (probablemente)
  • Time() - Hora actual
  • Date() - Fecha actual
  • DToC(dDate) - Convierte fecha a string
  • Memory(0) - Memoria en uso
  • MemoRead(cFile) / MemoWrit(cFile, cContent) - I/O de archivos

📝 Notas Importantes

  • Todas las tareas corren en un loop infinito (while .t.)
  • Es importante usar hb_idleSleep() para no saturar el CPU
  • Los logs se pueden acumular, considerar rotación de logs
  • Manejar errores apropiadamente en tareas de producción
  • Las tareas son ideales para:
    • Monitoreo continuo
    • Procesamiento en background
    • Tareas programadas (cron-like)
    • Sincronización de datos
    • Limpieza automática
    • Backups programados

🎯 Próximos Pasos

  1. Probar todos los ejemplos en localhost
  2. Documentar el comportamiento observado
  3. Identificar funciones adicionales de la API de HIX_AddTask
  4. Crear ejemplos de casos de uso reales
  5. Implementar manejo de errores robusto
  6. Considerar sistema de notificaciones para tareas críticas
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: OpenClaw concepts on HIX

Post by Antonio Linares »

test_counter.prg

Code: Select all | Expand

// test_counter.prg
// Ejemplo de contador incremental
// Ejecutar desde: http://localhost/task/test_counter.prg

function main()
	
? 'Init Counter Task: ', HIX_AddTask( 'COUNTER', hb_threadStart( @zCounter() ) )	

retu nil 

// Función contador que incrementa cada 2 segundos
function zCounter()
    local nCount := 0

while .t. 
nCount++
_d( 'Counter: ' + str(nCount) )
hb_idleSleep(2)
end 

retu nil

test_logger.prg

Code: Select all | Expand

// test_logger.prg
// Ejemplo de logger con fecha y hora
// Ejecutar desde: http://localhost/task/test_logger.prg

function main()
	
? 'Init Logger Task: ', HIX_AddTask( 'LOGGER', hb_threadStart( @zLogger() ) )	

retu nil 

// Función logger que registra fecha/hora cada 5 segundos
function zLogger()
    local cLogFile := '.log\task_logger.txt'

while .t. 
// Escribir al log
MemoWrit( cLogFile, ;
    MemoRead(cLogFile) + ;
    DToC(Date()) + ' ' + Time() + ' - Log Entry' + Chr(13) + Chr(10) )
	
_d( 'Log written: ' + DToC(Date()) + ' ' + Time() )
hb_idleSleep(5)
end 

retu nil

test_multiple.prg

Code: Select all | Expand

// test_multiple.prg
// Ejemplo de múltiples tareas concurrentes
// Ejecutar desde: http://localhost/task/test_multiple.prg

function main()
    local cHtml := ''
	
cHtml += '<h2>Inicializando múltiples tareas...</h2>'
cHtml += '<ul>'
cHtml += '<li>Tarea RELOJ: ' + hb_valToStr( HIX_AddTask( 'RELOJ', hb_threadStart( @zClock() ) ) ) + '</li>'
cHtml += '<li>Tarea STATS: ' + hb_valToStr( HIX_AddTask( 'STATS', hb_threadStart( @zStats() ) ) ) + '</li>'
cHtml += '<li>Tarea HEARTBEAT: ' + hb_valToStr( HIX_AddTask( 'HEARTBEAT', hb_threadStart( @zHeartBeat() ) ) ) + '</li>'
cHtml += '</ul>'
cHtml += '<p>Tareas iniciadas correctamente</p>'

? cHtml

retu nil 

// Tarea 1: Reloj que muestra la hora cada segundo
function zClock()
    while .t. 
    _d( '[RELOJ] ' + Time() )
    hb_idleSleep(1)
    end 
retu nil

// Tarea 2: Estadísticas del sistema cada 10 segundos
function zStats()
    local nCount := 0
    while .t. 
    nCount++
    _d( '[STATS] Iteración #' + str(nCount) + ' - Memoria: ' + str(Memory(0)) + ' bytes' )
    hb_idleSleep(10)
    end 
retu nil

// Tarea 3: HeartBeat cada 3 segundos
function zHeartBeat()
    while .t. 
    _d( '[HEARTBEAT] ♥ Sistema activo' )
    hb_idleSleep(3)
    end 
retu nil

test_scheduled.prg

Code: Select all | Expand

// test_scheduled.prg
// Ejemplo de tareas programadas (cron-like)
// Ejecutar desde: http://localhost/task/test_scheduled.prg

function main()
	
? 'Init Scheduled Task: ', HIX_AddTask( 'SCHEDULED', hb_threadStart( @zScheduledTask() ) )	

retu nil 

// Tarea que se ejecuta en momentos específicos
function zScheduledTask()
    local cLastHour := ''
    local cCurrentHour := ''
    local cLogFile := '.log\scheduled_tasks.txt'

while .t. 
cCurrentHour := SubStr(Time(), 1, 2)  // Obtener hora actual
	
// Ejecutar tarea cada hora (cuando cambia la hora)
if cCurrentHour != cLastHour
cLastHour := cCurrentHour
		
// Tarea programada por hora
DoHourlyTask(cLogFile)
		
// Tareas específicas según la hora
do case
case cCurrentHour == '09'
_d( '[SCHEDULED] Tarea de inicio del día ejecutada' )
MemoWrit( cLogFile, MemoRead(cLogFile) + ;
    DToC(Date()) + ' 09:00 - Inicio del día' + Chr(13) + Chr(10) )
				
case cCurrentHour == '12'
_d( '[SCHEDULED] Tarea del mediodía ejecutada' )
MemoWrit( cLogFile, MemoRead(cLogFile) + ;
    DToC(Date()) + ' 12:00 - Mediodía' + Chr(13) + Chr(10) )
				
case cCurrentHour == '18'
_d( '[SCHEDULED] Tarea de fin del día ejecutada' )
MemoWrit( cLogFile, MemoRead(cLogFile) + ;
    DToC(Date()) + ' 18:00 - Fin del día' + Chr(13) + Chr(10) )
				
case cCurrentHour == '00'
_d( '[SCHEDULED] Tarea de mantenimiento nocturno ejecutada' )
MemoWrit( cLogFile, MemoRead(cLogFile) + ;
    DToC(Date()) + ' 00:00 - Mantenimiento nocturno' + Chr(13) + Chr(10) )
endcase
endif
	
// Dormir 30 segundos antes de verificar de nuevo
hb_idleSleep(30)
end 

retu nil

// Tarea que se ejecuta cada hora
function DoHourlyTask(cLogFile)
    _d( '[SCHEDULED] Tarea horaria ejecutada: ' + Time() )
    MemoWrit( cLogFile, MemoRead(cLogFile) + ;
        DToC(Date()) + ' ' + Time() + ' - Tarea horaria' + Chr(13) + Chr(10) )
retu nil
regards, saludos

Antonio Linares
www.fivetechsoft.com
asesormix
Posts: 22
Joined: Wed Feb 12, 2020 4:55 pm
Been thanked: 2 times

Re: OpenClaw concepts on HIX

Post by asesormix »

Muy bueno Antonio. Gracias.
Nos abre mas la mente para ser creativos.
Cordial Saludo.

User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: OpenClaw concepts on HIX

Post by Antonio Linares »

El concepto de "Skills" en HIX:

Image

skills_loader.prg

Code: Select all | Expand

// skill_loader.prg - Con auto-generación de skills de ejemplo
#include "hix.ch"

function main()
    local cHtml
    local aDirs, cDir, cSkillPath, oSkill
    local nTotal := 0
    
// Auto-generar skills de ejemplo si no existen AutoGenerateDemoSkills() cHtml := '<!DOCTYPE html><html><head>' cHtml += '<meta charset="UTF-8">' cHtml += '<title>HIX Skills System</title>' cHtml += '<style>' cHtml += 'body { font-family: Arial, sans-serif; max-width: 900px; margin: 20px auto; padding: 20px; }' cHtml += 'h1 { color: #333; border-bottom: 3px solid #007bff; padding-bottom: 10px; }' cHtml += '.skill-card { border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 8px; background: #f9f9f9; }' cHtml += '.skill-name { font-size: 20px; font-weight: bold; color: #333; }' cHtml += '.skill-version { color: #666; font-size: 14px; }' cHtml += '.skill-desc { margin: 10px 0; color: #555; }' cHtml += '.badge { padding: 4px 10px; border-radius: 4px; font-size: 12px; margin-right: 5px; display: inline-block; }' cHtml += '.badge-danger { background: #dc3545; color: white; }' cHtml += '.badge-warning { background: #ffc107; color: black; }' cHtml += '.badge-info { background: #17a2b8; color: white; }' cHtml += '.summary { background: #e7f3ff; padding: 15px; border-radius: 8px; margin: 20px 0; }' cHtml += '</style></head><body>' aDirs := Directory('skills\*.*', 'D') cHtml += '<h1>📦 HIX Skills System</h1>' for each cDir in aDirs if cDir[5] == 'D' .and. !(cDir[1] $ '.,') .and. cDir[1] != '..' cSkillPath := 'skills\' + cDir[1] oSkill := ParseSkill(cSkillPath) if oSkill != NIL nTotal++ cHtml += '<div class="skill-card">' cHtml += '<div class="skill-name">' + oSkill['name'] + ' ' cHtml += '<span class="skill-version">v' + oSkill['version'] + '</span>' cHtml += '</div>' cHtml += '<div class="skill-desc">' + oSkill['description'] + '</div>' cHtml += '<div>' cHtml += '<span class="badge badge-info">📂 ' + hb_HGetDef(oSkill, 'category', 'general') + '</span> ' if hb_HGetDef(oSkill, 'requires_permission', .t.) cHtml += '<span class="badge badge-warning">🔐 Requiere Permiso</span> ' endif if oSkill['dangerous'] cHtml += '<span class="badge badge-danger">⚠️ PELIGROSA</span>' endif cHtml += '</div>' cHtml += '</div>' endif endif next cHtml += '<div class="summary">' cHtml += '<strong>✅ Total:</strong> ' + AllTrim(Str(nTotal)) + ' skills cargadas correctamente' cHtml += '</div>' cHtml += '</body></html>' UWrite(cHtml) retu "" //------------------------------------------------------------------------------ // Auto-genera las 3 skills de demostración si no existen //------------------------------------------------------------------------------ function AutoGenerateDemoSkills()
if !File('skills\gmail\SKILL.md') CreateGmailSkill() endif if !File('skills\whatsapp\SKILL.md') CreateWhatsAppSkill() endif if !File('skills\filesystem\SKILL.md') CreateFileSystemSkill() endif retu NIL function CreateGmailSkill() local cContent := ''
MkDir('skills') MkDir('skills\gmail') cContent += '---' + Chr(13) + Chr(10) cContent += 'name: "gmail"' + Chr(13) + Chr(10) cContent += 'version: "1.0.0"' + Chr(13) + Chr(10) cContent += 'description: "Gestiona correos electrónicos mediante Gmail API"' + Chr(13) + Chr(10) cContent += 'category: "communication"' + Chr(13) + Chr(10) cContent += 'requires_permission: true' + Chr(13) + Chr(10) cContent += 'dangerous: false' + Chr(13) + Chr(10) cContent += '---' + Chr(13) + Chr(10) cContent += Chr(13) + Chr(10) cContent += '# Gmail Skill' + Chr(13) + Chr(10) cContent += Chr(13) + Chr(10) cContent += 'Permite enviar, leer y gestionar correos electrónicos mediante Gmail API.' + Chr(13) + Chr(10) MemoWrit('skills\gmail\SKILL.md', cContent) retu NIL function CreateWhatsAppSkill() local cContent := ''
MkDir('skills') MkDir('skills\whatsapp') cContent += '---' + Chr(13) + Chr(10) cContent += 'name: "whatsapp"' + Chr(13) + Chr(10) cContent += 'version: "1.0.0"' + Chr(13) + Chr(10) cContent += 'description: "Envía y recibe mensajes de WhatsApp mediante WhatsApp Business API"' + Chr(13) + Chr(10) cContent += 'category: "communication"' + Chr(13) + Chr(10) cContent += 'requires_permission: true' + Chr(13) + Chr(10) cContent += 'dangerous: false' + Chr(13) + Chr(10) cContent += '---' + Chr(13) + Chr(10) cContent += Chr(13) + Chr(10) cContent += '# WhatsApp Skill' + Chr(13) + Chr(10) cContent += Chr(13) + Chr(10) cContent += 'Permite enviar mensajes, imágenes y archivos por WhatsApp.' + Chr(13) + Chr(10) MemoWrit('skills\whatsapp\SKILL.md', cContent) retu NIL function CreateFileSystemSkill() local cContent := ''
MkDir('skills') MkDir('skills\filesystem') cContent += '---' + Chr(13) + Chr(10) cContent += 'name: "filesystem"' + Chr(13) + Chr(10) cContent += 'version: "1.0.0"' + Chr(13) + Chr(10) cContent += 'description: "Gestiona archivos y directorios del sistema de archivos"' + Chr(13) + Chr(10) cContent += 'category: "filesystem"' + Chr(13) + Chr(10) cContent += 'requires_permission: true' + Chr(13) + Chr(10) cContent += 'dangerous: true' + Chr(13) + Chr(10) cContent += '---' + Chr(13) + Chr(10) cContent += Chr(13) + Chr(10) cContent += '# FileSystem Skill' + Chr(13) + Chr(10) cContent += Chr(13) + Chr(10) cContent += 'Permite listar, buscar, leer, escribir, mover y eliminar archivos.' + Chr(13) + Chr(10) cContent += Chr(13) + Chr(10) cContent += '⚠️ **ADVERTENCIA**: Esta skill puede modificar/eliminar archivos del sistema.' + Chr(13) + Chr(10) MemoWrit('skills\filesystem\SKILL.md', cContent) retu NIL function ParseSkill(cPath) local cFile := cPath + '\SKILL.md' local cContent, aLines, cLine local oSkill := {=>} local lInFront := .f. local aParts, cKey, cValue
if !File(cFile) retu NIL endif cContent := MemoRead(cFile) if Empty(cContent) retu NIL endif aLines := hb_ATokens(cContent, Chr(13) + Chr(10)) for each cLine in aLines cLine := AllTrim(cLine) if cLine == '---' lInFront := !lInFront loop endif if lInFront .and. ':' $ cLine aParts := hb_ATokens(cLine, ':') if Len(aParts) >= 2 cKey := AllTrim(aParts[1]) cValue := AllTrim(SubStr(cLine, At(':', cLine) + 1)) cValue := StrTran(cValue, '"', '') cValue := StrTran(cValue, "'", "") do case case Lower(cValue) == 'true' oSkill[cKey] := .t. case Lower(cValue) == 'false' oSkill[cKey] := .f. otherwise oSkill[cKey] := cValue endcase endif endif if !lInFront .and. !Empty(oSkill) exit endif next // Defaults if !hb_HHasKey(oSkill, 'version') oSkill['version'] := '1.0.0' endif if !hb_HHasKey(oSkill, 'dangerous') oSkill['dangerous'] := .f. endif if !hb_HHasKey(oSkill, 'requires_permission') oSkill['requires_permission'] := .t. endif if Empty(hb_HGetDef(oSkill, 'name', '')) retu NIL endif retu oSkill
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: OpenClaw concepts on HIX

Post by Antonio Linares »

HIX + Gemini (chat memory support)

Image

gemini_chat.prg

Code: Select all | Expand

function main()
    local cHtml := ""
    
cHtml += "<!DOCTYPE html>" cHtml += "<html lang='es'>" cHtml += "<head>" cHtml += " <meta charset='UTF-8'>" cHtml += " <meta name='viewport' content='width=device-width, initial-scale=1.0'>" cHtml += " <title>HIX AIOS v13 - Chat Premium</title>" cHtml += " <link href='https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&family=JetBrains+Mono&display=swap' rel='stylesheet'>" cHtml += " <style>" cHtml += " :root {" cHtml += " --bg: #0a0b10;" cHtml += " --glass: rgba(255, 255, 255, 0.05);" cHtml += " --glass-border: rgba(255, 255, 255, 0.1);" cHtml += " --accent: linear-gradient(135deg, #6366f1, #a855f7);" cHtml += " --text: #e2e8f0;" cHtml += " --bot-bubble: rgba(30, 41, 59, 0.7);" cHtml += " --user-bubble: linear-gradient(135deg, #4f46e5, #7c3aed);" cHtml += " }" cHtml += " * { margin:0; padding:0; box-sizing: border-box; }" cHtml += " body {" cHtml += " font-family: 'Outfit', sans-serif;" cHtml += " background: var(--bg);" cHtml += " background-image: radial-gradient(circle at 20% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%)," cHtml += " radial-gradient(circle at 80% 80%, rgba(168, 85, 247, 0.15) 0%, transparent 40%);" cHtml += " color: var(--text);" cHtml += " height: 100vh;" cHtml += " display: flex;" cHtml += " flex-direction: column;" cHtml += " overflow: hidden;" cHtml += " }" cHtml += " header {" cHtml += " padding: 1.5rem 2rem;" cHtml += " background: var(--glass);" cHtml += " backdrop-filter: blur(10px);" cHtml += " border-bottom: 1px solid var(--glass-border);" cHtml += " display: flex;" cHtml += " justify-content: space-between;" cHtml += " align-items: center;" cHtml += " z-index: 10;" cHtml += " }" cHtml += " header h1 { font-size: 1.5rem; font-weight: 600; letter-spacing: -0.5px; }" cHtml += " .version-badge { background: var(--accent); padding: 0.2rem 0.6rem; border-radius: 20px; font-size: 0.75rem; font-weight: bold; }" cHtml += " #chat-container {" cHtml += " flex: 1;" cHtml += " overflow-y: auto;" cHtml += " padding: 2rem;" cHtml += " display: flex;" cHtml += " flex-direction: column;" cHtml += " gap: 1.5rem;" cHtml += " }" cHtml += " .message {" cHtml += " max-width: 85%;" cHtml += " padding: 1rem 1.25rem;" cHtml += " border-radius: 1.2rem;" cHtml += " line-height: 1.6;" cHtml += " animation: fadeIn 0.3s ease-out;" cHtml += " }" cHtml += " @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }" cHtml += " .user { align-self: flex-end; background: var(--user-bubble); color: white; border-bottom-right-radius: 0.2rem; }" cHtml += " .bot { align-self: flex-start; background: var(--bot-bubble); backdrop-filter: blur(5px); border: 1px solid var(--glass-border); border-bottom-left-radius: 0.2rem; }" cHtml += " .error { align-self: center; background: rgba(255,0,0,0.2); color: #ff8888; border: 1px solid #ff0000; padding: 0.5rem 1rem; border-radius: 10px; }" cHtml += " pre { background: #1e1e1e; padding: 1rem; border-radius: 8px; overflow-x: auto; font-family: 'JetBrains Mono', monospace; }" cHtml += " .input-wrapper { padding: 2rem; background: linear-gradient(to top, var(--bg), transparent); }" cHtml += " .input-box {" cHtml += " max-width: 900px; margin: 0 auto; background: var(--glass); backdrop-filter: blur(20px); border: 1px solid var(--glass-border);" cHtml += " border-radius: 1.5rem; padding: 0.5rem; display: flex; align-items: center; gap: 0.5rem;" cHtml += " }" cHtml += " input { flex: 1; background: transparent; border: none; color: white; padding: 0.75rem 1rem; font-size: 1rem; outline: none; }" cHtml += " button { background: var(--accent); border: none; color: white; padding: 0.75rem 1.5rem; border-radius: 1.1rem; font-weight: 600; cursor: pointer; }" cHtml += " button:disabled { background: #444; }" cHtml += " .typing { display: flex; gap: 4px; padding: 10px; }" cHtml += " .dot { width: 8px; height: 8px; background: #6366f1; border-radius: 50%; animation: bounce 1.4s infinite ease-in-out; }" cHtml += " @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } }" cHtml += " </style>" cHtml += "</head>" cHtml += "<body>" cHtml += "<header>" cHtml += " <h1>HIX AIOS <span class='version-badge'>v13</span></h1>" cHtml += " <button onclick='clearChat()'>Limpiar</button>" cHtml += "</header>" cHtml += "<div id='chat-container'></div>" cHtml += "<div class='input-wrapper'>" cHtml += " <div class='input-box'>" cHtml += " <input type='text' id='input' placeholder='Pregunta algo...' onkeydown='if(event.keyCode===13) sendMessage()'>" cHtml += " <button id='send-btn' onclick='sendMessage()'>Enviar</button>" cHtml += " </div>" cHtml += "</div>" cHtml += "<script>" cHtml += " window.onerror = function(m, u, l) { alert('Error: ' + m + ' en ' + l); };" cHtml += " var history_data = [];" cHtml += " function addMessage(role, text) {" cHtml += " var container = document.getElementById('chat-container');" cHtml += " var div = document.createElement('div');" cHtml += " div.className = 'message ' + role;" cHtml += " div.innerHTML = text.split('\\n').join('<br>');" cHtml += " container.appendChild(div);" cHtml += " setTimeout(function() { container.scrollTop = container.scrollHeight; }, 50);" cHtml += " }" cHtml += " function addTyping() {" cHtml += " var container = document.getElementById('chat-container');" cHtml += " var div = document.createElement('div');" cHtml += " div.id = 'typing-indicator';" cHtml += " div.className = 'message bot typing';" cHtml += " div.innerHTML = 'ÔÜÖ´©Å Pensando...';" cHtml += " container.appendChild(div);" cHtml += " container.scrollTop = container.scrollHeight;" cHtml += " }" cHtml += " function removeTyping() {" cHtml += " var indicator = document.getElementById('typing-indicator');" cHtml += " if(indicator) indicator.parentNode.removeChild(indicator);" cHtml += " }" cHtml += " function sendMessage() {" cHtml += " var input = document.getElementById('input');" cHtml += " var text = input.value.trim();" cHtml += " var btn = document.getElementById('send-btn');" cHtml += " if (!text) return;" cHtml += " addMessage('user', text);" cHtml += " input.value = '';" cHtml += " addTyping();" cHtml += " btn.disabled = true;" cHtml += " var params = 'query=' + encodeURIComponent(text);" cHtml += " if (history_data.length > 0) params += '&history=' + encodeURIComponent(JSON.stringify(history_data));" cHtml += " console.log('SENDING:', params);" cHtml += " var xhr = new XMLHttpRequest();" cHtml += " xhr.open('POST', 'gemini_aios_v13.prg', true);" cHtml += " xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');" cHtml += " xhr.onreadystatechange = function() {" cHtml += " if (xhr.status !== 200 && xhr.status !== 0) { addMessage('error', 'Status: ' + xhr.status); }" cHtml += " if (xhr.readyState == 4) {" cHtml += " removeTyping(); btn.disabled = false;" cHtml += " if (xhr.status == 200) {" cHtml += " try {" cHtml += " var data = JSON.parse(xhr.responseText);" cHtml += " if (data.success) {" cHtml += " addMessage('bot', data.text);" cHtml += " history_data.push({role:'user', parts:[{text:text}]});" cHtml += " history_data.push({role:'model', parts:[{text:data.text}]});" cHtml += " } else { addMessage('error', 'Error: ' + data.error); }" cHtml += " } catch(e) { addMessage('error', 'JSON Error: ' + xhr.responseText.substring(0,100)); }" cHtml += " } else if(xhr.status !== 0) { addMessage('error', 'Server Error: ' + xhr.status); }" cHtml += " }" cHtml += " };" cHtml += " xhr.send(params);" cHtml += " }" cHtml += " function clearChat() {" cHtml += " document.getElementById('chat-container').innerHTML = '';" cHtml += " history_data = [];" cHtml += " }" cHtml += " window.onload = function() {" cHtml += " addMessage('bot', 'HIX AIOS v13 listo. Modo ultra-estable activado.');" cHtml += " };" cHtml += "</script>" cHtml += "</body></html>" UWrite(cHtml) return ""

gemini_aios_v13.prg

Code: Select all | Expand

// gemini_aios_v13.prg - Memory Fix Version
#include "hbcurl.ch"

function main()
    local cQuery, cModel, cHistory, cLogDoc := ""
    local hResult
    
cQuery := UGet("query") cModel := UGet("model") cHistory := UGet("history") if Empty(cQuery) ; cQuery := UPost("query") ; endif if Empty(cModel) ; cModel := UPost("model") ; endif if Empty(cHistory) ; cHistory := UPost("history") ; endif // Auto-decode if raw if !Empty(cHistory) .and. "%" $ hb_ValToStr(cHistory) cHistory := Hix_UrlDecode(hb_ValToStr(cHistory)) endif if !Empty(cQuery) .and. "%" $ hb_ValToStr(cQuery) cQuery := Hix_UrlDecode(hb_ValToStr(cQuery)) endif cLogDoc := hb_eol() + ">>> [" + Time() + "] NEW REQ" + hb_eol() cLogDoc += "QUERY: " + hb_ValToStr(cQuery) + hb_eol() if cQuery == "ping" UWrite('{"success":true, "text":"pong"}') return "" endif if Empty(cQuery) UWrite('{"success":false, "error":"No query provided"}') return "" endif if Empty(cModel) ; cModel := "gemini-2.0-flash" ; endif hResult := ExecuteReasoningLoop(cQuery, cModel, hb_ValToStr(cHistory), @cLogDoc) cLogDoc += "<<< [" + Time() + "] RESP SENT" + hb_eol() MemoWrit("aios_debug.log", MemoRead("aios_debug.log") + cLogDoc) UWrite(hb_jsonEncode(hResult)) return "" //------------------------------------------------------------------------------ function ExecuteReasoningLoop(cQuery, cModel, cHistory, cTrace) local aFunctions := BuildAiosFunctions() local aMessages := {}, nMaxSteps := 5, nStep := 0 local hResult := {=>}, aStepsArr := {}, hMsg, hGeminiResult, hSkillResult local nErr
if !Empty(cHistory) nErr := hb_jsonDecode(cHistory, @aMessages) // In HIX, hb_jsonDecode returns the POS where it stopped. // If it reaches the end, it's successful. if ValType(aMessages) != "A" .or. Len(aMessages) == 0 if nErr == 0 .or. nErr < Len(AllTrim(cHistory)) cTrace += "!! History Decode Failed. Pos: " + hb_ValToStr(nErr) + hb_eol() aMessages := {} endif endif endif if ValType(aMessages) != "A" ; aMessages := {} ; endif cTrace += "HIST_TURNS: " + hb_ValToStr(Len(aMessages)) + hb_eol() aAdd(aMessages, { "role" => "user", "parts" => { { "text" => cQuery } } }) do while nStep < nMaxSteps nStep++ hGeminiResult := GeminiCallFC(aMessages, aFunctions, cModel, @cTrace) if !hGeminiResult['success'] hResult['success'] := .f. ; hResult['error'] := hGeminiResult['error'] ; retu hResult endif hMsg := { "role" => "model", "parts" => hGeminiResult["raw_parts"] } aAdd(aMessages, hMsg) if hGeminiResult['type'] == 'function_call' aAdd(aStepsArr, { "type" => "call", "name" => hGeminiResult['function_name'], "args" => hGeminiResult['arguments'] }) hSkillResult := ExecuteAiosSkill(hGeminiResult['function_name'], hGeminiResult['arguments']) aAdd(aMessages, { "role" => "function", "parts" => { { "functionResponse" => { "name" => hGeminiResult['function_name'], "response" => { "content" => hb_jsonEncode(hSkillResult) } } } } }) aAdd(aStepsArr, { "type" => "result", "success" => hSkillResult['success'], "result" => hSkillResult }) else hResult['success'] := .t. ; hResult['text'] := hGeminiResult['text'] ; hResult['steps'] := aStepsArr ; hResult['model_used'] := cModel retu hResult endif enddo hResult['success'] := .f. ; hResult['error'] := "Max steps" retu hResult //------------------------------------------------------------------------------ function GeminiCallFC(aMessages, aFunctions, cModel, cTrace) local cApiKey := GetAiosApiKey(), cUrl, cJson, hCurl, nError, cResponse := "", hResult := {=>}
cUrl := "https://generativelanguage.googleapis.com/v1beta/models/" + cModel + ":generateContent?key=" + cApiKey cJson := hb_jsonEncode({ "contents" => aMessages, "tools" => { { "function_declarations" => aFunctions } } }) hCurl := curl_easy_init() if !Empty(hCurl) curl_easy_setopt(hCurl, HB_CURLOPT_POST, .T.) curl_easy_setopt(hCurl, HB_CURLOPT_URL, cUrl) curl_easy_setopt(hCurl, HB_CURLOPT_HTTPHEADER, { "Content-Type: application/json" }) curl_easy_setopt(hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F.) curl_easy_setopt(hCurl, HB_CURLOPT_POSTFIELDS, cJson) curl_easy_setopt(hCurl, HB_CURLOPT_DL_BUFF_SETUP) nError := curl_easy_perform(hCurl) if nError == HB_CURLE_OK cResponse := curl_easy_dl_buff_get(hCurl) hResult := ParseAiosFCResponse(cResponse, @cTrace) else hResult['success'] := .f. ; hResult['error'] := "Curl error: " + hb_ValToStr(nError) cTrace += "CURL_ERR: " + hb_ValToStr(nError) + hb_eol() endif curl_easy_cleanup(hCurl) else ; hResult['success'] := .f. ; hResult['error'] := "Curl init failed" ; endif retu hResult //------------------------------------------------------------------------------ function ParseAiosFCResponse(cJSON, cTrace) local hResult := { "success" => .f. }, hResponse := {=>}, aParts, nErr := 0 local nStart, nEnd, cWorking
cJSON := StrTran(hb_ValToStr(cJSON), Chr(0), "") cWorking := AllTrim(cJSON) nStart := At("{", cWorking) nEnd := RAt("}", cWorking) if nStart > 0 .and. nEnd > nStart cWorking := SubStr(cWorking, nStart, nEnd - nStart + 1) endif nErr := hb_jsonDecode(cWorking, @hResponse) do while nErr > 0 .and. Len(cWorking) > 10 cWorking := Left(cWorking, Len(cWorking) - 1) if Right(cWorking, 1) $ '}"0123456789' nErr := hb_jsonDecode(cWorking, @hResponse) endif enddo if (nErr == 0 .or. nErr == Len(cWorking)) .and. hb_HHasKey(hResponse, "candidates") .and. Len(hResponse["candidates"]) > 0 aParts := hResponse["candidates"][1]["content"]["parts"] hResult['success'] := .t. ; hResult['raw_parts'] := aParts if hb_HHasKey(aParts[1], "functionCall") hResult['type'] := 'function_call' hResult['function_name'] := aParts[1]["functionCall"]["name"] hResult['arguments'] := aParts[1]["functionCall"]["args"] else hResult['type'] := 'text' hResult['text'] := hb_HGetDef(aParts[1], "text", "") endif else hResult['error'] := "API JSON malformed (" + hb_ValToStr(nErr) + ")" cTrace += "!! Decoder failed on: " + Left(cWorking, 100) + hb_eol() endif retu hResult //------------------------------------------------------------------------------ function BuildAiosFunctions() local aFuncs := {} aAdd(aFuncs, { "name" => "filesystem_search", "description" => "Search files. pattern, path.", "parameters" => { "type" => "object", "properties" => { "pattern" => { "type" => "string" }, "path" => { "type" => "string" } }, "required" => {"pattern", "path"} } }) aAdd(aFuncs, { "name" => "filesystem_read", "description" => "Read file content.", "parameters" => { "type" => "object", "properties" => { "file" => { "type" => "string" } }, "required" => {"file"} } }) aAdd(aFuncs, { "name" => "filesystem_write", "description" => "Write file content.", "parameters" => { "type" => "object", "properties" => { "file" => { "type" => "string" }, "content" => { "type" => "string" } }, "required" => {"file", "content"} } }) aAdd(aFuncs, { "name" => "shell_execute", "description" => "Execute shell command.", "parameters" => { "type" => "object", "properties" => { "command" => { "type" => "string" } }, "required" => {"command"} } }) aAdd(aFuncs, { "name" => "identity_get_context", "description" => "Returns the identity and user soul information. Use this to know who you are and who the user is.", "parameters" => { "type" => "object", "properties" => {=>}, "required" => {} } }) retu aFuncs //------------------------------------------------------------------------------ function ExecuteAiosSkill(cName, hArgs) local hRes := { "success" => .f., "error" => "" } do case case cName == 'filesystem_search' .or. cName == 'filesystem_read' .or. cName == 'filesystem_write' hRes := Aios_FileSystem(cName, hArgs) case cName == 'shell_execute' hRes := Aios_Shell(hArgs) case cName == 'identity_get_context' hRes := Aios_Identity() endcase retu hRes //------------------------------------------------------------------------------ function Aios_Identity() local hR := { "success" => .t. }, cId := "", cSoul := "" if File("persona\IDENTITY.md") ; cId := MemoRead("persona\IDENTITY.md") ; endif if File("persona\SOUL.md") ; cSoul := MemoRead("persona\SOUL.md") ; endif hR['identity'] := cId hR['soul'] := cSoul if Empty(cId) .and. Empty(cSoul) ; hR['success'] := .f. ; hR['error'] := "Identity files not found" ; endif retu hR //------------------------------------------------------------------------------ function Aios_FileSystem(cAct, hP) local hR := { "success" => .t. }, cPat, cPath, cFile, aRes := {} if cAct == 'filesystem_search' cPat := hb_HGetDef(hP, 'pattern', '*.*') ; cPath := hb_HGetDef(hP, 'path', 'c:\hix') for each cFile in Directory(cPath + '\' + cPat) if !(cFile[1] $ '.,') ; aAdd(aRes, { "name" => cFile[1], "size" => cFile[2], "date" => DToC(cFile[3]), "time" => cFile[4] }) ; endif next hR['files'] := aRes ; hR['count'] := Len(aRes) elseif cAct == 'filesystem_read' cFile := hb_HGetDef(hP, 'file', '') if File(cFile) ; hR['content'] := Left(MemoRead(cFile), 5000) ; else ; hR['success'] := .f. ; hR['error'] := "File not found" ; endif elseif cAct == 'filesystem_write' if MemoWrit(hb_HGetDef(hP, 'file', ''), hb_HGetDef(hP, 'content', '')) ; hR['msg'] := "Stored" else ; hR['success'] := .f. ; hR['error'] := "Writ error" ; endif endif retu hR //------------------------------------------------------------------------------ function Aios_Shell(hP) local hR := { "success" => .t. }, cCmd := hb_HGetDef(hP, 'command', '') if Empty(cCmd) ; hR['success'] := .f. ; return hR ; endif hb_Run(cCmd + " > shell_out.tmp 2>&1") hR['output'] := MemoRead("shell_out.tmp") ; FErase("shell_out.tmp") retu hR //------------------------------------------------------------------------------ function GetAiosApiKey() local cKey := GetEnv("GEMINI_API_KEY") if Empty(cKey) .and. File("gemini_config.json") cKey := hb_HGetDef(hb_jsonDecode(MemoRead("gemini_config.json")), "api_key", "") endif retu cKey //------------------------------------------------------------------------------ function Hix_UrlDecode( cT ) local cR := "", n := 1, cX if Empty(cT) ; return "" ; endif cT := StrTran( cT, "+", " " ) do while n <= Len( cT ) if SubStr( cT, n, 1 ) == "%" cX := SubStr( cT, n + 1, 2 ) ; cR += Chr( Hix_HexToNum( cX ) ) ; n += 3 else ; cR += SubStr( cT, n, 1 ) ; n++ ; endif enddo retu cR //------------------------------------------------------------------------------ function Hix_HexToNum( cX ) local n := 0, i, cC, nV cX := Upper( cX ) for i := 1 to Len( cX ) cC := SubStr( cX, i, 1 ) ; nV := At( cC, "0123456789ABCDEF" ) - 1 if nV >= 0 ; n := n * 16 + nV ; endif next retu n
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: OpenClaw concepts on HIX

Post by Antonio Linares »

Persistent support for "PERSONA" files: IDENTITY.md and SOUL.md (like OpenClaw)

Image

gemini_aios_v13.prg

Code: Select all | Expand

// gemini_aios_v13.prg - Memory Fix Version
#include "hbcurl.ch"

function main()
    local cQuery, cModel, cHistory, cLogDoc := "", hIdContext, cSystemPrompt := ""
    local hResult
    
cQuery := UGet("query") cModel := UGet("model") cHistory := UGet("history") if Empty(cQuery) ; cQuery := UPost("query") ; endif if Empty(cModel) ; cModel := UPost("model") ; endif if Empty(cHistory) ; cHistory := UPost("history") ; endif // Auto-decode if raw if !Empty(cHistory) .and. "%" $ hb_ValToStr(cHistory) cHistory := Hix_UrlDecode(hb_ValToStr(cHistory)) endif if !Empty(cQuery) .and. "%" $ hb_ValToStr(cQuery) cQuery := Hix_UrlDecode(hb_ValToStr(cQuery)) endif cLogDoc := hb_eol() + ">>> [" + Time() + "] NEW REQ" + hb_eol() cLogDoc += "QUERY: " + hb_ValToStr(cQuery) + hb_eol() if cQuery == "ping" UWrite('{"success":true, "text":"pong"}') return "" endif if Empty(cQuery) UWrite('{"success":false, "error":"No query provided"}') return "" endif if Empty(cModel) ; cModel := "gemini-2.0-flash" ; endif // Pre-load identity context as system instructions hIdContext := Aios_Identity() cSystemPrompt := "Eres HIX AIOS (Harbourix AI Operating System)." + hb_eol() if hIdContext['success'] cSystemPrompt += "Tu IDENTIDAD actual:" + hb_eol() + hIdContext['identity'] + hb_eol() cSystemPrompt += "Tu ALMA (User Data):" + hb_eol() + hIdContext['soul'] + hb_eol() cSystemPrompt += "IMPORTANTE: Puedes actualizar estos ficheros usando filesystem_write en las rutas: " + hb_eol() cSystemPrompt += "- persona\IDENTITY.md" + hb_eol() cSystemPrompt += "- persona\SOUL.md" + hb_eol() endif hResult := ExecuteReasoningLoop(cQuery, cModel, hb_ValToStr(cHistory), @cLogDoc, cSystemPrompt) cLogDoc += "<<< [" + Time() + "] RESP SENT" + hb_eol() MemoWrit("aios_debug.log", MemoRead("aios_debug.log") + cLogDoc) UWrite(hb_jsonEncode(hResult)) return "" //------------------------------------------------------------------------------ function ExecuteReasoningLoop(cQuery, cModel, cHistory, cTrace, cSystemPrompt) local aFunctions := BuildAiosFunctions() local aMessages := {}, nMaxSteps := 5, nStep := 0 local hResult := {=>}, aStepsArr := {}, hMsg, hGeminiResult, hSkillResult local nErr
if !Empty(cHistory) nErr := hb_jsonDecode(cHistory, @aMessages) // In HIX, hb_jsonDecode returns the POS where it stopped. // If it reaches the end, it's successful. if ValType(aMessages) != "A" .or. Len(aMessages) == 0 if nErr == 0 .or. nErr < Len(AllTrim(cHistory)) cTrace += "!! History Decode Failed. Pos: " + hb_ValToStr(nErr) + hb_eol() aMessages := {} endif endif endif if ValType(aMessages) != "A" ; aMessages := {} ; endif cTrace += "HIST_TURNS: " + hb_ValToStr(Len(aMessages)) + hb_eol() aAdd(aMessages, { "role" => "user", "parts" => { { "text" => cQuery } } }) do while nStep < nMaxSteps nStep++ hGeminiResult := GeminiCallFC(aMessages, aFunctions, cModel, @cTrace, cSystemPrompt) if !hGeminiResult['success'] hResult['success'] := .f. ; hResult['error'] := hGeminiResult['error'] ; retu hResult endif hMsg := { "role" => "model", "parts" => hGeminiResult["raw_parts"] } aAdd(aMessages, hMsg) if hGeminiResult['type'] == 'function_call' aAdd(aStepsArr, { "type" => "call", "name" => hGeminiResult['function_name'], "args" => hGeminiResult['arguments'] }) hSkillResult := ExecuteAiosSkill(hGeminiResult['function_name'], hGeminiResult['arguments']) aAdd(aMessages, { "role" => "function", "parts" => { { "functionResponse" => { "name" => hGeminiResult['function_name'], "response" => { "content" => hb_jsonEncode(hSkillResult) } } } } }) aAdd(aStepsArr, { "type" => "result", "success" => hSkillResult['success'], "result" => hSkillResult }) else hResult['success'] := .t. ; hResult['text'] := hGeminiResult['text'] ; hResult['steps'] := aStepsArr ; hResult['model_used'] := cModel retu hResult endif enddo hResult['success'] := .f. ; hResult['error'] := "Max steps" retu hResult //------------------------------------------------------------------------------ function GeminiCallFC(aMessages, aFunctions, cModel, cTrace, cSystemPrompt) local cApiKey := GetAiosApiKey(), cUrl, cJson, hCurl, nError, cResponse := "", hResult := {=>} local hPayload := { "contents" => aMessages, "tools" => { { "function_declarations" => aFunctions } } }
if !Empty(cSystemPrompt) hPayload["system_instruction"] := { "parts" => { { "text" => cSystemPrompt } } } endif cUrl := "https://generativelanguage.googleapis.com/v1beta/models/" + cModel + ":generateContent?key=" + cApiKey cJson := hb_jsonEncode(hPayload) hCurl := curl_easy_init() if !Empty(hCurl) curl_easy_setopt(hCurl, HB_CURLOPT_POST, .T.) curl_easy_setopt(hCurl, HB_CURLOPT_URL, cUrl) curl_easy_setopt(hCurl, HB_CURLOPT_HTTPHEADER, { "Content-Type: application/json" }) curl_easy_setopt(hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F.) curl_easy_setopt(hCurl, HB_CURLOPT_POSTFIELDS, cJson) curl_easy_setopt(hCurl, HB_CURLOPT_DL_BUFF_SETUP) nError := curl_easy_perform(hCurl) if nError == HB_CURLE_OK cResponse := curl_easy_dl_buff_get(hCurl) hResult := ParseAiosFCResponse(cResponse, @cTrace) else hResult['success'] := .f. ; hResult['error'] := "Curl error: " + hb_ValToStr(nError) cTrace += "CURL_ERR: " + hb_ValToStr(nError) + hb_eol() endif curl_easy_cleanup(hCurl) else ; hResult['success'] := .f. ; hResult['error'] := "Curl init failed" ; endif retu hResult //------------------------------------------------------------------------------ function ParseAiosFCResponse(cJSON, cTrace) local hResult := { "success" => .f. }, hResponse := {=>}, aParts, nErr := 0 local nStart, nEnd, cWorking
cJSON := StrTran(hb_ValToStr(cJSON), Chr(0), "") cWorking := AllTrim(cJSON) nStart := At("{", cWorking) nEnd := RAt("}", cWorking) if nStart > 0 .and. nEnd > nStart cWorking := SubStr(cWorking, nStart, nEnd - nStart + 1) endif nErr := hb_jsonDecode(cWorking, @hResponse) do while nErr > 0 .and. Len(cWorking) > 10 cWorking := Left(cWorking, Len(cWorking) - 1) if Right(cWorking, 1) $ '}"0123456789' nErr := hb_jsonDecode(cWorking, @hResponse) endif enddo if (nErr == 0 .or. nErr == Len(cWorking)) .and. hb_HHasKey(hResponse, "candidates") .and. Len(hResponse["candidates"]) > 0 aParts := hResponse["candidates"][1]["content"]["parts"] hResult['success'] := .t. ; hResult['raw_parts'] := aParts if hb_HHasKey(aParts[1], "functionCall") hResult['type'] := 'function_call' hResult['function_name'] := aParts[1]["functionCall"]["name"] hResult['arguments'] := aParts[1]["functionCall"]["args"] else hResult['type'] := 'text' hResult['text'] := hb_HGetDef(aParts[1], "text", "") endif else hResult['error'] := "API JSON malformed (" + hb_ValToStr(nErr) + ")" cTrace += "!! Decoder failed on: " + Left(cWorking, 100) + hb_eol() endif retu hResult //------------------------------------------------------------------------------ function BuildAiosFunctions() local aFuncs := {} aAdd(aFuncs, { "name" => "filesystem_search", "description" => "Search files. pattern, path.", "parameters" => { "type" => "object", "properties" => { "pattern" => { "type" => "string" }, "path" => { "type" => "string" } }, "required" => {"pattern", "path"} } }) aAdd(aFuncs, { "name" => "filesystem_read", "description" => "Read file content.", "parameters" => { "type" => "object", "properties" => { "file" => { "type" => "string" } }, "required" => {"file"} } }) aAdd(aFuncs, { "name" => "filesystem_write", "description" => "Write file content.", "parameters" => { "type" => "object", "properties" => { "file" => { "type" => "string" }, "content" => { "type" => "string" } }, "required" => {"file", "content"} } }) aAdd(aFuncs, { "name" => "shell_execute", "description" => "Execute shell command.", "parameters" => { "type" => "object", "properties" => { "command" => { "type" => "string" } }, "required" => {"command"} } }) aAdd(aFuncs, { "name" => "identity_get_context", "description" => "Returns the identity and user soul information. Use this to know who you are and who the user is.", "parameters" => { "type" => "object", "properties" => {=>}, "required" => {} } }) retu aFuncs //------------------------------------------------------------------------------ function ExecuteAiosSkill(cName, hArgs) local hRes := { "success" => .f., "error" => "" } do case case cName == 'filesystem_search' .or. cName == 'filesystem_read' .or. cName == 'filesystem_write' hRes := Aios_FileSystem(cName, hArgs) case cName == 'shell_execute' hRes := Aios_Shell(hArgs) case cName == 'identity_get_context' hRes := Aios_Identity() endcase retu hRes //------------------------------------------------------------------------------ function Aios_Identity() local hR := { "success" => .t. }, cId := "", cSoul := "" if File("persona\IDENTITY.md") ; cId := MemoRead("persona\IDENTITY.md") ; endif if File("persona\SOUL.md") ; cSoul := MemoRead("persona\SOUL.md") ; endif hR['identity'] := cId hR['soul'] := cSoul if Empty(cId) .and. Empty(cSoul) ; hR['success'] := .f. ; hR['error'] := "Identity files not found" ; endif retu hR //------------------------------------------------------------------------------ function Aios_FileSystem(cAct, hP) local hR := { "success" => .t. }, cPat, cPath, cFile, aRes := {} if cAct == 'filesystem_search' cPat := hb_HGetDef(hP, 'pattern', '*.*') ; cPath := hb_HGetDef(hP, 'path', 'c:\hix') for each cFile in Directory(cPath + '\' + cPat) if !(cFile[1] $ '.,') ; aAdd(aRes, { "name" => cFile[1], "size" => cFile[2], "date" => DToC(cFile[3]), "time" => cFile[4] }) ; endif next hR['files'] := aRes ; hR['count'] := Len(aRes) elseif cAct == 'filesystem_read' cFile := hb_HGetDef(hP, 'file', '') if File(cFile) ; hR['content'] := Left(MemoRead(cFile), 5000) ; else ; hR['success'] := .f. ; hR['error'] := "File not found" ; endif elseif cAct == 'filesystem_write' if MemoWrit(hb_HGetDef(hP, 'file', ''), hb_HGetDef(hP, 'content', '')) ; hR['msg'] := "Stored" else ; hR['success'] := .f. ; hR['error'] := "Writ error" ; endif endif retu hR //------------------------------------------------------------------------------ function Aios_Shell(hP) local hR := { "success" => .t. }, cCmd := hb_HGetDef(hP, 'command', '') if Empty(cCmd) ; hR['success'] := .f. ; return hR ; endif hb_Run(cCmd + " > shell_out.tmp 2>&1") hR['output'] := MemoRead("shell_out.tmp") ; FErase("shell_out.tmp") retu hR //------------------------------------------------------------------------------ function GetAiosApiKey() local cKey := GetEnv("GEMINI_API_KEY") if Empty(cKey) .and. File("gemini_config.json") cKey := hb_HGetDef(hb_jsonDecode(MemoRead("gemini_config.json")), "api_key", "") endif retu cKey //------------------------------------------------------------------------------ function Hix_UrlDecode( cT ) local cR := "", n := 1, cX if Empty(cT) ; return "" ; endif cT := StrTran( cT, "+", " " ) do while n <= Len( cT ) if SubStr( cT, n, 1 ) == "%" cX := SubStr( cT, n + 1, 2 ) ; cR += Chr( Hix_HexToNum( cX ) ) ; n += 3 else ; cR += SubStr( cT, n, 1 ) ; n++ ; endif enddo retu cR //------------------------------------------------------------------------------ function Hix_HexToNum( cX ) local n := 0, i, cC, nV cX := Upper( cX ) for i := 1 to Len( cX ) cC := SubStr( cX, i, 1 ) ; nV := At( cC, "0123456789ABCDEF" ) - 1 if nV >= 0 ; n := n * 16 + nV ; endif next retu n
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
Posts: 44508
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 717 times
Been thanked: 311 times
Contact:

Re: OpenClaw concepts on HIX

Post by Antonio Linares »

Telegram Skill for HIX:

telegram.prg

Code: Select all | Expand

// telegram.prg - Telegram Bot API Integration for HIX AIOS
#include "hbcurl.ch"

function Aios_Telegram(cAct, hP)
    local hR := { "success" => .t. }
    
do case case cAct == 'telegram_send_message' hR := Telegram_SendMessage(hb_HGetDef(hP, "chat_id", ""), hb_HGetDef(hP, "text", "")) case cAct == 'telegram_get_updates' hR := Telegram_GetUpdates() otherwise hR := { "success" => .f., "error" => "Telegram action not supported: " + cAct } endcase retu hR //------------------------------------------------------------------------------ function Telegram_SendMessage(cChatId, cText) local hR := { "success" => .f. }, cToken := GetTelegramToken() local cUrl, cPayload, hCurl, nError, cResponse := ""
if Empty(cToken) ; hR['error'] := "Telegram Token not found" ; retu hR ; endif if Empty(cChatId) ; hR['error'] := "Chat ID required" ; retu hR ; endif cUrl := "https://api.telegram.org/bot" + cToken + "/sendMessage" cPayload := hb_jsonEncode({ "chat_id" => cChatId, "text" => cText }) hCurl := curl_easy_init() if !Empty(hCurl) curl_easy_setopt(hCurl, HB_CURLOPT_POST, .T.) curl_easy_setopt(hCurl, HB_CURLOPT_URL, cUrl) curl_easy_setopt(hCurl, HB_CURLOPT_HTTPHEADER, { "Content-Type: application/json" }) curl_easy_setopt(hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F.) curl_easy_setopt(hCurl, HB_CURLOPT_POSTFIELDS, cPayload) curl_easy_setopt(hCurl, HB_CURLOPT_DL_BUFF_SETUP) nError := curl_easy_perform(hCurl) if nError == HB_CURLE_OK cResponse := curl_easy_dl_buff_get(hCurl) hR['success'] := .t. hR['raw_response'] := cResponse else hR['error'] := "Curl error: " + hb_ValToStr(nError) endif curl_easy_cleanup(hCurl) else hR['error'] := "Curl init failed" endif retu hR //------------------------------------------------------------------------------ function Telegram_GetUpdates() local hR := { "success" => .f. }, cToken := GetTelegramToken() local cUrl, hCurl, nError, cResponse := ""
if Empty(cToken) ; hR['error'] := "Telegram Token not found" ; retu hR ; endif cUrl := "https://api.telegram.org/bot" + cToken + "/getUpdates" hCurl := curl_easy_init() if !Empty(hCurl) curl_easy_setopt(hCurl, HB_CURLOPT_URL, cUrl) curl_easy_setopt(hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F.) curl_easy_setopt(hCurl, HB_CURLOPT_DL_BUFF_SETUP) nError := curl_easy_perform(hCurl) if nError == HB_CURLE_OK cResponse := curl_easy_dl_buff_get(hCurl) hR['success'] := .t. hR['updates'] := cResponse else hR['error'] := "Curl error: " + hb_ValToStr(nError) endif curl_easy_cleanup(hCurl) else hR['error'] := "Curl init failed" endif retu hR //------------------------------------------------------------------------------ function GetTelegramToken() local cKey := GetEnv("TELEGRAM_BOT_TOKEN") local hConfig if Empty(cKey) .and. File("telegram_config.json") hConfig := {=>} hb_jsonDecode(MemoRead("telegram_config.json"), @hConfig) cKey := hb_HGetDef(hConfig, "bot_token", "") endif retu cKey
regards, saludos

Antonio Linares
www.fivetechsoft.com
Post Reply