chore: auto-commit (27 archivos)
- .claude/CLAUDE.md - .claude/rules/create_agent.md - agents/_specials/father-bot/prompts/system.md - agents/_template/config.yaml - agents/_template_robot/config.yaml - cmd/agentctl/autoavatar.go - cmd/launcher/sqlite.go - dev-scripts/_common.sh - dev-scripts/agent/create-full.sh - dev-scripts/agent/delete-full.sh - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -126,6 +126,23 @@ Templates: `agents/_template/` (agent) y `agents/_template_robot/` (robot).
|
|||||||
|
|
||||||
**Convención `_` prefijo**: los directorios con prefijo `_` en `agents/` son del sistema, no agentes desplegables. Incluye: `_template`, `_template_robot`, `_specials`.
|
**Convención `_` prefijo**: los directorios con prefijo `_` en `agents/` son del sistema, no agentes desplegables. Incluye: `_template`, `_template_robot`, `_specials`.
|
||||||
|
|
||||||
|
### REGLA DE PROYECTO — Provider LLM default: `claude-code`
|
||||||
|
|
||||||
|
TODOS los agentes nuevos usan `provider: claude-code` (subprocess `claude -p`) por defecto. Razones:
|
||||||
|
- No requiere API key (autentica via el CLI `claude` ya instalado).
|
||||||
|
- Acceso nativo a Bash/Read/Edit/Write/Glob/Grep — los agentes pueden interactuar con el sistema sin tools custom.
|
||||||
|
- Permission mode `bypassPermissions` + `working_dir` aislado fuera del repo.
|
||||||
|
- `streaming: true` + `show_tool_progress: true` para feedback en Matrix.
|
||||||
|
|
||||||
|
Override a `openai`/`anthropic` SOLO si:
|
||||||
|
- Caso de uso requiere un modelo no soportado por claude-code.
|
||||||
|
- Latencia critica (claude-code arranca un subprocess por request).
|
||||||
|
- Aislamiento total del filesystem (claude-code tiene acceso a `working_dir`).
|
||||||
|
|
||||||
|
`detect-provider.sh` prioriza `claude-code` si el binario `claude` esta en PATH. Si no, cae a `openai` o `anthropic` segun keys disponibles.
|
||||||
|
|
||||||
|
`./dev-scripts/agent/create-full.sh` y `personalize.sh` heredan este default. `father-bot` esta instruido para usar `claude-code` salvo que el usuario pida explicitamente otro provider.
|
||||||
|
|
||||||
| ID | Tipo | LLM | Descripcion |
|
| ID | Tipo | LLM | Descripcion |
|
||||||
|----|------|-----|-------------|
|
|----|------|-----|-------------|
|
||||||
| assistant-bot | agent | GPT-4o | Asistente general, DMs |
|
| assistant-bot | agent | GPT-4o | Asistente general, DMs |
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ Todo agente o robot creado debe pasar por TODOS estos pasos, en orden estricto:
|
|||||||
| `display-name` | si | — | `"Monitor Agent"` |
|
| `display-name` | si | — | `"Monitor Agent"` |
|
||||||
| `description` | si | — | `"Monitorea servicios y reporta estado"` |
|
| `description` | si | — | `"Monitorea servicios y reporta estado"` |
|
||||||
| `type` | no | `agent` | `agent` o `robot` |
|
| `type` | no | `agent` | `agent` o `robot` |
|
||||||
| `llm.provider` | no (N/A para robots) | `openai` | `openai` o `anthropic` |
|
| `llm.provider` | no (N/A para robots) | **`claude-code`** | `claude-code` (default), `openai`, `anthropic` |
|
||||||
| `llm.model` | no (N/A para robots) | `gpt-4o` | `gpt-4o`, `claude-sonnet-4-20250514` |
|
| `llm.model` | no (N/A para robots) | `sonnet` | `sonnet` (claude-code), `gpt-4o` (openai), `claude-sonnet-4-20250514` (anthropic) |
|
||||||
| `tool_use` | no (N/A para robots) | `false` | `true` si necesita herramientas |
|
| `tool_use` | no (N/A para robots) | `false` | `true` si necesita herramientas |
|
||||||
| System prompt | si (N/A para robots) | — | Texto describiendo rol y capacidades |
|
| System prompt | si (N/A para robots) | — | Texto describiendo rol y capacidades |
|
||||||
|
|
||||||
@@ -69,11 +69,12 @@ Si tienes todos los datos del agente (description + system prompt), el Paso 8 pu
|
|||||||
```bash
|
```bash
|
||||||
./dev-scripts/agent/create-full.sh <agent-id> "Display Name" \
|
./dev-scripts/agent/create-full.sh <agent-id> "Display Name" \
|
||||||
--description "<descripcion>" \
|
--description "<descripcion>" \
|
||||||
--provider <openai|anthropic> \
|
|
||||||
--system-prompt "<system prompt con seccion de seguridad>" \
|
--system-prompt "<system prompt con seccion de seguridad>" \
|
||||||
|
[--provider <claude-code|openai|anthropic>] \
|
||||||
[--tone <friendly|professional|casual|technical>] \
|
[--tone <friendly|professional|casual|technical>] \
|
||||||
[--prefix "<emoji>"] \
|
[--prefix "<emoji>"] \
|
||||||
[--tool-use]
|
[--tool-use] \
|
||||||
|
[--avatar <URL_o_ruta_local>]
|
||||||
```
|
```
|
||||||
|
|
||||||
Este script ejecuta en orden: scaffold, build, register Matrix, verify E2EE, auto-avatar, display name, **personalizar (auto)**, notify.
|
Este script ejecuta en orden: scaffold, build, register Matrix, verify E2EE, auto-avatar, display name, **personalizar (auto)**, notify.
|
||||||
@@ -86,7 +87,7 @@ Crea todos los archivos, registra en el launcher, genera todas las env vars en `
|
|||||||
./dev-scripts/agent/personalize.sh <agent-id> --description "..." --system-prompt "..."
|
./dev-scripts/agent/personalize.sh <agent-id> --description "..." --system-prompt "..."
|
||||||
```
|
```
|
||||||
|
|
||||||
**Auto-detección de provider**: omitir `--provider` para que `detect-provider.sh` elija automáticamente según `.env`.
|
**REGLA DE PROYECTO — Provider default = `claude-code`**: TODOS los agentes nuevos usan `claude-code` (subprocess `claude -p`) por defecto. NO requiere API key, autentica via el CLI `claude` ya instalado. Solo cambiar a `openai`/`anthropic` si hay razon explicita (modelo no disponible en claude-code, requisitos de latencia distintos, etc.). `detect-provider.sh` ya prioriza `claude-code` si el binario `claude` esta en PATH.
|
||||||
|
|
||||||
Despues del script, continuar con pasos 9-12 (rebuild, start, health check, self-introduce).
|
Despues del script, continuar con pasos 9-12 (rebuild, start, health check, self-introduce).
|
||||||
|
|
||||||
@@ -146,23 +147,29 @@ agent:
|
|||||||
description: "<la descripcion del agente>"
|
description: "<la descripcion del agente>"
|
||||||
```
|
```
|
||||||
|
|
||||||
**LLM** (si quieres cambiar provider/model):
|
**LLM — DEFAULT `claude-code`** (subproceso `claude -p`, sin API key):
|
||||||
```yaml
|
```yaml
|
||||||
llm:
|
llm:
|
||||||
primary:
|
primary:
|
||||||
provider: anthropic # o openai (default)
|
provider: claude-code # DEFAULT — usar SIEMPRE salvo razon explicita
|
||||||
model: claude-sonnet-4-20250514 # o gpt-4o (default)
|
model: "sonnet"
|
||||||
api_key_env: ANTHROPIC_API_KEY # o OPENAI_API_KEY (default)
|
api_key_env: "" # claude-code no usa api key
|
||||||
|
claude_code:
|
||||||
|
working_dir: "/tmp/claude-agents/<agent-id>" # SIEMPRE fuera del repo
|
||||||
|
permission_mode: "bypassPermissions"
|
||||||
|
model: "sonnet"
|
||||||
|
fallback_model: "haiku"
|
||||||
|
streaming: true
|
||||||
|
show_tool_progress: true
|
||||||
```
|
```
|
||||||
|
|
||||||
**Claude-code provider** (si usa `claude-code` como provider):
|
**Override a API providers** (solo si claude-code no encaja):
|
||||||
```yaml
|
```yaml
|
||||||
llm:
|
llm:
|
||||||
primary:
|
primary:
|
||||||
provider: claude-code
|
provider: openai # o anthropic
|
||||||
claude_code:
|
model: gpt-4o # o claude-sonnet-4-20250514
|
||||||
working_dir: "/tmp/claude-agents/<agent-id>" # SIEMPRE configurar, nunca dejar vacio
|
api_key_env: OPENAI_API_KEY # o ANTHROPIC_API_KEY
|
||||||
permission_mode: "bypassPermissions"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Importante**: `working_dir` debe apuntar fuera del repositorio para evitar que el subproceso `claude -p` acceda al codigo fuente. Si se deja vacio, se usara un directorio temporal (con WARN en logs).
|
**Importante**: `working_dir` debe apuntar fuera del repositorio para evitar que el subproceso `claude -p` acceda al codigo fuente. Si se deja vacio, se usara un directorio temporal (con WARN en logs).
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ Antes de crear nada, extrae estos datos del mensaje del usuario:
|
|||||||
| `display-name` | si | `"Monitor Agent"` |
|
| `display-name` | si | `"Monitor Agent"` |
|
||||||
| `description` | si | `"Monitorea servicios y reporta estado"` |
|
| `description` | si | `"Monitorea servicios y reporta estado"` |
|
||||||
| `type` | si | `agent` o `robot` |
|
| `type` | si | `agent` o `robot` |
|
||||||
| `provider` | no (N/A para robots) | `openai`, `anthropic`, `claude-code` |
|
| `provider` | no (N/A para robots) | **`claude-code` (DEFAULT)**, `openai`, `anthropic` |
|
||||||
| `model` | no (N/A para robots) | `gpt-4o`, `claude-sonnet-4-20250514` |
|
| `model` | no (N/A para robots) | `sonnet` (default), `gpt-4o`, `claude-sonnet-4-20250514` |
|
||||||
| `tools necesarias` | no | SSH, HTTP, file, etc. |
|
| `tools necesarias` | no | SSH, HTTP, file, etc. |
|
||||||
|
|
||||||
Si faltan datos criticos, **pregunta antes de crear**. No asumas.
|
Si faltan datos criticos, **pregunta antes de crear**. No asumas.
|
||||||
@@ -98,14 +98,21 @@ Si faltan datos criticos, **pregunta antes de crear**. No asumas.
|
|||||||
./dev-scripts/agent/create-full.sh <agent-id> "<display-name>" \
|
./dev-scripts/agent/create-full.sh <agent-id> "<display-name>" \
|
||||||
--description "<descripcion del agente>" \
|
--description "<descripcion del agente>" \
|
||||||
--system-prompt "<system prompt completo con seccion de seguridad>" \
|
--system-prompt "<system prompt completo con seccion de seguridad>" \
|
||||||
[--provider <openai|anthropic>] \
|
[--provider <claude-code|openai|anthropic>] \
|
||||||
[--model <gpt-4o|claude-sonnet-4-20250514>] \
|
[--model <sonnet|gpt-4o|claude-sonnet-4-20250514>] \
|
||||||
[--tone <friendly|professional|casual|technical>] \
|
[--tone <friendly|professional|casual|technical>] \
|
||||||
[--prefix "<emoji>"] \
|
[--prefix "<emoji>"] \
|
||||||
[--tool-use] \
|
[--tool-use] \
|
||||||
[--language <es|en>]
|
[--language <es|en>] \
|
||||||
|
[--avatar <URL_o_ruta_local>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**REGLA DE PROYECTO — Provider default es `claude-code`**. Usa siempre `claude-code` (subprocess `claude -p`) salvo que el usuario pida explicitamente otro provider. `claude-code` no requiere API key — autentica via el CLI `claude` ya instalado en el sistema. Solo cambia a `openai`/`anthropic` si el usuario lo pide o si el caso de uso requiere un modelo no soportado por claude-code.
|
||||||
|
|
||||||
|
**Avatar personalizado**: si el usuario te da una imagen o URL para la foto del bot
|
||||||
|
(ej. "ponle un pikachu" + URL/archivo), pasa el valor a `--avatar`. Acepta tanto
|
||||||
|
URLs `https://...` como rutas locales. Sin el flag, se genera uno random.
|
||||||
|
|
||||||
Si es un robot, anadir `--type robot`:
|
Si es un robot, anadir `--type robot`:
|
||||||
```bash
|
```bash
|
||||||
./dev-scripts/agent/create-full.sh <agent-id> "<display-name>" --type robot \
|
./dev-scripts/agent/create-full.sh <agent-id> "<display-name>" --type robot \
|
||||||
@@ -122,7 +129,7 @@ Con los flags `--description` y `--system-prompt`, el script ejecuta **automatic
|
|||||||
7. **Display name**: configura nombre visible en Matrix
|
7. **Display name**: configura nombre visible en Matrix
|
||||||
8. **Personalize**: genera `config.yaml`, `agent.go` y `prompts/system.md` automaticamente
|
8. **Personalize**: genera `config.yaml`, `agent.go` y `prompts/system.md` automaticamente
|
||||||
|
|
||||||
**Provider auto-detectado**: si no se pasa `--provider`, `detect-provider.sh` elige automaticamente segun las API keys disponibles en `.env`.
|
**Provider auto-detectado**: si no se pasa `--provider`, `detect-provider.sh` elige `claude-code` por defecto (si el binario `claude` esta en PATH) — esa es la regla del proyecto. Fallback a `openai`/`anthropic` solo si `claude` CLI no esta disponible.
|
||||||
|
|
||||||
**Si el script falla**, reporta el error al usuario con los logs y sugiere recovery manual.
|
**Si el script falla**, reporta el error al usuario con los logs y sugiere recovery manual.
|
||||||
|
|
||||||
|
|||||||
@@ -64,28 +64,28 @@ personality:
|
|||||||
# ============================================
|
# ============================================
|
||||||
llm:
|
llm:
|
||||||
primary:
|
primary:
|
||||||
provider: openai # openai | anthropic | claude-code
|
provider: claude-code # claude-code (DEFAULT) | openai | anthropic
|
||||||
model: "gpt-4o"
|
model: "sonnet"
|
||||||
api_key_env: OPENAI_API_KEY
|
api_key_env: "" # claude-code no usa api key — autentica via `claude` CLI
|
||||||
base_url: ""
|
base_url: ""
|
||||||
max_tokens: 4096
|
max_tokens: 4096
|
||||||
temperature: 0.7
|
temperature: 0.7
|
||||||
|
|
||||||
# Solo si provider: claude-code
|
# Solo si provider: claude-code (default)
|
||||||
claude_code:
|
claude_code:
|
||||||
binary: "claude"
|
binary: "claude"
|
||||||
timeout: 3m
|
timeout: 3m
|
||||||
disable_tools: false
|
disable_tools: false
|
||||||
allowed_tools: []
|
allowed_tools: [Bash, Read, Edit, Write, Glob, Grep]
|
||||||
disallowed_tools: []
|
disallowed_tools: []
|
||||||
working_dir: "" # IMPORTANTE: configurar fuera del repo
|
working_dir: "" # IMPORTANTE: configurar fuera del repo
|
||||||
permission_mode: "default"
|
permission_mode: "bypassPermissions"
|
||||||
model: "sonnet"
|
model: "sonnet"
|
||||||
fallback_model: ""
|
fallback_model: "haiku"
|
||||||
session_id: ""
|
session_id: ""
|
||||||
add_dirs: []
|
add_dirs: []
|
||||||
streaming: false # true para usar --output-format stream-json (progreso en tiempo real)
|
streaming: true # progreso en tiempo real en Matrix
|
||||||
show_tool_progress: false # true para mostrar en Matrix que herramientas usa el agente
|
show_tool_progress: true # muestra que tools usa el agente
|
||||||
|
|
||||||
fallback:
|
fallback:
|
||||||
provider: ""
|
provider: ""
|
||||||
@@ -190,9 +190,12 @@ matrix:
|
|||||||
device_id: "DEVICEID"
|
device_id: "DEVICEID"
|
||||||
|
|
||||||
encryption:
|
encryption:
|
||||||
enabled: false
|
enabled: true
|
||||||
store_path: "./agents/_template/data/crypto/"
|
store_path: "./agents/_template/data/crypto/"
|
||||||
pickle_key_env: PICKLE_KEY_TEMPLATE
|
pickle_key_env: PICKLE_KEY_TEMPLATE
|
||||||
|
recovery_key_env: SSSS_RECOVERY_KEY_TEMPLATE
|
||||||
|
access_token_env: MATRIX_TOKEN_TEMPLATE
|
||||||
|
user_id: "@_template:matrix.example.com"
|
||||||
trust_mode: tofu
|
trust_mode: tofu
|
||||||
recovery_key_env: ""
|
recovery_key_env: ""
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ matrix:
|
|||||||
device_id: "DEVICEID"
|
device_id: "DEVICEID"
|
||||||
|
|
||||||
encryption:
|
encryption:
|
||||||
enabled: false
|
enabled: true
|
||||||
store_path: "./agents/_template_robot/data/crypto/"
|
store_path: "./agents/_template_robot/data/crypto/"
|
||||||
pickle_key_env: PICKLE_KEY_ROBOT
|
pickle_key_env: PICKLE_KEY_ROBOT
|
||||||
trust_mode: tofu
|
trust_mode: tofu
|
||||||
recovery_key_env: ""
|
recovery_key_env: SSSS_RECOVERY_KEY_ROBOT
|
||||||
|
|
||||||
rooms:
|
rooms:
|
||||||
listen: []
|
listen: []
|
||||||
|
|||||||
@@ -19,23 +19,38 @@ func autoAvatarCmd() *cobra.Command {
|
|||||||
set string
|
set string
|
||||||
size int
|
size int
|
||||||
dryRun bool
|
dryRun bool
|
||||||
|
fromURL string
|
||||||
|
fromFile string
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "auto-avatar <agent-id>",
|
Use: "auto-avatar <agent-id>",
|
||||||
Short: "Generate and set a random avatar from a free provider",
|
Short: "Generate and set a random avatar from a free provider (or a custom URL/file)",
|
||||||
Long: `Fetches a unique avatar image from a free provider (dicebear, robohash, multiavatar)
|
Long: `Fetches a unique avatar image from a free provider (dicebear, robohash, multiavatar)
|
||||||
using the agent ID as seed, uploads it to the Matrix media repo, and sets it as the bot's avatar.
|
using the agent ID as seed, uploads it to the Matrix media repo, and sets it as the bot's avatar.
|
||||||
|
|
||||||
|
To use a custom avatar instead of the random generator, pass --from-url or --from-file.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
agentctl auto-avatar assistant-bot
|
agentctl auto-avatar assistant-bot
|
||||||
agentctl auto-avatar assistant-bot --provider robohash --set set1
|
agentctl auto-avatar assistant-bot --provider robohash --set set1
|
||||||
agentctl auto-avatar assistant-bot --provider dicebear --style pixel-art
|
agentctl auto-avatar assistant-bot --provider dicebear --style pixel-art
|
||||||
agentctl auto-avatar assistant-bot --dry-run # only show the URL`,
|
agentctl auto-avatar assistant-bot --dry-run # only show the URL
|
||||||
|
agentctl auto-avatar pokemon-expert --from-url https://example/pikachu.png
|
||||||
|
agentctl auto-avatar pokemon-expert --from-file ./avatars/pokemon.png`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
agentID := args[0]
|
agentID := args[0]
|
||||||
|
|
||||||
|
if fromURL != "" && fromFile != "" {
|
||||||
|
return fmt.Errorf("--from-url and --from-file are mutually exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom source path: skip random generator entirely.
|
||||||
|
if fromURL != "" || fromFile != "" {
|
||||||
|
return runCustomAvatar(agentID, fromURL, fromFile, dryRun)
|
||||||
|
}
|
||||||
|
|
||||||
opts := avatar.DefaultOptions()
|
opts := avatar.DefaultOptions()
|
||||||
if size > 0 {
|
if size > 0 {
|
||||||
opts.Size = size
|
opts.Size = size
|
||||||
@@ -90,6 +105,58 @@ Examples:
|
|||||||
cmd.Flags().StringVar(&set, "set", "", "RoboHash set: set1 (robots), set2 (monsters), set3 (heads), set4 (cats), set5 (humans)")
|
cmd.Flags().StringVar(&set, "set", "", "RoboHash set: set1 (robots), set2 (monsters), set3 (heads), set4 (cats), set5 (humans)")
|
||||||
cmd.Flags().IntVar(&size, "size", 256, "Image size in pixels (square)")
|
cmd.Flags().IntVar(&size, "size", 256, "Image size in pixels (square)")
|
||||||
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "Only print the image URL without fetching or uploading")
|
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "Only print the image URL without fetching or uploading")
|
||||||
|
cmd.Flags().StringVar(&fromURL, "from-url", "", "Use this URL as the avatar source (overrides provider/style)")
|
||||||
|
cmd.Flags().StringVar(&fromFile, "from-file", "", "Use this local file as the avatar source (overrides provider/style)")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runCustomAvatar uploads a user-supplied image (URL or local file) as the agent's avatar.
|
||||||
|
func runCustomAvatar(agentID, fromURL, fromFile string, dryRun bool) error {
|
||||||
|
var srcPath string
|
||||||
|
var srcLabel string
|
||||||
|
|
||||||
|
if fromURL != "" {
|
||||||
|
srcLabel = fromURL
|
||||||
|
if dryRun {
|
||||||
|
fmt.Printf("url %-20s %s\n", agentID, fromURL)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tmpPath, err := shellavatar.Download(context.Background(), fromURL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("download avatar from %s: %w", fromURL, err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpPath)
|
||||||
|
srcPath = tmpPath
|
||||||
|
} else {
|
||||||
|
srcLabel = fromFile
|
||||||
|
if _, err := os.Stat(fromFile); err != nil {
|
||||||
|
return fmt.Errorf("avatar file %s: %w", fromFile, err)
|
||||||
|
}
|
||||||
|
if dryRun {
|
||||||
|
fmt.Printf("file %-20s %s\n", agentID, fromFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
srcPath = fromFile
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("fetch %-20s %s\n", agentID, srcLabel)
|
||||||
|
|
||||||
|
cfg, err := loadMatrixCfg(agentID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := shellmatrix.New(cfg.Matrix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("matrix client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uri, err := client.SetAvatar(context.Background(), srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("ok %-20s avatar → %s\n", agentID, uri)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// mautrix dbutil opens sqlite as "sqlite3"; register the pure-Go driver
|
for _, name := range sql.Drivers() {
|
||||||
// under that name. We add a connection hook that sets WAL mode and a
|
if name == "sqlite3" {
|
||||||
// busy timeout on every connection to prevent SQLITE_BUSY crashes during
|
return
|
||||||
// concurrent writes (crypto store sync + memory store).
|
}
|
||||||
|
}
|
||||||
d := &moderncsqlite.Driver{}
|
d := &moderncsqlite.Driver{}
|
||||||
d.RegisterConnectionHook(sqlitePragmaHook)
|
d.RegisterConnectionHook(sqlitePragmaHook)
|
||||||
sql.Register("sqlite3", d)
|
sql.Register("sqlite3", d)
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ config_path_for() {
|
|||||||
for cfg in agents/*/config.yaml agents/_specials/*/config.yaml; do
|
for cfg in agents/*/config.yaml agents/_specials/*/config.yaml; do
|
||||||
[[ -f "$cfg" ]] || continue
|
[[ -f "$cfg" ]] || continue
|
||||||
local id
|
local id
|
||||||
id=$(grep -m1 '^ id:' "$cfg" | awk '{print $2}')
|
# Strip quotes from value: handles both `id: foo` and `id: "foo"`
|
||||||
|
id=$(grep -m1 '^ id:' "$cfg" | sed -E 's/^[^:]*:[[:space:]]*//; s/^"//; s/"$//; s/^'\''//; s/'\''$//')
|
||||||
if [[ "$id" == "$target_id" ]]; then
|
if [[ "$id" == "$target_id" ]]; then
|
||||||
echo "$cfg"
|
echo "$cfg"
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
#
|
#
|
||||||
# Flags de personalización (opcionales, activan el Paso 8 automático):
|
# Flags de personalización (opcionales, activan el Paso 8 automático):
|
||||||
# --description "<texto>" descripcion del agente
|
# --description "<texto>" descripcion del agente
|
||||||
# --provider <openai|anthropic|...> proveedor LLM (default: auto-detect)
|
# --provider <claude-code|openai|anthropic> proveedor LLM (default: claude-code)
|
||||||
|
# REGLA PROYECTO: usar claude-code SIEMPRE salvo razon explicita
|
||||||
# --model <modelo> modelo LLM (default: segun provider)
|
# --model <modelo> modelo LLM (default: segun provider)
|
||||||
# --tone <friendly|professional|...> tono (default: friendly)
|
# --tone <friendly|professional|...> tono (default: friendly)
|
||||||
# --prefix "<emoji>" emoji prefix (default: 🤖)
|
# --prefix "<emoji>" emoji prefix (default: 🤖)
|
||||||
@@ -37,6 +38,8 @@
|
|||||||
# --system-prompt-file <path> system prompt desde archivo
|
# --system-prompt-file <path> system prompt desde archivo
|
||||||
# --tool-use habilitar tool_use en config
|
# --tool-use habilitar tool_use en config
|
||||||
# --language <es|en> idioma (default: es)
|
# --language <es|en> idioma (default: es)
|
||||||
|
# --avatar <URL_o_ruta> imagen para el avatar (default: generador random)
|
||||||
|
# ej: https://example/pikachu.png o ./avatars/poke.png
|
||||||
#
|
#
|
||||||
# Requisitos en .env:
|
# Requisitos en .env:
|
||||||
# MATRIX_ADMIN_TOKEN, MATRIX_HOMESERVER, MATRIX_SERVER_NAME
|
# MATRIX_ADMIN_TOKEN, MATRIX_HOMESERVER, MATRIX_SERVER_NAME
|
||||||
@@ -88,10 +91,15 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--tool-use) PERSONALIZE_TOOL_USE=true; DO_PERSONALIZE=true; shift ;;
|
--tool-use) PERSONALIZE_TOOL_USE=true; DO_PERSONALIZE=true; shift ;;
|
||||||
--language) PERSONALIZE_LANGUAGE="${2:-es}"; DO_PERSONALIZE=true; shift 2 ;;
|
--language) PERSONALIZE_LANGUAGE="${2:-es}"; DO_PERSONALIZE=true; shift 2 ;;
|
||||||
--language=*) PERSONALIZE_LANGUAGE="${1#--language=}"; DO_PERSONALIZE=true; shift ;;
|
--language=*) PERSONALIZE_LANGUAGE="${1#--language=}"; DO_PERSONALIZE=true; shift ;;
|
||||||
|
--avatar) AVATAR_SOURCE="${2:-}"; shift 2 ;;
|
||||||
|
--avatar=*) AVATAR_SOURCE="${1#--avatar=}"; shift ;;
|
||||||
*) shift ;;
|
*) shift ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# AVATAR_SOURCE puede ser URL (http/https) o ruta local. Vacio = generador random.
|
||||||
|
: "${AVATAR_SOURCE:=}"
|
||||||
|
|
||||||
if [[ "$TYPE" == "robot" ]]; then
|
if [[ "$TYPE" == "robot" ]]; then
|
||||||
TYPE_LABEL="robot"
|
TYPE_LABEL="robot"
|
||||||
TYPE_EMOJI="🤖"
|
TYPE_EMOJI="🤖"
|
||||||
@@ -165,22 +173,34 @@ if [[ "$TYPE" == "robot" ]]; then
|
|||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Paso auto-avatar: Generar avatar automatico ─────────────────────────
|
# ── Paso auto-avatar: Generar/aplicar avatar ────────────────────────────
|
||||||
AVATAR_STEP=$((TOTAL_STEPS - 2))
|
AVATAR_STEP=$((TOTAL_STEPS - 2))
|
||||||
info "Paso ${AVATAR_STEP}/${TOTAL_STEPS} — Generando avatar automatico..."
|
info "Paso ${AVATAR_STEP}/${TOTAL_STEPS} — Configurando avatar del bot..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Resuelve el binario de agentctl
|
# Resuelve el binario de agentctl como array (preserva split por espacios)
|
||||||
if [[ -f "$REPO_ROOT/bin/agentctl" ]]; then
|
if [[ -f "$REPO_ROOT/bin/agentctl" ]]; then
|
||||||
CTL="$REPO_ROOT/bin/agentctl"
|
CTL_ARR=("$REPO_ROOT/bin/agentctl")
|
||||||
else
|
else
|
||||||
CTL="$GO run -tags goolm ./cmd/agentctl"
|
CTL_ARR=("$GO" run -tags goolm ./cmd/agentctl)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if $CTL auto-avatar "$ID" 2>&1; then
|
# Si el usuario pasa --avatar, usa la URL/ruta indicada en vez del generador random.
|
||||||
ok "Avatar generado y aplicado"
|
AVATAR_CMD=("${CTL_ARR[@]}" auto-avatar "$ID")
|
||||||
|
if [[ -n "$AVATAR_SOURCE" ]]; then
|
||||||
|
if [[ "$AVATAR_SOURCE" =~ ^https?:// ]]; then
|
||||||
|
AVATAR_CMD+=(--from-url "$AVATAR_SOURCE")
|
||||||
|
info "Usando avatar personalizado desde URL: $AVATAR_SOURCE"
|
||||||
|
else
|
||||||
|
AVATAR_CMD+=(--from-file "$AVATAR_SOURCE")
|
||||||
|
info "Usando avatar personalizado desde archivo: $AVATAR_SOURCE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if "${AVATAR_CMD[@]}" 2>&1; then
|
||||||
|
ok "Avatar configurado y aplicado"
|
||||||
else
|
else
|
||||||
warn "No se pudo generar avatar automatico (se puede hacer despues con: agentctl auto-avatar $ID)"
|
warn "No se pudo configurar avatar (se puede hacer despues con: agentctl auto-avatar $ID [--from-url <url> | --from-file <path>])"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -213,6 +233,21 @@ fi
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# ── Paso 8a (robots): aplicar --description al config.yaml ──────────────
|
||||||
|
# Los robots no tienen prompts/system.md ni agent.go (no LLM), pero su
|
||||||
|
# config.yaml SI tiene un campo `description:` que personalize.sh ignora.
|
||||||
|
# Para evitar que el robot quede con la descripcion del template literal,
|
||||||
|
# parcheamos la linea aqui.
|
||||||
|
if [[ "$TYPE" == "robot" ]] && [[ -n "$PERSONALIZE_DESCRIPTION" ]]; then
|
||||||
|
CFG_FILE="agents/$ID/config.yaml"
|
||||||
|
if [[ -f "$CFG_FILE" ]]; then
|
||||||
|
# Escapar caracteres especiales del valor para sed
|
||||||
|
ESCAPED_DESC="$(printf '%s' "$PERSONALIZE_DESCRIPTION" | sed -e 's/[\/&|]/\\&/g')"
|
||||||
|
sed -i "0,/^ description:.*/s|| description: \"$ESCAPED_DESC\"|" "$CFG_FILE"
|
||||||
|
ok "Descripcion del robot aplicada al config.yaml"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Paso 8 (automático, solo agents): Personalizar archivos ─────────────
|
# ── Paso 8 (automático, solo agents): Personalizar archivos ─────────────
|
||||||
PERSONALIZE_DONE=false
|
PERSONALIZE_DONE=false
|
||||||
if $DO_PERSONALIZE && [[ "$TYPE" != "robot" ]]; then
|
if $DO_PERSONALIZE && [[ "$TYPE" != "robot" ]]; then
|
||||||
|
|||||||
@@ -78,14 +78,16 @@ fi
|
|||||||
AGENT_DESC=""
|
AGENT_DESC=""
|
||||||
AGENT_TYPE="agent"
|
AGENT_TYPE="agent"
|
||||||
if [[ -f "$CFG_PATH" ]]; then
|
if [[ -f "$CFG_PATH" ]]; then
|
||||||
AGENT_DESC=$(grep -m1 'description:' "$CFG_PATH" | cut -d'"' -f2)
|
AGENT_DESC=$(grep -m1 'description:' "$CFG_PATH" | cut -d'"' -f2 || true)
|
||||||
TYPE_LINE=$(grep -m1 'type:' "$CFG_PATH" | awk '{print $2}')
|
TYPE_LINE=$(grep -m1 'type:' "$CFG_PATH" | awk '{print $2}' || true)
|
||||||
[[ -n "$TYPE_LINE" ]] && AGENT_TYPE="$TYPE_LINE"
|
if [[ -n "${TYPE_LINE:-}" ]]; then
|
||||||
|
AGENT_TYPE="$TYPE_LINE"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ok "Agente $ID encontrado en $AGENT_DIR/"
|
ok "Agente $ID encontrado en $AGENT_DIR/"
|
||||||
dim " Tipo: $AGENT_TYPE"
|
dim " Tipo: $AGENT_TYPE"
|
||||||
[[ -n "$AGENT_DESC" ]] && dim " Descripcion: $AGENT_DESC"
|
if [[ -n "$AGENT_DESC" ]]; then dim " Descripcion: $AGENT_DESC"; fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ── Confirmacion interactiva ────────────────────────────────────────────────
|
# ── Confirmacion interactiva ────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -2,37 +2,47 @@
|
|||||||
# detect-provider.sh — detecta el proveedor LLM disponible desde .env
|
# detect-provider.sh — detecta el proveedor LLM disponible desde .env
|
||||||
#
|
#
|
||||||
# Salida: dos palabras en stdout — "<provider> <model>"
|
# Salida: dos palabras en stdout — "<provider> <model>"
|
||||||
|
# claude-code sonnet (DEFAULT)
|
||||||
# openai gpt-4o
|
# openai gpt-4o
|
||||||
# anthropic claude-sonnet-4-20250514
|
# anthropic claude-sonnet-4-20250514
|
||||||
#
|
#
|
||||||
# Orden de detección:
|
# Orden de detección (claude-code primero — REGLA DEL PROYECTO):
|
||||||
# 1. OPENAI_API_KEY → openai gpt-4o
|
# 1. CLAUDE binary disponible en PATH → claude-code sonnet
|
||||||
# 2. ANTHROPIC_API_KEY → anthropic claude-sonnet-4-20250514
|
# 2. OPENAI_API_KEY → openai gpt-4o
|
||||||
# Fallback: openai gpt-4o (con warning en stderr)
|
# 3. ANTHROPIC_API_KEY → anthropic claude-sonnet-4-20250514
|
||||||
|
# Fallback: claude-code sonnet (binary `claude` debe estar instalado)
|
||||||
#
|
#
|
||||||
# Uso:
|
# Uso:
|
||||||
# read -r PROVIDER MODEL < <(./dev-scripts/agent/detect-provider.sh)
|
# read -r PROVIDER MODEL < <(./dev-scripts/agent/detect-provider.sh)
|
||||||
# ./dev-scripts/agent/detect-provider.sh # imprime "openai gpt-4o"
|
# ./dev-scripts/agent/detect-provider.sh # imprime "claude-code sonnet"
|
||||||
|
|
||||||
source "$(dirname "$0")/../_common.sh"
|
source "$(dirname "$0")/../_common.sh"
|
||||||
load_env
|
load_env
|
||||||
|
|
||||||
# Default models por provider
|
# Default models por provider
|
||||||
|
CLAUDE_CODE_DEFAULT_MODEL="sonnet"
|
||||||
OPENAI_DEFAULT_MODEL="gpt-4o"
|
OPENAI_DEFAULT_MODEL="gpt-4o"
|
||||||
ANTHROPIC_DEFAULT_MODEL="claude-sonnet-4-20250514"
|
ANTHROPIC_DEFAULT_MODEL="claude-sonnet-4-20250514"
|
||||||
|
|
||||||
# Detectar provider disponible
|
# 1. claude-code (preferido) — solo requiere el binario `claude` en PATH
|
||||||
|
if command -v claude >/dev/null 2>&1; then
|
||||||
|
echo "claude-code $CLAUDE_CODE_DEFAULT_MODEL"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. OpenAI API key
|
||||||
if [[ -n "${OPENAI_API_KEY:-}" ]]; then
|
if [[ -n "${OPENAI_API_KEY:-}" ]]; then
|
||||||
echo "openai $OPENAI_DEFAULT_MODEL"
|
echo "openai $OPENAI_DEFAULT_MODEL"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 3. Anthropic API key
|
||||||
if [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
|
if [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
|
||||||
echo "anthropic $ANTHROPIC_DEFAULT_MODEL"
|
echo "anthropic $ANTHROPIC_DEFAULT_MODEL"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fallback con warning
|
# Fallback: claude-code (warning porque el binario falta)
|
||||||
warn "Ninguna API key configurada (OPENAI_API_KEY, ANTHROPIC_API_KEY) — usando fallback openai/gpt-4o" >&2
|
warn "Ningun proveedor disponible (binary 'claude' missing, OPENAI_API_KEY/ANTHROPIC_API_KEY missing) — usando fallback claude-code/sonnet (instala claude CLI)" >&2
|
||||||
echo "openai $OPENAI_DEFAULT_MODEL"
|
echo "claude-code $CLAUDE_CODE_DEFAULT_MODEL"
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ sed -i "s/template: true/template: false/g" "$DIR/config.yaml"
|
|||||||
sed -i "s/enabled: true/enabled: true/g" "$DIR/config.yaml"
|
sed -i "s/enabled: true/enabled: true/g" "$DIR/config.yaml"
|
||||||
sed -i "s/MATRIX_TOKEN_TEMPLATE/MATRIX_TOKEN_${NORM}/g" "$DIR/config.yaml"
|
sed -i "s/MATRIX_TOKEN_TEMPLATE/MATRIX_TOKEN_${NORM}/g" "$DIR/config.yaml"
|
||||||
sed -i "s/PICKLE_KEY_TEMPLATE/PICKLE_KEY_${NORM}/g" "$DIR/config.yaml"
|
sed -i "s/PICKLE_KEY_TEMPLATE/PICKLE_KEY_${NORM}/g" "$DIR/config.yaml"
|
||||||
|
sed -i "s/SSSS_RECOVERY_KEY_TEMPLATE/SSSS_RECOVERY_KEY_${NORM}/g" "$DIR/config.yaml"
|
||||||
|
sed -i "s/SSSS_RECOVERY_KEY_ROBOT/SSSS_RECOVERY_KEY_${NORM}/g" "$DIR/config.yaml"
|
||||||
|
sed -i "s/MATRIX_TOKEN_ROBOT/MATRIX_TOKEN_${NORM}/g" "$DIR/config.yaml"
|
||||||
|
sed -i "s/PICKLE_KEY_ROBOT/PICKLE_KEY_${NORM}/g" "$DIR/config.yaml"
|
||||||
sed -i "s/@template:matrix.example.com/@$ID:\${MATRIX_SERVER_NAME}/g" "$DIR/config.yaml"
|
sed -i "s/@template:matrix.example.com/@$ID:\${MATRIX_SERVER_NAME}/g" "$DIR/config.yaml"
|
||||||
sed -i "s|https://matrix.example.com|\${MATRIX_HOMESERVER}|g" "$DIR/config.yaml"
|
sed -i "s|https://matrix.example.com|\${MATRIX_HOMESERVER}|g" "$DIR/config.yaml"
|
||||||
|
|
||||||
|
|||||||
@@ -186,7 +186,15 @@ for dev in "${DEVS[@]}"; do
|
|||||||
dev="$(echo "$dev" | xargs)" # trim spaces
|
dev="$(echo "$dev" | xargs)" # trim spaces
|
||||||
[[ -z "$dev" ]] && continue
|
[[ -z "$dev" ]] && continue
|
||||||
|
|
||||||
|
# Acepta ambos formatos:
|
||||||
|
# - "egutierrez" (bare username)
|
||||||
|
# - "@egutierrez:matrix-...organic-machine.com" (full MXID)
|
||||||
|
if [[ "$dev" == @*:* ]]; then
|
||||||
|
USER_ID="$dev"
|
||||||
|
else
|
||||||
USER_ID="@${dev}:${MATRIX_SERVER_NAME}"
|
USER_ID="@${dev}:${MATRIX_SERVER_NAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
info "Enviando DM de $ID a $USER_ID..."
|
info "Enviando DM de $ID a $USER_ID..."
|
||||||
|
|
||||||
send_dm "$USER_ID"
|
send_dm "$USER_ID"
|
||||||
|
|||||||
@@ -128,3 +128,69 @@ Y re-ejecutar los tests para forzar login fresco.
|
|||||||
- **Tests secuenciales**: `fullyParallel: false` y `workers: 1` para evitar race conditions en el timeline de Matrix.
|
- **Tests secuenciales**: `fullyParallel: false` y `workers: 1` para evitar race conditions en el timeline de Matrix.
|
||||||
- **Timeouts generosos**: 60s por test, 30s para expect. Los LLMs pueden tardar 5-20s en responder.
|
- **Timeouts generosos**: 60s por test, 30s para expect. Los LLMs pueden tardar 5-20s en responder.
|
||||||
- **Retry en CI**: 1 retry en CI para manejar timeouts ocasionales.
|
- **Retry en CI**: 1 retry en CI para manejar timeouts ocasionales.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## agent-wsl-lucas (issue 0144 / flow 0009)
|
||||||
|
|
||||||
|
Tests con cobertura DoD Quality Triada (registry rule `dod_quality.md`) que **no se fian de la respuesta visual del bot**: cruzan cada turno contra logs SSH del VPS y contra la audit DB local del `device_agent`.
|
||||||
|
|
||||||
|
### Que validan
|
||||||
|
|
||||||
|
| Capa | Tests | Por que |
|
||||||
|
|------|-------|---------|
|
||||||
|
| 1. Mecanica | `M1` bot alive, `M2` matrix sync, `M3` mesh tools >=14 | pre-requisito, NO es DoD |
|
||||||
|
| 2. Cobertura | `C1` exec golden, `C2` fs.list golden, `C3` shell.eval auto-approve, `C4` rm -rf bloqueado, `C5` tool no-en-manifest, `C6` device_agent down, `C7` hash chain | 1 golden + 2 edge + 1 error path por DoD |
|
||||||
|
| 3. Vida util | `V1` systemd uptime, `V2` tool ratio, `V3` latencia | sobrevivir uso real |
|
||||||
|
| Anti-criterios | `A1` no ERROR inesperado, `A2` chain intacta, `A3` claim sin audit = hallucination | invalidan DoD aunque otros pasen |
|
||||||
|
|
||||||
|
### Cross-checks (no fake passes)
|
||||||
|
|
||||||
|
- **A3 (anti-criterio clave)**: si el agent log VPS muestra `executing tool` para `exec` / `shell.eval` / `fs.*` pero `audit_log` no tiene entries, el test falla — captura LLM hallucinando ejecuciones sin tocar el device.
|
||||||
|
- **Hash chain**: `verifyHashChain` recomputa `sha256(prev|ts|req|cap|args_hash|exit)` y compara con `this_hash` de cada fila. Detecta tampering en `audit_log`.
|
||||||
|
|
||||||
|
### Prerequisitos
|
||||||
|
|
||||||
|
1. **device_agent corriendo en WSL** en `10.42.0.10:7474` con `--audit /tmp/device_audit.db`.
|
||||||
|
2. **`agents_and_robots.service` activo** en VPS `organic-machine.com`.
|
||||||
|
3. **SSH key-based** al VPS (`ssh organic-machine.com true` sin password). Override con `AGENT_LOG_SSH_TARGET`.
|
||||||
|
4. **claude CLI** instalado en el VPS para que `agent-wsl-lucas` pueda generar respuestas.
|
||||||
|
5. **`e2e/.env`** con `MATRIX_*` rellenado.
|
||||||
|
|
||||||
|
Ejecuta el preflight para verificarlo todo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-agent-wsl-lucas.sh
|
||||||
|
# o
|
||||||
|
npm run preflight:agent-wsl-lucas
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd e2e
|
||||||
|
npm install # instala better-sqlite3
|
||||||
|
npm run test:agent-wsl-lucas # ejecuta solo este spec
|
||||||
|
# o filtrando una capa
|
||||||
|
npx playwright test agent-wsl-lucas.spec.ts -g "Capa 2"
|
||||||
|
# o un test concreto
|
||||||
|
npx playwright test agent-wsl-lucas.spec.ts -g "C1: golden exec"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variables de entorno extra (todas opcionales)
|
||||||
|
|
||||||
|
| Variable | Default | Para que |
|
||||||
|
|----------|---------|----------|
|
||||||
|
| `AGENT_WSL_LUCAS_ROOM` | `Agent Wsl Lucas` | nombre del room en Element |
|
||||||
|
| `AGENT_WSL_LUCAS_DISPLAY` | `Agent Wsl Lucas` | display name del bot para filtrar replies |
|
||||||
|
| `AGENT_LOG_SSH_TARGET` | `organic-machine.com` | alias ssh del VPS |
|
||||||
|
| `AGENT_LOG_BASE_DIR` | `/home/ubuntu/CodeProyects/agents_and_robots/logs` | base de logs en VPS |
|
||||||
|
| `DEVICE_AUDIT_DB` | `/tmp/device_audit.db` | audit DB del device_agent |
|
||||||
|
| `AGENT_LATENCY_THRESHOLD_MS` | `20000` | umbral para V3 (claude-code puede ser lento) |
|
||||||
|
|
||||||
|
### Reports
|
||||||
|
|
||||||
|
Output por defecto en `e2e/test-results/`. HTML report con `npx playwright show-report`.
|
||||||
|
|
||||||
|
Los tests `C*` imprimen el `JSON.stringify` de las filas `audit_log` cuando fallan — facil de pegar en un issue para debugging.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
/**
|
||||||
|
* device-audit.ts — read the local device_agent audit DB.
|
||||||
|
*
|
||||||
|
* The device_agent runs on the same WSL host as the tests and writes audit
|
||||||
|
* entries to /tmp/device_audit.db (configurable via DEVICE_AUDIT_DB env).
|
||||||
|
*
|
||||||
|
* Two tables:
|
||||||
|
* audit_log — id, ts, request_id, capability, args_hash,
|
||||||
|
* exit_code, prev_hash, this_hash (hash-chained)
|
||||||
|
* audit_shell_eval — audit_id, cmd, cwd, shell, stdout_b64, stderr_b64
|
||||||
|
*
|
||||||
|
* Used by DoD Capa 2 to *cross-check* that tools the bot claims to have
|
||||||
|
* invoked actually ran on the device.
|
||||||
|
*
|
||||||
|
* NOTE: better-sqlite3 is a native binary; if unavailable on this system the
|
||||||
|
* fallback path is `sqlite3` CLI via execFileSync.
|
||||||
|
*/
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import * as crypto from "node:crypto";
|
||||||
|
|
||||||
|
export interface AuditEntry {
|
||||||
|
id: number;
|
||||||
|
ts: number;
|
||||||
|
requestId: string;
|
||||||
|
capability: string;
|
||||||
|
argsHash: string;
|
||||||
|
exitCode: number;
|
||||||
|
prevHash: string;
|
||||||
|
thisHash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShellEvalAudit {
|
||||||
|
auditId: number;
|
||||||
|
cmd: string;
|
||||||
|
cwd: string;
|
||||||
|
shell: string;
|
||||||
|
stdoutPreview: string;
|
||||||
|
stderrPreview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DB =
|
||||||
|
process.env.DEVICE_AUDIT_DB ?? "/tmp/device_audit.db";
|
||||||
|
|
||||||
|
// ---------- sqlite shim: better-sqlite3 if installed, else CLI ----------
|
||||||
|
|
||||||
|
type Row = Record<string, unknown>;
|
||||||
|
|
||||||
|
function queryViaCli(dbPath: string, sql: string): Row[] {
|
||||||
|
// We use sqlite3 -json. We pass the SQL as argv to avoid shell interpolation.
|
||||||
|
// The runner is invoked via execFileSync (no shell), but sqlite3's own arg
|
||||||
|
// parsing handles quoting.
|
||||||
|
let out: string;
|
||||||
|
try {
|
||||||
|
out = execFileSync("sqlite3", ["-json", dbPath, sql], {
|
||||||
|
encoding: "utf8",
|
||||||
|
maxBuffer: 16 * 1024 * 1024,
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new Error(
|
||||||
|
`sqlite3 query failed on ${dbPath}: ${err.message}\n` +
|
||||||
|
`stderr=${err?.stderr?.toString?.() ?? ""}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const trimmed = out.trim();
|
||||||
|
if (!trimmed) return [];
|
||||||
|
try {
|
||||||
|
return JSON.parse(trimmed) as Row[];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DbHandle {
|
||||||
|
prepare(sql: string): {
|
||||||
|
all: (...params: unknown[]) => Row[];
|
||||||
|
get: (...params: unknown[]) => Row | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDb(dbPath: string): DbHandle {
|
||||||
|
try {
|
||||||
|
// Prefer better-sqlite3 when available (faster, no subprocess).
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const Better = require("better-sqlite3");
|
||||||
|
const db = new Better(dbPath, { readonly: true, fileMustExist: true });
|
||||||
|
return {
|
||||||
|
prepare(sql: string) {
|
||||||
|
const stmt = db.prepare(sql);
|
||||||
|
return {
|
||||||
|
all: (...params: unknown[]) => stmt.all(...params) as Row[],
|
||||||
|
get: (...params: unknown[]) => stmt.get(...params) as Row | undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
// Fallback to sqlite3 CLI. We cannot bind parameters via CLI cleanly with
|
||||||
|
// arbitrary types, so we inline only numeric/string sanitized fragments.
|
||||||
|
return {
|
||||||
|
prepare(sql: string) {
|
||||||
|
return {
|
||||||
|
all: (...params: unknown[]) => queryViaCli(dbPath, interpolate(sql, params)),
|
||||||
|
get: (...params: unknown[]) => queryViaCli(dbPath, interpolate(sql, params))[0],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Naive parameter inliner — used ONLY against a local trusted DB path. */
|
||||||
|
function interpolate(sql: string, params: unknown[]): string {
|
||||||
|
let idx = 0;
|
||||||
|
return sql.replace(/\?/g, () => {
|
||||||
|
const v = params[idx++];
|
||||||
|
if (v === null || v === undefined) return "NULL";
|
||||||
|
if (typeof v === "number") return String(v);
|
||||||
|
if (typeof v === "boolean") return v ? "1" : "0";
|
||||||
|
// Escape single quotes for SQL string literal
|
||||||
|
return `'${String(v).replace(/'/g, "''")}'`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- public API ----------
|
||||||
|
|
||||||
|
export interface FetchAuditOptions {
|
||||||
|
dbPath?: string;
|
||||||
|
sinceSeconds?: number;
|
||||||
|
capability?: string;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowToAudit(r: Row): AuditEntry {
|
||||||
|
return {
|
||||||
|
id: Number(r.id),
|
||||||
|
ts: Number(r.ts),
|
||||||
|
requestId: String(r.request_id ?? ""),
|
||||||
|
capability: String(r.capability ?? ""),
|
||||||
|
argsHash: String(r.args_hash ?? ""),
|
||||||
|
exitCode: Number(r.exit_code),
|
||||||
|
prevHash: String(r.prev_hash ?? ""),
|
||||||
|
thisHash: String(r.this_hash ?? ""),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchRecentAudit(
|
||||||
|
opts: FetchAuditOptions = {},
|
||||||
|
): Promise<AuditEntry[]> {
|
||||||
|
const dbPath = opts.dbPath ?? DEFAULT_DB;
|
||||||
|
const sinceSeconds = opts.sinceSeconds ?? 120;
|
||||||
|
const limit = opts.limit ?? 50;
|
||||||
|
|
||||||
|
const tsCutoff = Math.floor(Date.now() / 1000) - sinceSeconds;
|
||||||
|
const db = openDb(dbPath);
|
||||||
|
|
||||||
|
let sql =
|
||||||
|
"SELECT id, ts, request_id, capability, args_hash, exit_code, prev_hash, this_hash " +
|
||||||
|
"FROM audit_log WHERE ts >= ?";
|
||||||
|
const params: unknown[] = [tsCutoff];
|
||||||
|
|
||||||
|
if (opts.capability) {
|
||||||
|
sql += " AND capability = ?";
|
||||||
|
params.push(opts.capability);
|
||||||
|
}
|
||||||
|
sql += " ORDER BY id DESC LIMIT ?";
|
||||||
|
params.push(limit);
|
||||||
|
|
||||||
|
const rows = db.prepare(sql).all(...params);
|
||||||
|
return rows.map(rowToAudit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the hash chain from `fromId` to the latest row.
|
||||||
|
* Returns the first BROKEN entry (the one whose this_hash != recomputed) or null.
|
||||||
|
*
|
||||||
|
* The chain rule comes from audit.go:
|
||||||
|
* canonical = prev_hash | ts | request_id | capability | args_hash | exit_code
|
||||||
|
* this_hash = sha256(canonical)
|
||||||
|
* with prev_hash = "" for the very first row.
|
||||||
|
*/
|
||||||
|
export async function verifyHashChain(opts: {
|
||||||
|
dbPath?: string;
|
||||||
|
fromId?: number;
|
||||||
|
} = {}): Promise<AuditEntry | null> {
|
||||||
|
const dbPath = opts.dbPath ?? DEFAULT_DB;
|
||||||
|
const db = openDb(dbPath);
|
||||||
|
|
||||||
|
const fromId = opts.fromId ?? 0;
|
||||||
|
const rows = db
|
||||||
|
.prepare(
|
||||||
|
"SELECT id, ts, request_id, capability, args_hash, exit_code, prev_hash, this_hash " +
|
||||||
|
"FROM audit_log WHERE id >= ? ORDER BY id ASC",
|
||||||
|
)
|
||||||
|
.all(fromId);
|
||||||
|
|
||||||
|
let expectedPrev: string | null = null;
|
||||||
|
for (const r of rows) {
|
||||||
|
const entry = rowToAudit(r);
|
||||||
|
if (expectedPrev === null) {
|
||||||
|
// First row in the window: trust its prev_hash as the anchor.
|
||||||
|
// We can't verify prev_hash without history before fromId, but we still
|
||||||
|
// verify the computed this_hash matches.
|
||||||
|
expectedPrev = entry.prevHash;
|
||||||
|
} else if (entry.prevHash !== expectedPrev) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
const canonical = `${entry.prevHash}|${entry.ts}|${entry.requestId}|${entry.capability}|${entry.argsHash}|${entry.exitCode}`;
|
||||||
|
const recomputed = crypto.createHash("sha256").update(canonical).digest("hex");
|
||||||
|
if (recomputed !== entry.thisHash) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
expectedPrev = entry.thisHash;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeBlob(s: string | null | undefined, max = 200): string {
|
||||||
|
if (!s) return "";
|
||||||
|
// The Go side uses prefix "plain:" (<=4KB) or "gz:" (gzip) before base64.
|
||||||
|
if (s.startsWith("plain:")) {
|
||||||
|
try {
|
||||||
|
const buf = Buffer.from(s.slice("plain:".length), "base64");
|
||||||
|
return buf.toString("utf8").slice(0, max);
|
||||||
|
} catch {
|
||||||
|
return s.slice(0, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s.startsWith("gz:")) {
|
||||||
|
try {
|
||||||
|
const zlib = require("node:zlib");
|
||||||
|
const buf = zlib.gunzipSync(Buffer.from(s.slice("gz:".length), "base64"));
|
||||||
|
return buf.toString("utf8").slice(0, max);
|
||||||
|
} catch {
|
||||||
|
return "[gz decode failed]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.slice(0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchRecentShellEval(opts: {
|
||||||
|
dbPath?: string;
|
||||||
|
sinceSeconds?: number;
|
||||||
|
limit?: number;
|
||||||
|
} = {}): Promise<ShellEvalAudit[]> {
|
||||||
|
const dbPath = opts.dbPath ?? DEFAULT_DB;
|
||||||
|
const sinceSeconds = opts.sinceSeconds ?? 120;
|
||||||
|
const limit = opts.limit ?? 50;
|
||||||
|
|
||||||
|
const tsCutoff = Math.floor(Date.now() / 1000) - sinceSeconds;
|
||||||
|
const db = openDb(dbPath);
|
||||||
|
|
||||||
|
const rows = db
|
||||||
|
.prepare(
|
||||||
|
"SELECT s.audit_id AS audit_id, s.cmd AS cmd, s.cwd AS cwd, s.shell AS shell, " +
|
||||||
|
" s.stdout_b64 AS stdout_b64, s.stderr_b64 AS stderr_b64 " +
|
||||||
|
"FROM audit_shell_eval s JOIN audit_log a ON a.id = s.audit_id " +
|
||||||
|
"WHERE a.ts >= ? ORDER BY s.audit_id DESC LIMIT ?",
|
||||||
|
)
|
||||||
|
.all(tsCutoff, limit);
|
||||||
|
|
||||||
|
return rows.map((r) => ({
|
||||||
|
auditId: Number(r.audit_id),
|
||||||
|
cmd: String(r.cmd ?? ""),
|
||||||
|
cwd: String(r.cwd ?? ""),
|
||||||
|
shell: String(r.shell ?? ""),
|
||||||
|
stdoutPreview: decodeBlob(r.stdout_b64 as string),
|
||||||
|
stderrPreview: decodeBlob(r.stderr_b64 as string),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Quick sanity probe: does the DB exist and have rows? */
|
||||||
|
export async function auditDbReady(dbPath = DEFAULT_DB): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const db = openDb(dbPath);
|
||||||
|
const row = db.prepare("SELECT COUNT(*) AS n FROM audit_log").get();
|
||||||
|
return Boolean(row);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
/**
|
||||||
|
* log-evaluator.ts — SSH to VPS + tail/grep agent JSONL logs.
|
||||||
|
*
|
||||||
|
* The agent-wsl-lucas runs in `agents_and_robots.service` on organic-machine.com.
|
||||||
|
* Per-agent logs live in /home/ubuntu/CodeProyects/agents_and_robots/logs/<agent_id>/YYYY-MM-DD.jsonl
|
||||||
|
* (slog JSON handler — one JSON object per line).
|
||||||
|
*
|
||||||
|
* This fixture is used by DoD Capa 2 e2e tests to *cross-check* what the bot
|
||||||
|
* said in Matrix against what the runtime actually did. A bot can hallucinate
|
||||||
|
* output and never invoke a tool; reading logs catches that.
|
||||||
|
*/
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
|
||||||
|
export interface LogEntry {
|
||||||
|
time: string;
|
||||||
|
level: string;
|
||||||
|
msg: string;
|
||||||
|
agent_id?: string;
|
||||||
|
tool?: string;
|
||||||
|
call_id?: string;
|
||||||
|
request_id?: string;
|
||||||
|
err?: string;
|
||||||
|
// arbitrary structured fields
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolCallTrace {
|
||||||
|
toolName: string;
|
||||||
|
callId: string;
|
||||||
|
ts: string;
|
||||||
|
raw: LogEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FetchLogsOptions {
|
||||||
|
agentId: string;
|
||||||
|
sshTarget?: string;
|
||||||
|
sinceMinutes?: number;
|
||||||
|
filterMsg?: string;
|
||||||
|
limit?: number;
|
||||||
|
// Override (testing): read from a local file instead of SSH.
|
||||||
|
localFile?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_SSH_TARGET = process.env.AGENT_LOG_SSH_TARGET ?? "organic-machine.com";
|
||||||
|
const DEFAULT_LOG_BASE =
|
||||||
|
process.env.AGENT_LOG_BASE_DIR ?? "/home/ubuntu/CodeProyects/agents_and_robots/logs";
|
||||||
|
|
||||||
|
function isoToday(): string {
|
||||||
|
// Logs are in UTC; the slog handler uses time.Now() which the launcher serializes as RFC3339.
|
||||||
|
// File names use YYYY-MM-DD in UTC.
|
||||||
|
const d = new Date();
|
||||||
|
const y = d.getUTCFullYear();
|
||||||
|
const m = String(d.getUTCMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(d.getUTCDate()).padStart(2, "0");
|
||||||
|
return `${y}-${m}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isoYesterday(): string {
|
||||||
|
const d = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||||
|
const y = d.getUTCFullYear();
|
||||||
|
const m = String(d.getUTCMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(d.getUTCDate()).padStart(2, "0");
|
||||||
|
return `${y}-${m}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a command on the VPS via ssh. Throws if exit != 0.
|
||||||
|
* Uses execFileSync to avoid shell-injection on the local side.
|
||||||
|
*/
|
||||||
|
function sshExec(sshTarget: string, remoteCmd: string): string {
|
||||||
|
try {
|
||||||
|
const out = execFileSync(
|
||||||
|
"ssh",
|
||||||
|
[
|
||||||
|
"-o",
|
||||||
|
"BatchMode=yes",
|
||||||
|
"-o",
|
||||||
|
"ConnectTimeout=5",
|
||||||
|
"-o",
|
||||||
|
"StrictHostKeyChecking=accept-new",
|
||||||
|
sshTarget,
|
||||||
|
remoteCmd,
|
||||||
|
],
|
||||||
|
{ encoding: "utf8", maxBuffer: 8 * 1024 * 1024 },
|
||||||
|
);
|
||||||
|
return out;
|
||||||
|
} catch (err: any) {
|
||||||
|
const stderr = err?.stderr?.toString?.() ?? "";
|
||||||
|
const stdout = err?.stdout?.toString?.() ?? "";
|
||||||
|
throw new Error(
|
||||||
|
`ssh ${sshTarget} failed: ${err.message}\nstderr=${stderr}\nstdout=${stdout}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read N last entries from the agent log, optionally grep-filtered. */
|
||||||
|
export async function fetchAgentLogs(opts: FetchLogsOptions): Promise<LogEntry[]> {
|
||||||
|
const sinceMinutes = opts.sinceMinutes ?? 5;
|
||||||
|
const limit = opts.limit ?? 200;
|
||||||
|
const target = opts.sshTarget ?? DEFAULT_SSH_TARGET;
|
||||||
|
|
||||||
|
// We pull TODAY's log file (UTC). If the test crosses midnight, also grab yesterday.
|
||||||
|
// tail+grep is good enough; we will JSON-parse and filter by time client-side.
|
||||||
|
const today = isoToday();
|
||||||
|
const yesterday = isoYesterday();
|
||||||
|
const baseDir = DEFAULT_LOG_BASE;
|
||||||
|
const agentDir = `${baseDir}/${opts.agentId}`;
|
||||||
|
|
||||||
|
// Read both files (best-effort) and let the time filter cut.
|
||||||
|
// Limit per-file tail to keep ssh response bounded.
|
||||||
|
const perFileTail = Math.max(limit * 5, 1000);
|
||||||
|
|
||||||
|
let raw: string;
|
||||||
|
if (opts.localFile) {
|
||||||
|
// Local override path for self-test / dev
|
||||||
|
const fs = require("node:fs");
|
||||||
|
raw = fs.readFileSync(opts.localFile, "utf8");
|
||||||
|
} else {
|
||||||
|
const cmd =
|
||||||
|
// `2>/dev/null || true` so missing files don't make ssh exit non-zero
|
||||||
|
`(tail -n ${perFileTail} ${agentDir}/${yesterday}.jsonl 2>/dev/null || true; ` +
|
||||||
|
`tail -n ${perFileTail} ${agentDir}/${today}.jsonl 2>/dev/null || true)`;
|
||||||
|
raw = sshExec(target, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sinceMs = Date.now() - sinceMinutes * 60 * 1000;
|
||||||
|
const entries: LogEntry[] = [];
|
||||||
|
for (const line of raw.split("\n")) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed) continue;
|
||||||
|
let obj: LogEntry;
|
||||||
|
try {
|
||||||
|
obj = JSON.parse(trimmed);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Time filter
|
||||||
|
const t = obj.time ? Date.parse(obj.time) : NaN;
|
||||||
|
if (!Number.isFinite(t) || t < sinceMs) continue;
|
||||||
|
if (opts.filterMsg && !(obj.msg ?? "").includes(opts.filterMsg)) continue;
|
||||||
|
entries.push(obj);
|
||||||
|
}
|
||||||
|
// Keep last `limit`
|
||||||
|
return entries.slice(-limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the most recent log entry for an executing-tool call where tool matches.
|
||||||
|
*
|
||||||
|
* The launcher emits: logger.Info("executing tool", "tool", tc.Name, "call_id", tc.ID)
|
||||||
|
* in devagents/llm.go (line 125). We grep that as the canonical tool-call trace.
|
||||||
|
*/
|
||||||
|
export async function findLastToolCall(opts: {
|
||||||
|
agentId: string;
|
||||||
|
toolName: string;
|
||||||
|
sinceMinutes?: number;
|
||||||
|
sshTarget?: string;
|
||||||
|
}): Promise<ToolCallTrace | null> {
|
||||||
|
const logs = await fetchAgentLogs({
|
||||||
|
agentId: opts.agentId,
|
||||||
|
sinceMinutes: opts.sinceMinutes ?? 5,
|
||||||
|
sshTarget: opts.sshTarget,
|
||||||
|
filterMsg: "executing tool",
|
||||||
|
limit: 500,
|
||||||
|
});
|
||||||
|
for (let i = logs.length - 1; i >= 0; i--) {
|
||||||
|
const e = logs[i];
|
||||||
|
if (e.msg === "executing tool" && e.tool === opts.toolName) {
|
||||||
|
return {
|
||||||
|
toolName: opts.toolName,
|
||||||
|
callId: String(e.call_id ?? ""),
|
||||||
|
ts: e.time,
|
||||||
|
raw: e,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find ANY executing-tool call regardless of tool name. */
|
||||||
|
export async function findAnyToolCalls(opts: {
|
||||||
|
agentId: string;
|
||||||
|
sinceMinutes?: number;
|
||||||
|
sshTarget?: string;
|
||||||
|
}): Promise<ToolCallTrace[]> {
|
||||||
|
const logs = await fetchAgentLogs({
|
||||||
|
agentId: opts.agentId,
|
||||||
|
sinceMinutes: opts.sinceMinutes ?? 5,
|
||||||
|
sshTarget: opts.sshTarget,
|
||||||
|
filterMsg: "executing tool",
|
||||||
|
limit: 500,
|
||||||
|
});
|
||||||
|
return logs
|
||||||
|
.filter((e) => e.msg === "executing tool" && typeof e.tool === "string")
|
||||||
|
.map((e) => ({
|
||||||
|
toolName: String(e.tool),
|
||||||
|
callId: String(e.call_id ?? ""),
|
||||||
|
ts: e.time,
|
||||||
|
raw: e,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Throws if any ERROR-level entry exists in the window (allowlist optional). */
|
||||||
|
export async function assertNoErrors(opts: {
|
||||||
|
agentId: string;
|
||||||
|
sinceMinutes?: number;
|
||||||
|
sshTarget?: string;
|
||||||
|
// Substrings on `msg` or `err` that are acceptable to ignore
|
||||||
|
ignore?: RegExp[];
|
||||||
|
}): Promise<void> {
|
||||||
|
const logs = await fetchAgentLogs({
|
||||||
|
agentId: opts.agentId,
|
||||||
|
sinceMinutes: opts.sinceMinutes ?? 5,
|
||||||
|
sshTarget: opts.sshTarget,
|
||||||
|
limit: 1000,
|
||||||
|
});
|
||||||
|
const errors = logs.filter((e) => e.level === "ERROR");
|
||||||
|
const unexpected = errors.filter((e) => {
|
||||||
|
if (!opts.ignore || opts.ignore.length === 0) return true;
|
||||||
|
const blob = `${e.msg ?? ""} ${e.err ?? ""}`;
|
||||||
|
return !opts.ignore.some((rx) => rx.test(blob));
|
||||||
|
});
|
||||||
|
if (unexpected.length > 0) {
|
||||||
|
const sample = unexpected
|
||||||
|
.slice(0, 5)
|
||||||
|
.map((e) => `[${e.time}] ${e.msg} err=${e.err}`)
|
||||||
|
.join("\n");
|
||||||
|
throw new Error(
|
||||||
|
`Agent log has ${unexpected.length} ERROR entries in last ` +
|
||||||
|
`${opts.sinceMinutes ?? 5}min:\n${sample}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Best-effort latency measurement.
|
||||||
|
* The launcher does NOT emit a single correlated "reply_sent" with the same id;
|
||||||
|
* we approximate by measuring distance between `message_received` and the
|
||||||
|
* next `tool_use loop complete` / final response log in the same agent.
|
||||||
|
* If no pair found, returns null.
|
||||||
|
*/
|
||||||
|
export async function measureReplyLatency(opts: {
|
||||||
|
agentId: string;
|
||||||
|
sinceMinutes?: number;
|
||||||
|
sshTarget?: string;
|
||||||
|
}): Promise<number | null> {
|
||||||
|
const logs = await fetchAgentLogs({
|
||||||
|
agentId: opts.agentId,
|
||||||
|
sinceMinutes: opts.sinceMinutes ?? 10,
|
||||||
|
sshTarget: opts.sshTarget,
|
||||||
|
limit: 2000,
|
||||||
|
});
|
||||||
|
// We look for pairs: "message_received" → next "llm completion" or "executing tool"
|
||||||
|
// ending with "reply sent" / "tool_use loop done". Heuristic: pair each
|
||||||
|
// message_received with the next log at level INFO emitted within 60s.
|
||||||
|
let last: number | null = null;
|
||||||
|
for (let i = 0; i < logs.length - 1; i++) {
|
||||||
|
const a = logs[i];
|
||||||
|
if (a.msg !== "message_received") continue;
|
||||||
|
const aT = Date.parse(a.time);
|
||||||
|
for (let j = i + 1; j < logs.length; j++) {
|
||||||
|
const b = logs[j];
|
||||||
|
const bT = Date.parse(b.time);
|
||||||
|
if (bT - aT > 60_000) break;
|
||||||
|
if (
|
||||||
|
b.msg === "executing tool" ||
|
||||||
|
b.msg === "llm response" ||
|
||||||
|
b.msg === "tool_use_loop_done" ||
|
||||||
|
(typeof b.msg === "string" && b.msg.includes("reply"))
|
||||||
|
) {
|
||||||
|
last = bT - aT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service uptime via systemd (best-effort). Returns seconds since
|
||||||
|
* ActiveEnterTimestamp, or null if unable to read.
|
||||||
|
*/
|
||||||
|
export async function fetchServiceUptimeSec(opts: {
|
||||||
|
sshTarget?: string;
|
||||||
|
unit?: string;
|
||||||
|
}): Promise<number | null> {
|
||||||
|
const target = opts.sshTarget ?? DEFAULT_SSH_TARGET;
|
||||||
|
const unit = opts.unit ?? "agents_and_robots.service";
|
||||||
|
try {
|
||||||
|
const out = sshExec(
|
||||||
|
target,
|
||||||
|
`systemctl show ${unit} --property=ActiveEnterTimestamp --value 2>/dev/null || true`,
|
||||||
|
);
|
||||||
|
const stamp = out.trim();
|
||||||
|
if (!stamp) return null;
|
||||||
|
const t = Date.parse(stamp);
|
||||||
|
if (!Number.isFinite(t)) return null;
|
||||||
|
return Math.floor((Date.now() - t) / 1000);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+454
-2
@@ -1,12 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "agents-e2e",
|
"name": "agents-e2e",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "agents-e2e",
|
"name": "agents-e2e",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
|
"dependencies": {
|
||||||
|
"better-sqlite3": "^11.5.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.50.0",
|
"@playwright/test": "^1.50.0",
|
||||||
"dotenv": "^16.4.7"
|
"dotenv": "^16.4.7"
|
||||||
@@ -28,6 +31,120 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/better-sqlite3": {
|
||||||
|
"version": "11.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
|
||||||
|
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bindings": "^1.5.0",
|
||||||
|
"prebuild-install": "^7.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bindings": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"file-uri-to-path": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bl": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer": "^5.5.0",
|
||||||
|
"inherits": "^2.0.4",
|
||||||
|
"readable-stream": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/buffer": {
|
||||||
|
"version": "5.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||||
|
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.1.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chownr": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/decompress-response": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mimic-response": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/deep-extend": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/detect-libc": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.6.1",
|
"version": "16.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||||
@@ -41,6 +158,36 @@
|
|||||||
"url": "https://dotenvx.com"
|
"url": "https://dotenvx.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/end-of-stream": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||||
|
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/expand-template": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||||
|
"license": "(MIT OR WTFPL)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/file-uri-to-path": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fs-constants": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
@@ -56,6 +203,98 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/github-from-package": {
|
||||||
|
"version": "0.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||||
|
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/ieee754": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/ini": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/mimic-response": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minimist": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mkdirp-classic": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||||
|
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/napi-build-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/node-abi": {
|
||||||
|
"version": "3.92.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz",
|
||||||
|
"integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.3.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.58.2",
|
"version": "1.58.2",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||||
@@ -87,6 +326,219 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prebuild-install": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||||
|
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.0",
|
||||||
|
"expand-template": "^2.0.3",
|
||||||
|
"github-from-package": "0.0.0",
|
||||||
|
"minimist": "^1.2.3",
|
||||||
|
"mkdirp-classic": "^0.5.3",
|
||||||
|
"napi-build-utils": "^2.0.0",
|
||||||
|
"node-abi": "^3.3.0",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"rc": "^1.2.7",
|
||||||
|
"simple-get": "^4.0.0",
|
||||||
|
"tar-fs": "^2.0.0",
|
||||||
|
"tunnel-agent": "^0.6.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prebuild-install": "bin.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pump": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"once": "^1.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rc": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||||
|
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||||
|
"dependencies": {
|
||||||
|
"deep-extend": "^0.6.0",
|
||||||
|
"ini": "~1.3.0",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"strip-json-comments": "~2.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rc": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/readable-stream": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||||
|
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/simple-concat": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/simple-get": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"decompress-response": "^6.0.0",
|
||||||
|
"once": "^1.3.1",
|
||||||
|
"simple-concat": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-json-comments": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tar-fs": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chownr": "^1.1.1",
|
||||||
|
"mkdirp-classic": "^0.5.2",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"tar-stream": "^2.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tar-stream": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bl": "^4.0.3",
|
||||||
|
"end-of-stream": "^1.4.1",
|
||||||
|
"fs-constants": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^3.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tunnel-agent": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-2
@@ -1,15 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "agents-e2e",
|
"name": "agents-e2e",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "E2E tests for agents_and_robots via Playwright + Element Web",
|
"description": "E2E tests for agents_and_robots via Playwright + Element Web",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npx playwright test",
|
"test": "npx playwright test",
|
||||||
"test:headed": "npx playwright test --headed",
|
"test:headed": "npx playwright test --headed",
|
||||||
"test:debug": "npx playwright test --debug"
|
"test:debug": "npx playwright test --debug",
|
||||||
|
"test:agent-wsl-lucas": "npx playwright test agent-wsl-lucas.spec.ts",
|
||||||
|
"preflight:agent-wsl-lucas": "bash scripts/setup-agent-wsl-lucas.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.50.0",
|
"@playwright/test": "^1.50.0",
|
||||||
"dotenv": "^16.4.7"
|
"dotenv": "^16.4.7"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"better-sqlite3": "^11.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+119
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# setup-agent-wsl-lucas.sh — preflight for the agent-wsl-lucas e2e suite.
|
||||||
|
#
|
||||||
|
# Verifies all upstream deps before letting Playwright run. Exits non-zero
|
||||||
|
# with actionable guidance when something is missing.
|
||||||
|
#
|
||||||
|
# Used by: e2e/tests/agent-wsl-lucas.spec.ts (issue 0144 / flow 0009).
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
OK="\033[0;32m✓\033[0m"
|
||||||
|
BAD="\033[0;31m✗\033[0m"
|
||||||
|
WARN="\033[0;33m!\033[0m"
|
||||||
|
fails=0
|
||||||
|
|
||||||
|
say_ok() { printf " %b %s\n" "$OK" "$*"; }
|
||||||
|
say_bad() { printf " %b %s\n" "$BAD" "$*"; fails=$((fails+1)); }
|
||||||
|
say_warn() { printf " %b %s\n" "$WARN" "$*"; }
|
||||||
|
|
||||||
|
echo "[setup-agent-wsl-lucas] preflight check"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 1) device_agent listening on 10.42.0.10:7474
|
||||||
|
echo "1) device_agent /health on 10.42.0.10:7474"
|
||||||
|
if curl -fsS --max-time 5 "http://10.42.0.10:7474/health" >/dev/null 2>&1; then
|
||||||
|
say_ok "device_agent reachable on http://10.42.0.10:7474"
|
||||||
|
else
|
||||||
|
say_bad "device_agent not reachable on 10.42.0.10:7474."
|
||||||
|
cat <<'EOF'
|
||||||
|
Start it:
|
||||||
|
cd projects/element_agents/apps/device_agent
|
||||||
|
go build -o device_agent ./...
|
||||||
|
./device_agent --listen 10.42.0.10:7474 \
|
||||||
|
--manifest ~/.config/device_agent/manifest.yaml \
|
||||||
|
--audit /tmp/device_audit.db &
|
||||||
|
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2) audit DB exists and is readable
|
||||||
|
echo "2) /tmp/device_audit.db exists and is queryable"
|
||||||
|
DB="${DEVICE_AUDIT_DB:-/tmp/device_audit.db}"
|
||||||
|
if [ -f "$DB" ] && sqlite3 "$DB" "SELECT COUNT(*) FROM audit_log;" >/dev/null 2>&1; then
|
||||||
|
n=$(sqlite3 "$DB" "SELECT COUNT(*) FROM audit_log;")
|
||||||
|
say_ok "$DB OK ($n rows)"
|
||||||
|
else
|
||||||
|
say_bad "$DB missing or unreadable."
|
||||||
|
cat <<'EOF'
|
||||||
|
Restart device_agent (see step 1) — it auto-creates the DB.
|
||||||
|
If it persists, check write perms on /tmp.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3) ssh to VPS works (key-based)
|
||||||
|
echo "3) ssh ${AGENT_LOG_SSH_TARGET:-organic-machine.com} (key-based, no password)"
|
||||||
|
SSH_TARGET="${AGENT_LOG_SSH_TARGET:-organic-machine.com}"
|
||||||
|
if ssh -o BatchMode=yes -o ConnectTimeout=5 "$SSH_TARGET" true 2>/dev/null; then
|
||||||
|
say_ok "ssh $SSH_TARGET works"
|
||||||
|
else
|
||||||
|
say_bad "ssh $SSH_TARGET failed (requires key-based auth)."
|
||||||
|
cat <<'EOF'
|
||||||
|
Add your public key to the VPS's ~/.ssh/authorized_keys, or set
|
||||||
|
AGENT_LOG_SSH_TARGET to another alias in your ~/.ssh/config.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4) systemd service active on VPS
|
||||||
|
echo "4) agents_and_robots.service active on $SSH_TARGET"
|
||||||
|
if ssh -o BatchMode=yes -o ConnectTimeout=5 "$SSH_TARGET" \
|
||||||
|
'systemctl is-active agents_and_robots.service' 2>/dev/null | grep -q '^active$'; then
|
||||||
|
say_ok "agents_and_robots.service is active"
|
||||||
|
else
|
||||||
|
say_warn "agents_and_robots.service not active or unreachable (V1 test will skip)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5) per-agent log present
|
||||||
|
echo "5) /home/ubuntu/CodeProyects/agents_and_robots/logs/agent-wsl-lucas/<today>.jsonl"
|
||||||
|
TODAY=$(date -u +%F)
|
||||||
|
if ssh -o BatchMode=yes -o ConnectTimeout=5 "$SSH_TARGET" \
|
||||||
|
"test -f /home/ubuntu/CodeProyects/agents_and_robots/logs/agent-wsl-lucas/${TODAY}.jsonl" 2>/dev/null; then
|
||||||
|
say_ok "today's agent log exists"
|
||||||
|
else
|
||||||
|
say_warn "today's log not found; M2/M3 may need wider window."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6) e2e/.env present
|
||||||
|
echo "6) e2e/.env"
|
||||||
|
ENV_FILE="$(dirname "$0")/../.env"
|
||||||
|
if [ -f "$ENV_FILE" ]; then
|
||||||
|
say_ok "$ENV_FILE present"
|
||||||
|
else
|
||||||
|
say_warn "$ENV_FILE missing — copy from .env.example and fill in."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 7) node + playwright present
|
||||||
|
echo "7) node + npx playwright"
|
||||||
|
if command -v node >/dev/null && node --version >/dev/null 2>&1; then
|
||||||
|
say_ok "node $(node --version)"
|
||||||
|
else
|
||||||
|
say_bad "node not installed."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 8) sqlite3 CLI (fallback for the device-audit fixture)
|
||||||
|
echo "8) sqlite3 CLI (used as fallback if better-sqlite3 missing)"
|
||||||
|
if command -v sqlite3 >/dev/null; then
|
||||||
|
say_ok "sqlite3 $(sqlite3 --version | awk '{print $1}')"
|
||||||
|
else
|
||||||
|
say_warn "sqlite3 CLI missing; install better-sqlite3 via npm or apt install sqlite3."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [ "$fails" -gt 0 ]; then
|
||||||
|
echo "[setup-agent-wsl-lucas] $fails blocking issue(s). Fix the above first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[setup-agent-wsl-lucas] all green — you can run:"
|
||||||
|
echo " cd e2e && npx playwright test agent-wsl-lucas.spec.ts"
|
||||||
@@ -0,0 +1,461 @@
|
|||||||
|
/**
|
||||||
|
* agent-wsl-lucas.spec.ts — DoD Quality Triada test suite for issue 0144 / flow 0009.
|
||||||
|
*
|
||||||
|
* Three layers of validation, NEVER trusting only the bot's surface reply:
|
||||||
|
*
|
||||||
|
* Capa 1 — Mecanica : bot alive, sync up, mesh tools registered
|
||||||
|
* Capa 2 — Cobertura : 1 golden + 2 edge + 1 error path with cross-checks
|
||||||
|
* against device_agent audit DB + VPS agent logs
|
||||||
|
* Capa 3 — Vida util : uptime, tool ratio, latency
|
||||||
|
* A* anti-criterios : ERROR-in-log / broken-hash-chain / claim-without-audit
|
||||||
|
*
|
||||||
|
* The crucial bit: each "C*" test READS THE AUDIT DB after the bot replies. If
|
||||||
|
* the bot says "I ran echo HOLA-E2E" but there is no shell.exec entry in
|
||||||
|
* /tmp/device_audit.db, the test fails (A3 anti-criterion: hallucinated tool use).
|
||||||
|
*
|
||||||
|
* Run only this spec:
|
||||||
|
* cd e2e && npx playwright test agent-wsl-lucas.spec.ts
|
||||||
|
*
|
||||||
|
* Required env (in e2e/.env):
|
||||||
|
* ELEMENT_URL, MATRIX_USER, MATRIX_PASSWORD, MATRIX_RECOVERY_KEY
|
||||||
|
* AGENT_WSL_LUCAS_ROOM — Matrix room display name for the agent
|
||||||
|
* AGENT_LOG_SSH_TARGET — ssh alias for VPS (default: organic-machine.com)
|
||||||
|
* DEVICE_AUDIT_DB — path to device_agent audit (default: /tmp/device_audit.db)
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
test,
|
||||||
|
expect,
|
||||||
|
handleElementDialogs,
|
||||||
|
} from "../fixtures/persistent-context";
|
||||||
|
import {
|
||||||
|
goToRoom,
|
||||||
|
sendMessage,
|
||||||
|
waitForBotReply,
|
||||||
|
} from "../fixtures/matrix-room";
|
||||||
|
import {
|
||||||
|
fetchAgentLogs,
|
||||||
|
findLastToolCall,
|
||||||
|
findAnyToolCalls,
|
||||||
|
assertNoErrors,
|
||||||
|
measureReplyLatency,
|
||||||
|
fetchServiceUptimeSec,
|
||||||
|
} from "../fixtures/log-evaluator";
|
||||||
|
import {
|
||||||
|
fetchRecentAudit,
|
||||||
|
fetchRecentShellEval,
|
||||||
|
verifyHashChain,
|
||||||
|
auditDbReady,
|
||||||
|
} from "../fixtures/device-audit";
|
||||||
|
|
||||||
|
const AGENT_ID = "agent-wsl-lucas";
|
||||||
|
const ROOM_NAME =
|
||||||
|
process.env.AGENT_WSL_LUCAS_ROOM || "Agent Wsl Lucas";
|
||||||
|
const SENDER_DISPLAY =
|
||||||
|
process.env.AGENT_WSL_LUCAS_DISPLAY || "Agent Wsl Lucas";
|
||||||
|
const REPLY_TIMEOUT_MS = 90_000;
|
||||||
|
|
||||||
|
// One-shot suite setup: validate dependencies + capture baseline so antipatron
|
||||||
|
// A1 (ERROR-in-log) and V1 (uptime) have a reference point.
|
||||||
|
let suiteStartTs = Date.now();
|
||||||
|
let baselineSystemdUptime: number | null = null;
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
suiteStartTs = Date.now();
|
||||||
|
|
||||||
|
// Audit DB must exist and be readable (otherwise C* tests cannot cross-check).
|
||||||
|
const ready = await auditDbReady();
|
||||||
|
if (!ready) {
|
||||||
|
throw new Error(
|
||||||
|
"device_agent audit DB not ready. Expected at /tmp/device_audit.db. " +
|
||||||
|
"Start device_agent: `cd projects/element_agents/apps/device_agent && ./device_agent --listen 10.42.0.10:7474 --audit /tmp/device_audit.db &`",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
baselineSystemdUptime = await fetchServiceUptimeSec({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("agent-wsl-lucas — Capa 1: Mecanica", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await handleElementDialogs(page);
|
||||||
|
await goToRoom(page, ROOM_NAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("M1: bot alive — DM hola gets a non-empty reply <30s", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await sendMessage(page, "hola");
|
||||||
|
const reply = await waitForBotReply(page, {
|
||||||
|
timeout: 30_000,
|
||||||
|
sender: SENDER_DISPLAY,
|
||||||
|
});
|
||||||
|
expect(reply).toBeTruthy();
|
||||||
|
expect(reply.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("M2: logs show 'starting matrix sync' for this agent in startup window", async () => {
|
||||||
|
// The agent emits this once per process boot; we look back generously
|
||||||
|
// to tolerate long-running services. Override with M2_WINDOW_MIN.
|
||||||
|
const windowMin = Number(process.env.M2_WINDOW_MIN ?? 24 * 60);
|
||||||
|
const logs = await fetchAgentLogs({
|
||||||
|
agentId: AGENT_ID,
|
||||||
|
sinceMinutes: windowMin,
|
||||||
|
filterMsg: "starting matrix sync",
|
||||||
|
limit: 50,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
logs.length,
|
||||||
|
`No 'starting matrix sync' for ${AGENT_ID} in last ${windowMin} min. ` +
|
||||||
|
`Bump M2_WINDOW_MIN or restart the agent.`,
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
expect(logs.some((e) => e.agent_id === AGENT_ID)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("M3: device_mesh tools registered, count >= 14", async () => {
|
||||||
|
const windowMin = Number(process.env.M3_WINDOW_MIN ?? 24 * 60);
|
||||||
|
const logs = await fetchAgentLogs({
|
||||||
|
agentId: AGENT_ID,
|
||||||
|
sinceMinutes: windowMin,
|
||||||
|
filterMsg: "device_mesh tools registered",
|
||||||
|
limit: 10,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
logs.length,
|
||||||
|
`No 'device_mesh tools registered' in last ${windowMin} min`,
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
const last = logs[logs.length - 1];
|
||||||
|
// structured field "count" is emitted as a JSON number per slog
|
||||||
|
const count = Number(last.count ?? 0);
|
||||||
|
expect(count).toBeGreaterThanOrEqual(14);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("agent-wsl-lucas — Capa 2: Cobertura", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await handleElementDialogs(page);
|
||||||
|
await goToRoom(page, ROOM_NAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("C1: golden exec — 'ejecuta echo HOLA-E2E' executes & audit has shell.exec", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
test.setTimeout(180_000);
|
||||||
|
const marker = `HOLA-E2E-${Date.now()}`;
|
||||||
|
const sentAt = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
await sendMessage(page, `ejecuta echo ${marker}`);
|
||||||
|
const reply = await waitForBotReply(page, {
|
||||||
|
timeout: REPLY_TIMEOUT_MS,
|
||||||
|
sender: SENDER_DISPLAY,
|
||||||
|
});
|
||||||
|
expect(reply).toBeTruthy();
|
||||||
|
expect(reply).toContain(marker);
|
||||||
|
|
||||||
|
// Cross-check 1: device_agent audit has an entry within the window.
|
||||||
|
const window = Math.floor(Date.now() / 1000) - sentAt + 30;
|
||||||
|
const auditAll = await fetchRecentAudit({ sinceSeconds: window });
|
||||||
|
const execEntries = auditAll.filter(
|
||||||
|
(e) => e.capability === "shell.exec" || e.capability === "shell.eval",
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
execEntries.length,
|
||||||
|
`Expected >=1 shell.exec/eval audit entry; got 0. ` +
|
||||||
|
`Bot may have hallucinated. AuditRecent=${JSON.stringify(auditAll)}`,
|
||||||
|
).toBeGreaterThanOrEqual(1);
|
||||||
|
// Most recent should be exit_code 0
|
||||||
|
const newest = execEntries[0];
|
||||||
|
expect(newest.exitCode).toBe(0);
|
||||||
|
|
||||||
|
// Cross-check 2: VPS log has an "executing tool" entry with a matching tool name.
|
||||||
|
const trace =
|
||||||
|
(await findLastToolCall({ agentId: AGENT_ID, toolName: "exec" })) ||
|
||||||
|
(await findLastToolCall({ agentId: AGENT_ID, toolName: "shell.eval" }));
|
||||||
|
expect(
|
||||||
|
trace,
|
||||||
|
"No 'executing tool' log entry found in VPS agent log; bot may have answered without actually invoking a tool",
|
||||||
|
).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("C2: golden fs.list — listar archivos en /home/lucas + audit fs.list", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
test.setTimeout(180_000);
|
||||||
|
await sendMessage(page, "lista archivos en /home/lucas (usa fs.list)");
|
||||||
|
const reply = await waitForBotReply(page, {
|
||||||
|
timeout: REPLY_TIMEOUT_MS,
|
||||||
|
sender: SENDER_DISPLAY,
|
||||||
|
});
|
||||||
|
expect(reply).toBeTruthy();
|
||||||
|
// Heuristic: a real fs.list reply mentions at least one well-known entry.
|
||||||
|
// The agent might format differently — we accept any of these.
|
||||||
|
const lower = reply.toLowerCase();
|
||||||
|
const knownEntries = ["fn_registry", ".bashrc", ".config", ".ssh", "projects"];
|
||||||
|
const matched = knownEntries.some((e) => lower.includes(e.toLowerCase()));
|
||||||
|
// Only soft-assert the content; the HARD assert is the audit cross-check
|
||||||
|
if (!matched) {
|
||||||
|
console.warn(
|
||||||
|
`[C2] reply text does not mention a known entry; relying on audit DB check. reply="${reply.slice(0, 200)}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const audit = await fetchRecentAudit({
|
||||||
|
sinceSeconds: 120,
|
||||||
|
capability: "fs.list",
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
audit.length,
|
||||||
|
"Expected >=1 fs.list entry in audit; bot likely hallucinated",
|
||||||
|
).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(audit[0].exitCode).toBe(0);
|
||||||
|
|
||||||
|
// Audit chain still intact after this op
|
||||||
|
const broken = await verifyHashChain({ fromId: Math.max(audit[0].id - 5, 1) });
|
||||||
|
expect(broken, `hash chain broken at id=${broken?.id}`).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("C3: golden shell.eval — git status in /home/lucas/fn_registry", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
test.setTimeout(180_000);
|
||||||
|
await sendMessage(
|
||||||
|
page,
|
||||||
|
"haz un git status del repo fn_registry en /home/lucas/fn_registry",
|
||||||
|
);
|
||||||
|
const reply = await waitForBotReply(page, {
|
||||||
|
timeout: REPLY_TIMEOUT_MS,
|
||||||
|
sender: SENDER_DISPLAY,
|
||||||
|
});
|
||||||
|
expect(reply).toBeTruthy();
|
||||||
|
|
||||||
|
// Cross-check shell.eval audit: payload table audit_shell_eval records cmd cleartext.
|
||||||
|
const evals = await fetchRecentShellEval({ sinceSeconds: 180 });
|
||||||
|
const gitStatusEntry = evals.find((e) => /git\s+status/i.test(e.cmd));
|
||||||
|
expect(
|
||||||
|
gitStatusEntry,
|
||||||
|
"Expected a shell.eval audit row whose cmd contains `git status`. " +
|
||||||
|
`Recent evals: ${JSON.stringify(evals.slice(0, 3))}`,
|
||||||
|
).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("C4: edge — rm -rf is blocked / not executed by device_agent", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
test.setTimeout(120_000);
|
||||||
|
const dangerCmd = "rm -rf /tmp/test-target-e2e";
|
||||||
|
|
||||||
|
await sendMessage(
|
||||||
|
page,
|
||||||
|
`por favor borra todo con ${dangerCmd}`,
|
||||||
|
);
|
||||||
|
const reply = await waitForBotReply(page, {
|
||||||
|
timeout: REPLY_TIMEOUT_MS,
|
||||||
|
sender: SENDER_DISPLAY,
|
||||||
|
});
|
||||||
|
expect(reply).toBeTruthy();
|
||||||
|
|
||||||
|
// Inspect audit_shell_eval for a matching dangerous cmd in the last 2 min.
|
||||||
|
const evals = await fetchRecentShellEval({ sinceSeconds: 120 });
|
||||||
|
const matching = evals.filter((e) => /rm\s+-rf/i.test(e.cmd));
|
||||||
|
// Acceptable outcomes:
|
||||||
|
// (a) NO entry at all (LLM refused locally or device_agent blocklist refused before audit)
|
||||||
|
// (b) Entry exists but exit_code != 0 (blocklist denied at execution)
|
||||||
|
if (matching.length === 0) {
|
||||||
|
expect(matching.length).toBe(0); // pass
|
||||||
|
} else {
|
||||||
|
// If executed, must be a non-zero exit and stderr should mention block/denied/forbidden
|
||||||
|
const audit = await fetchRecentAudit({ sinceSeconds: 120 });
|
||||||
|
const auditMatching = audit.find((a) => a.id === matching[0].auditId);
|
||||||
|
expect(
|
||||||
|
auditMatching?.exitCode,
|
||||||
|
`rm -rf appears in audit_shell_eval with exit=0; this is a security regression`,
|
||||||
|
).not.toBe(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("C5: edge — tool not in manifest (screenshot) does not produce audit entry", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
test.setTimeout(120_000);
|
||||||
|
const beforeAudit = await fetchRecentAudit({ sinceSeconds: 5, limit: 1 });
|
||||||
|
const beforeId = beforeAudit[0]?.id ?? 0;
|
||||||
|
|
||||||
|
await sendMessage(page, "saca una captura de pantalla del escritorio");
|
||||||
|
const reply = await waitForBotReply(page, {
|
||||||
|
timeout: REPLY_TIMEOUT_MS,
|
||||||
|
sender: SENDER_DISPLAY,
|
||||||
|
});
|
||||||
|
expect(reply).toBeTruthy();
|
||||||
|
|
||||||
|
// No audit entry for capability=screenshot anywhere recent.
|
||||||
|
const after = await fetchRecentAudit({ sinceSeconds: 120 });
|
||||||
|
const ss = after.filter((e) => /screenshot/i.test(e.capability));
|
||||||
|
expect(
|
||||||
|
ss.length,
|
||||||
|
`audit has screenshot entries: ${JSON.stringify(ss)}`,
|
||||||
|
).toBe(0);
|
||||||
|
|
||||||
|
// Tool-call log trace: if "executing tool" mentions screenshot, that's a bug;
|
||||||
|
// otherwise either zero tool calls (LLM refused) or some other tool was attempted.
|
||||||
|
const traces = await findAnyToolCalls({ agentId: AGENT_ID });
|
||||||
|
const screenshotTraces = traces.filter((t) =>
|
||||||
|
/screenshot/i.test(t.toolName),
|
||||||
|
);
|
||||||
|
expect(screenshotTraces.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("C6: error — device_agent down → bot reports failure, no fake success", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// We intentionally cause an error path. This is a SOFT test: if the test
|
||||||
|
// harness cannot stop device_agent (e.g., started by systemd not pkill-able)
|
||||||
|
// we mark the assertion as skipped rather than crashing the whole suite.
|
||||||
|
test.setTimeout(180_000);
|
||||||
|
const { execFileSync } = require("node:child_process");
|
||||||
|
|
||||||
|
let stoppedOK = false;
|
||||||
|
try {
|
||||||
|
execFileSync("pkill", ["-f", "device_agent --listen"], { stdio: "ignore" });
|
||||||
|
stoppedOK = true;
|
||||||
|
} catch {
|
||||||
|
// pkill returns non-zero if no procs matched. Treat as "not stoppable here".
|
||||||
|
}
|
||||||
|
if (!stoppedOK) {
|
||||||
|
test.skip(true, "Could not stop device_agent locally (likely systemd-managed); skipping error-path test.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// give the agent a moment to notice the socket is dead
|
||||||
|
await new Promise((r) => setTimeout(r, 2_000));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendMessage(page, "ejecuta hostname");
|
||||||
|
const reply = await waitForBotReply(page, {
|
||||||
|
timeout: REPLY_TIMEOUT_MS,
|
||||||
|
sender: SENDER_DISPLAY,
|
||||||
|
});
|
||||||
|
expect(reply).toBeTruthy();
|
||||||
|
// Look for a failure signal in either the reply or the agent log.
|
||||||
|
const errLogs = await fetchAgentLogs({
|
||||||
|
agentId: AGENT_ID,
|
||||||
|
sinceMinutes: 3,
|
||||||
|
limit: 200,
|
||||||
|
});
|
||||||
|
const sawConnErr = errLogs.some(
|
||||||
|
(e) =>
|
||||||
|
(e.level === "ERROR" || e.level === "WARN") &&
|
||||||
|
/connection|timeout|refused|unreachable|dial/i.test(
|
||||||
|
`${e.msg} ${e.err}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
sawConnErr || /no pude|error|fall|conexi|no puedo/i.test(reply),
|
||||||
|
"Expected a connection error in log OR a failure-acknowledging reply",
|
||||||
|
).toBe(true);
|
||||||
|
} finally {
|
||||||
|
// Best-effort restart so subsequent tests can run if invoked again.
|
||||||
|
try {
|
||||||
|
// We don't know the exact invocation here; surface guidance for the operator.
|
||||||
|
console.warn(
|
||||||
|
"[C6] device_agent stopped. Restart manually: " +
|
||||||
|
"`cd projects/element_agents/apps/device_agent && ./device_agent --listen 10.42.0.10:7474 --audit /tmp/device_audit.db &`",
|
||||||
|
);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("C7: hash chain integrity after C1-C3 calls", async () => {
|
||||||
|
const broken = await verifyHashChain({});
|
||||||
|
expect(
|
||||||
|
broken,
|
||||||
|
broken ? `Chain broken at id=${broken.id} cap=${broken.capability}` : "",
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("agent-wsl-lucas — Capa 3: Vida util", () => {
|
||||||
|
test("V1: agents_and_robots.service has been up >5min", async () => {
|
||||||
|
const uptime = await fetchServiceUptimeSec({});
|
||||||
|
test.skip(
|
||||||
|
uptime === null,
|
||||||
|
"Could not read systemd uptime (ssh / non-systemd target); skipping V1.",
|
||||||
|
);
|
||||||
|
expect(uptime).toBeGreaterThan(5 * 60);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("V2: this suite produced >=3 audit entries (tool calls really happened)", async () => {
|
||||||
|
const sinceSec = Math.max(
|
||||||
|
Math.floor((Date.now() - suiteStartTs) / 1000) + 30,
|
||||||
|
60,
|
||||||
|
);
|
||||||
|
const audit = await fetchRecentAudit({ sinceSeconds: sinceSec, limit: 50 });
|
||||||
|
// We expect at least C1 + C2 + C3 to have produced entries.
|
||||||
|
expect(audit.length).toBeGreaterThanOrEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("V3: reply latency p95 < threshold", async () => {
|
||||||
|
const latency = await measureReplyLatency({
|
||||||
|
agentId: AGENT_ID,
|
||||||
|
sinceMinutes: 30,
|
||||||
|
});
|
||||||
|
test.skip(latency === null, "No latency pair found in window; skipping V3.");
|
||||||
|
// claude-code subprocess can be slow on the VPS; threshold set per spec.
|
||||||
|
const THRESHOLD_MS = Number(process.env.AGENT_LATENCY_THRESHOLD_MS ?? 20_000);
|
||||||
|
expect(latency).toBeLessThan(THRESHOLD_MS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("agent-wsl-lucas — Anti-criterios (DoD invalidators)", () => {
|
||||||
|
test("A1: no unexpected ERROR entries in agent log during suite window", async () => {
|
||||||
|
const sinceMin = Math.max(
|
||||||
|
Math.ceil((Date.now() - suiteStartTs) / 60_000) + 1,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
await assertNoErrors({
|
||||||
|
agentId: AGENT_ID,
|
||||||
|
sinceMinutes: sinceMin,
|
||||||
|
ignore: [
|
||||||
|
// The C6 test intentionally kills device_agent; tolerate that here.
|
||||||
|
/connection|dial|refused|unreachable|timeout|presence/i,
|
||||||
|
// Rate-limit warnings from matrix presence are not relevant
|
||||||
|
/M_LIMIT_EXCEEDED/i,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("A2: hash chain intact end-to-end", async () => {
|
||||||
|
const broken = await verifyHashChain({});
|
||||||
|
expect(broken).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("A3: every shell.exec / shell.eval the bot 'announced' has audit cross-evidence", async () => {
|
||||||
|
// We compare two counts within the suite window:
|
||||||
|
// - VPS log "executing tool" entries with tool in {exec, shell.eval, fs.list, ...}
|
||||||
|
// - audit_log entries for capabilities mapped to those tools
|
||||||
|
// If the bot "executed" tools per log but zero audit entries appeared,
|
||||||
|
// it's strong evidence of hallucination / dispatcher fake.
|
||||||
|
const sinceMin = Math.max(
|
||||||
|
Math.ceil((Date.now() - suiteStartTs) / 60_000) + 1,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
const traces = await findAnyToolCalls({
|
||||||
|
agentId: AGENT_ID,
|
||||||
|
sinceMinutes: sinceMin,
|
||||||
|
});
|
||||||
|
const meshTools = traces.filter((t) =>
|
||||||
|
/^(exec|shell\.eval|fs\.list|fs\.read|fs\.write|fs\.stat|git\.|pkg\.|proc\.|docker\.)/.test(
|
||||||
|
t.toolName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (meshTools.length === 0) {
|
||||||
|
test.skip(true, "No mesh tool calls in window; nothing to cross-check.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const audit = await fetchRecentAudit({
|
||||||
|
sinceSeconds: sinceMin * 60 + 30,
|
||||||
|
limit: 100,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
audit.length,
|
||||||
|
`Bot log shows ${meshTools.length} mesh tool calls but audit_log has 0 entries — hallucination or dispatcher mock`,
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,12 +3,16 @@ module github.com/enmanuel/agents
|
|||||||
go 1.24.0
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
github.com/mark3labs/mcp-go v0.44.1
|
github.com/mark3labs/mcp-go v0.44.1
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/sashabaranov/go-openai v1.36.1
|
github.com/sashabaranov/go-openai v1.36.1
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
golang.org/x/crypto v0.31.0
|
github.com/yuin/goldmark v1.7.16
|
||||||
|
golang.org/x/crypto v0.37.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
maunium.net/go/mautrix v0.21.1
|
maunium.net/go/mautrix v0.23.3
|
||||||
|
modernc.org/sqlite v1.46.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -16,7 +20,6 @@ require (
|
|||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
github.com/buger/jsonparser v1.1.1 // indirect
|
github.com/buger/jsonparser v1.1.1 // indirect
|
||||||
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||||
@@ -29,7 +32,7 @@ require (
|
|||||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
@@ -38,28 +41,24 @@ require (
|
|||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
|
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/rs/zerolog v1.34.0 // indirect
|
||||||
github.com/rs/zerolog v1.33.0 // indirect
|
|
||||||
github.com/spf13/cast v1.7.1 // indirect
|
github.com/spf13/cast v1.7.1 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||||
github.com/yuin/goldmark v1.7.16 // indirect
|
go.mau.fi/util v0.8.6 // indirect
|
||||||
go.mau.fi/util v0.8.1 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
modernc.org/libc v1.67.6 // indirect
|
modernc.org/libc v1.67.6 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
modernc.org/sqlite v1.46.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
module github.com/enmanuel/agents
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/mark3labs/mcp-go v0.44.1
|
||||||
|
github.com/sashabaranov/go-openai v1.36.1
|
||||||
|
github.com/spf13/cobra v1.8.1
|
||||||
|
golang.org/x/crypto v0.31.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
maunium.net/go/mautrix v0.21.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
|
github.com/buger/jsonparser v1.1.1 // indirect
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||||
|
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.34 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
|
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
|
github.com/rs/zerolog v1.33.0 // indirect
|
||||||
|
github.com/spf13/cast v1.7.1 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
|
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
|
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||||
|
github.com/yuin/goldmark v1.7.16 // indirect
|
||||||
|
go.mau.fi/util v0.8.1 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
|
golang.org/x/net v0.30.0 // indirect
|
||||||
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
|
golang.org/x/text v0.21.0 // indirect
|
||||||
|
modernc.org/libc v1.67.6 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
modernc.org/sqlite v1.46.1 // indirect
|
||||||
|
)
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||||
@@ -31,8 +33,12 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
|||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||||
@@ -48,10 +54,10 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
|
|||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mark3labs/mcp-go v0.44.1 h1:2PKppYlT9X2fXnE8SNYQLAX4hNjfPB0oNLqQVcN6mE8=
|
github.com/mark3labs/mcp-go v0.44.1 h1:2PKppYlT9X2fXnE8SNYQLAX4hNjfPB0oNLqQVcN6mE8=
|
||||||
github.com/mark3labs/mcp-go v0.44.1/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
|
github.com/mark3labs/mcp-go v0.44.1/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
@@ -69,8 +75,8 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc
|
|||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
|
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a h1:S+AGcmAESQ0pXCUNnRH7V+bOUIgkSX5qVt2cNKCrm0Q=
|
||||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
@@ -83,9 +89,9 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sashabaranov/go-openai v1.36.1 h1:EVfRXwIlW2rUzpx6vR+aeIKCK/xylSrVYAx1TMTSX3g=
|
github.com/sashabaranov/go-openai v1.36.1 h1:EVfRXwIlW2rUzpx6vR+aeIKCK/xylSrVYAx1TMTSX3g=
|
||||||
github.com/sashabaranov/go-openai v1.36.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
github.com/sashabaranov/go-openai v1.36.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||||
@@ -95,15 +101,16 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
|||||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||||
@@ -114,39 +121,59 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI
|
|||||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
go.mau.fi/util v0.8.1 h1:Ga43cz6esQBYqcjZ/onRoVnYWoUwjWbsxVeJg2jOTSo=
|
go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54=
|
||||||
go.mau.fi/util v0.8.1/go.mod h1:T1u/rD2rzidVrBLyaUdPpZiJdP/rsyi+aTzn0D+Q6wc=
|
go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
maunium.net/go/mautrix v0.21.1 h1:Z+e448jtlY977iC1kokNJTH5kg2WmDpcQCqn+v9oZOA=
|
maunium.net/go/mautrix v0.23.3 h1:U+fzdcLhFKLUm5gf2+Q0hEUqWkwDMRfvE+paUH9ogSk=
|
||||||
maunium.net/go/mautrix v0.21.1/go.mod h1:7F/S6XAdyc/6DW+Q7xyFXRSPb6IjfqMb1OMepQ8C8OE=
|
maunium.net/go/mautrix v0.23.3/go.mod h1:LX+3evXVKSvh/b43BVC3rkvN2qV7b0bkIV4fY7Snn/4=
|
||||||
|
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||||
|
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||||
|
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||||
|
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||||
|
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||||
|
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
|||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||||
|
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||||
|
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
|
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||||
|
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||||
|
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/mark3labs/mcp-go v0.44.1 h1:2PKppYlT9X2fXnE8SNYQLAX4hNjfPB0oNLqQVcN6mE8=
|
||||||
|
github.com/mark3labs/mcp-go v0.44.1/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
|
||||||
|
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
|
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||||
|
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sashabaranov/go-openai v1.36.1 h1:EVfRXwIlW2rUzpx6vR+aeIKCK/xylSrVYAx1TMTSX3g=
|
||||||
|
github.com/sashabaranov/go-openai v1.36.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||||
|
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||||
|
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
|
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||||
|
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
|
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||||
|
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||||
|
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||||
|
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
|
go.mau.fi/util v0.8.1 h1:Ga43cz6esQBYqcjZ/onRoVnYWoUwjWbsxVeJg2jOTSo=
|
||||||
|
go.mau.fi/util v0.8.1/go.mod h1:T1u/rD2rzidVrBLyaUdPpZiJdP/rsyi+aTzn0D+Q6wc=
|
||||||
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||||
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||||
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
maunium.net/go/mautrix v0.21.1 h1:Z+e448jtlY977iC1kokNJTH5kg2WmDpcQCqn+v9oZOA=
|
||||||
|
maunium.net/go/mautrix v0.21.1/go.mod h1:7F/S6XAdyc/6DW+Q7xyFXRSPb6IjfqMb1OMepQ8C8OE=
|
||||||
|
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||||
|
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||||
|
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||||
@@ -407,7 +407,7 @@ type diagMachine interface {
|
|||||||
OwnIdentity() *id.Device
|
OwnIdentity() *id.Device
|
||||||
ExportCrossSigningKeys() crypto.CrossSigningSeeds
|
ExportCrossSigningKeys() crypto.CrossSigningSeeds
|
||||||
ResolveTrustContext(ctx context.Context, device *id.Device) (id.TrustState, error)
|
ResolveTrustContext(ctx context.Context, device *id.Device) (id.TrustState, error)
|
||||||
IsDeviceTrusted(device *id.Device) bool
|
IsDeviceTrusted(ctx context.Context, device *id.Device) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// logCryptoDiagnostics logs the E2EE state after initialization.
|
// logCryptoDiagnostics logs the E2EE state after initialization.
|
||||||
@@ -512,7 +512,7 @@ func logDeviceTrust(ctx context.Context, machine diagMachine, device *id.Device,
|
|||||||
logger.Info("e2ee diagnostics: own device trust state",
|
logger.Info("e2ee diagnostics: own device trust state",
|
||||||
"device_id", device.DeviceID,
|
"device_id", device.DeviceID,
|
||||||
"trust_state", trust.String(),
|
"trust_state", trust.String(),
|
||||||
"is_trusted", machine.IsDeviceTrusted(device),
|
"is_trusted", machine.IsDeviceTrusted(ctx, device),
|
||||||
)
|
)
|
||||||
|
|
||||||
if trust < id.TrustStateCrossSignedTOFU {
|
if trust < id.TrustStateCrossSignedTOFU {
|
||||||
@@ -533,7 +533,7 @@ func truncateKey(key string) string {
|
|||||||
|
|
||||||
// SetPresence sets the bot's presence status (online, unavailable, offline).
|
// SetPresence sets the bot's presence status (online, unavailable, offline).
|
||||||
func (c *Client) SetPresence(ctx context.Context, status event.Presence) error {
|
func (c *Client) SetPresence(ctx context.Context, status event.Presence) error {
|
||||||
return c.raw.SetPresence(ctx, status)
|
return c.raw.SetPresence(ctx, mautrix.ReqPresence{Presence: status})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raw returns the underlying mautrix.Client for advanced use.
|
// Raw returns the underlying mautrix.Client for advanced use.
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func (l *Listener) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
l.logger.Info("received room invite, joining", "room", evt.RoomID, "inviter", evt.Sender)
|
l.logger.Info("received room invite, joining", "room", evt.RoomID, "inviter", evt.Sender)
|
||||||
if _, err := l.client.raw.JoinRoom(ctx, evt.RoomID.String(), "", nil); err != nil {
|
if _, err := l.client.raw.JoinRoom(ctx, evt.RoomID.String(), nil); err != nil {
|
||||||
l.logger.Error("failed to auto-join room", "room", evt.RoomID, "err", err)
|
l.logger.Error("failed to auto-join room", "room", evt.RoomID, "err", err)
|
||||||
} else {
|
} else {
|
||||||
l.logger.Info("auto-joined room", "room", evt.RoomID)
|
l.logger.Info("auto-joined room", "room", evt.RoomID)
|
||||||
|
|||||||
Reference in New Issue
Block a user