mirror of
https://github.com/domfelipe/mika-agent-assist.git
synced 2026-08-07 04:16:40 +00:00
fix: clean runtime docs and deterministic hydration (#8)
This commit is contained in:
parent
6b3943807c
commit
c626c555db
3 changed files with 40 additions and 40 deletions
44
README.md
44
README.md
|
|
@ -24,8 +24,10 @@ Já configuradas via `.env` (gerado automaticamente pelo Lovable Cloud):
|
||||||
- `PADDLE_API_KEY` / `PADDLE_WEBHOOK_SECRET`
|
- `PADDLE_API_KEY` / `PADDLE_WEBHOOK_SECRET`
|
||||||
- `SUPABASE_SERVICE_ROLE_KEY`
|
- `SUPABASE_SERVICE_ROLE_KEY`
|
||||||
- `RAILWAY_API_TOKEN` — token da Railway Public API, usado por `provision-agent`/`suspend-agent`/`resume-agent` para criar e gerenciar containers Hermes.
|
- `RAILWAY_API_TOKEN` — token da Railway Public API, usado por `provision-agent`/`suspend-agent`/`resume-agent` para criar e gerenciar containers Hermes.
|
||||||
- `OLLAMA_API_KEY` — chave do Ollama Cloud, injetada como env var no container Hermes (imagem custom `ghcr.io/domfelipe/hermes-agent-custom:latest`).
|
- `OLLAMA_API_KEY` — chave do Ollama Cloud, injetada como env var no container Hermes.
|
||||||
- `HERMES_API_SERVER_KEY` — token usado pelo API server interno do Hermes (`API_SERVER_KEY`). Valor atual: `HermesRailwayKey2026SecureToken123456`.
|
- `HERMES_RUNTIME_IMAGE` — opcional; imagem Docker usada no Railway. Default: `ghcr.io/domfelipe/hermes-agent-custom:latest`.
|
||||||
|
- `HERMES_API_SERVER_KEY` — token usado pelo API server interno do Hermes (`API_SERVER_KEY`). Gere um valor forte por ambiente e nunca versione no repo.
|
||||||
|
- `INTERNAL_FUNCTION_SECRET` — segredo server-to-server usado pelo runtime Hermes para chamar Edge Functions internas, como `create-cronjob-from-agent` e `create-skill-from-agent`.
|
||||||
- `TELEGRAM_MANAGER_BOT_TOKEN` — token do bot manager (`@mika_managerbot`) usado para criar bots dos clientes em 1 toque via Bot Management Mode do BotFather.
|
- `TELEGRAM_MANAGER_BOT_TOKEN` — token do bot manager (`@mika_managerbot`) usado para criar bots dos clientes em 1 toque via Bot Management Mode do BotFather.
|
||||||
- `TELEGRAM_MANAGER_BOT_USERNAME` — username do bot manager, padrão `mika_managerbot` (sem `@`).
|
- `TELEGRAM_MANAGER_BOT_USERNAME` — username do bot manager, padrão `mika_managerbot` (sem `@`).
|
||||||
|
|
||||||
|
|
@ -42,18 +44,21 @@ Já configuradas via `.env` (gerado automaticamente pelo Lovable Cloud):
|
||||||
```
|
```
|
||||||
Resposta esperada: `{ "webhook_set": "...", "telegram_response": { "ok": true } }`.
|
Resposta esperada: `{ "webhook_set": "...", "telegram_response": { "ok": true } }`.
|
||||||
|
|
||||||
> **Imagem Docker custom**: o container roda `ghcr.io/domfelipe/hermes-agent-custom:latest`, que já contém o `SOUL.md` embutido. Por isso não passamos mais `HERMES_SOUL_OVERRIDE` via env var, e o Dockerfile define o `CMD` (sem `startCommand` no Railway). Secrets removidos do projeto: `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `HERMES_SOUL_OVERRIDE`.
|
> **Imagem Docker custom**: por padrão o container roda `ghcr.io/domfelipe/hermes-agent-custom:latest`, mas `HERMES_RUNTIME_IMAGE` pode apontar para uma tag de branch/sha durante smoke. A imagem contém um `SOUL.md` base, e o `provision-agent` pode injetar `HERMES_SOUL_OVERRIDE` para aplicar a identidade do agente e as instruções obrigatórias de runtime.
|
||||||
|
|
||||||
|
> **Suspend/Resume via flag, não via stop.** Railway não permite parar containers sem redeploy. As Edge Functions `suspend-agent`/`resume-agent` usam a env var `HERMES_SUSPENDED` + `serviceInstanceRedeploy`:
|
||||||
|
>
|
||||||
|
> - `suspend` → upsert `HERMES_SUSPENDED=true` e redeploy → container entra em `sleep infinity`.
|
||||||
|
> - `resume` → upsert `HERMES_SUSPENDED=""` e redeploy → container sobe normal.
|
||||||
|
>
|
||||||
|
> Isso depende do start command verificar a flag. Novos serviços já são provisionados com o comando correto (`HERMES_START_COMMAND` em `_shared/railway.ts`):
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> /bin/bash -c 'if [ "$HERMES_SUSPENDED" = "true" ]; then echo "Agent suspended" && sleep infinity; fi && if [ -n "$HERMES_SOUL_OVERRIDE" ]; then echo "$HERMES_SOUL_OVERRIDE" > /opt/data/SOUL.md; fi && /opt/hermes/docker/entrypoint.sh gateway run'
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> **Para serviços Railway existentes (provisionados antes desta mudança):** use `admin-backfill-runtime` para reconciliar envs, imagem e start command. Não atualize manualmente secrets no Railway.
|
||||||
|
|
||||||
> **Suspend/Resume via flag, não via stop.** Railway não permite parar containers sem redeploy. As Edge Functions `suspend-agent`/`resume-agent` usam a env var `HERMES_SUSPENDED` + `serviceInstanceRedeploy`:
|
|
||||||
> - `suspend` → upsert `HERMES_SUSPENDED=true` e redeploy → container entra em `sleep infinity`.
|
|
||||||
> - `resume` → upsert `HERMES_SUSPENDED=""` e redeploy → container sobe normal.
|
|
||||||
>
|
|
||||||
> Isso depende do start command verificar a flag. Novos serviços já são provisionados com o comando correto (`HERMES_START_COMMAND` em `_shared/railway.ts`):
|
|
||||||
> ```bash
|
|
||||||
> /bin/bash -c 'if [ "$HERMES_SUSPENDED" = "true" ]; then echo "Agent suspended" && sleep infinity; fi && if [ -n "$HERMES_SOUL_OVERRIDE" ]; then echo "$HERMES_SOUL_OVERRIDE" > /opt/data/SOUL.md; fi && /opt/hermes/docker/entrypoint.sh gateway run'
|
|
||||||
> ```
|
|
||||||
> **Para serviços Railway existentes (provisionados antes desta mudança):** atualize manualmente o Start Command via Railway UI/Agent para o comando acima — caso contrário `suspend-agent` apenas marcará `status='suspended'` no banco mas o container continuará rodando.
|
|
||||||
- `OPENROUTER_API_KEY` — **obrigatório**. Injetado em cada container Hermes provisionado para que o agente possa chamar os modelos `openrouter/google/gemma-4-*-it`. Sem isso o `provision-agent` retorna 500.
|
|
||||||
- `ADMIN_TELEGRAM_BOT_TOKEN` — token do bot de admin (ex: `@mika_test2_bot`) usado pelo `payments-webhook` para enviar notificações de novos clientes, falhas de pagamento e cancelamentos.
|
- `ADMIN_TELEGRAM_BOT_TOKEN` — token do bot de admin (ex: `@mika_test2_bot`) usado pelo `payments-webhook` para enviar notificações de novos clientes, falhas de pagamento e cancelamentos.
|
||||||
- `ADMIN_TELEGRAM_CHAT_ID` — chat ID do admin que recebe as notificações (ex: `179720882`).
|
- `ADMIN_TELEGRAM_CHAT_ID` — chat ID do admin que recebe as notificações (ex: `179720882`).
|
||||||
- (opcional, Fase 5) credenciais SSH para Hermes
|
- (opcional, Fase 5) credenciais SSH para Hermes
|
||||||
|
|
@ -155,13 +160,13 @@ Para cada provider abaixo, crie um OAuth app e configure a **Redirect URI**:
|
||||||
https://smsarmgoirlcedmqvdgc.supabase.co/functions/v1/oauth-callback
|
https://smsarmgoirlcedmqvdgc.supabase.co/functions/v1/oauth-callback
|
||||||
```
|
```
|
||||||
|
|
||||||
| Provider | Console | Scopes mínimos |
|
| Provider | Console | Scopes mínimos |
|
||||||
|----------|---------|----------------|
|
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Google Workspace** | [console.cloud.google.com](https://console.cloud.google.com) → APIs & Services → Credentials → OAuth 2.0 Client ID (Web app) | `openid email profile https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/calendar` |
|
| **Google Workspace** | [console.cloud.google.com](https://console.cloud.google.com) → APIs & Services → Credentials → OAuth 2.0 Client ID (Web app) | `openid email profile https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/calendar` |
|
||||||
| **Microsoft 365** | [Azure Portal](https://portal.azure.com) → App registrations → New registration → Web | `openid email profile offline_access Mail.Read Calendars.ReadWrite` |
|
| **Microsoft 365** | [Azure Portal](https://portal.azure.com) → App registrations → New registration → Web | `openid email profile offline_access Mail.Read Calendars.ReadWrite` |
|
||||||
| **Notion** | [notion.so/my-integrations](https://www.notion.so/my-integrations) → New integration (Public) | (definidos na integração) |
|
| **Notion** | [notion.so/my-integrations](https://www.notion.so/my-integrations) → New integration (Public) | (definidos na integração) |
|
||||||
| **Todoist** | [developer.todoist.com](https://developer.todoist.com/appconsole.html) → App management → Create app | `data:read_write` |
|
| **Todoist** | [developer.todoist.com](https://developer.todoist.com/appconsole.html) → App management → Create app | `data:read_write` |
|
||||||
| **Cal.com** | [app.cal.com/settings/developer](https://app.cal.com/settings/developer/oauth-clients) → New OAuth Client | `READ_BOOKING WRITE_BOOKING READ_PROFILE` |
|
| **Cal.com** | [app.cal.com/settings/developer](https://app.cal.com/settings/developer/oauth-clients) → New OAuth Client | `READ_BOOKING WRITE_BOOKING READ_PROFILE` |
|
||||||
|
|
||||||
#### 2. Adicionar os 10 secrets no Lovable Cloud
|
#### 2. Adicionar os 10 secrets no Lovable Cloud
|
||||||
|
|
||||||
|
|
@ -292,4 +297,3 @@ bun dev # dev server (porta 8080)
|
||||||
bun run build # build de produção
|
bun run build # build de produção
|
||||||
bun run typecheck
|
bun run typecheck
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useMemo } from "react";
|
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { MessageCircle, ExternalLink, CheckCircle2 } from "lucide-react";
|
import { MessageCircle, ExternalLink, CheckCircle2 } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -14,6 +13,11 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMOJIS = ["🎉", "✨", "⭐", "🚀", "🎉", "✨", "⭐", "🚀", "🎉", "✨", "⭐", "🚀"];
|
const EMOJIS = ["🎉", "✨", "⭐", "🚀", "🎉", "✨", "⭐", "🚀", "🎉", "✨", "⭐", "🚀"];
|
||||||
|
const EMOJI_POSITIONS = EMOJIS.map((_, index) => ({
|
||||||
|
x: ((index * 73) % 320) - 160,
|
||||||
|
y: ((index * 47) % 240) - 120,
|
||||||
|
rotate: ((index * 31) % 80) - 40,
|
||||||
|
}));
|
||||||
|
|
||||||
export function StepWaiting({ agentInstanceId, botUsername, connectedAt, onFinish }: Props) {
|
export function StepWaiting({ agentInstanceId, botUsername, connectedAt, onFinish }: Props) {
|
||||||
const { received } = useTelegramFirstMessage({
|
const { received } = useTelegramFirstMessage({
|
||||||
|
|
@ -22,15 +26,7 @@ export function StepWaiting({ agentInstanceId, botUsername, connectedAt, onFinis
|
||||||
enabled: true,
|
enabled: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const positions = useMemo(
|
const positions = EMOJI_POSITIONS;
|
||||||
() =>
|
|
||||||
EMOJIS.map(() => ({
|
|
||||||
x: (Math.random() - 0.5) * 320,
|
|
||||||
y: (Math.random() - 0.5) * 240,
|
|
||||||
rotate: (Math.random() - 0.5) * 80,
|
|
||||||
})),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-6 py-8 max-w-xl mx-auto">
|
<div className="px-6 py-8 max-w-xl mx-auto">
|
||||||
|
|
@ -56,11 +52,7 @@ export function StepWaiting({ agentInstanceId, botUsername, connectedAt, onFinis
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button asChild size="lg" className="mt-6">
|
<Button asChild size="lg" className="mt-6">
|
||||||
<a
|
<a href={`https://t.me/${botUsername}`} target="_blank" rel="noopener noreferrer">
|
||||||
href={`https://t.me/${botUsername}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Abrir meu bot no Telegram <ExternalLink className="ml-2 h-4 w-4" />
|
Abrir meu bot no Telegram <ExternalLink className="ml-2 h-4 w-4" />
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -103,8 +95,8 @@ export function StepWaiting({ agentInstanceId, botUsername, connectedAt, onFinis
|
||||||
</div>
|
</div>
|
||||||
<h2 className="mt-4 text-2xl font-bold tracking-tight">🎉 Mika conectado!</h2>
|
<h2 className="mt-4 text-2xl font-bold tracking-tight">🎉 Mika conectado!</h2>
|
||||||
<p className="mt-2 text-muted-foreground">
|
<p className="mt-2 text-muted-foreground">
|
||||||
Você recebeu a primeira resposta do seu agente. Ele ainda está em modo de teste,
|
Você recebeu a primeira resposta do seu agente. Ele ainda está em modo de teste, mas
|
||||||
mas em breve vai responder de verdade.
|
em breve vai responder de verdade.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Button size="lg" className="mt-8 min-w-56" onClick={onFinish}>
|
<Button size="lg" className="mt-8 min-w-56" onClick={onFinish}>
|
||||||
|
|
|
||||||
|
|
@ -638,10 +638,14 @@ const SidebarMenuSkeleton = React.forwardRef<
|
||||||
showIcon?: boolean;
|
showIcon?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, showIcon = false, ...props }, ref) => {
|
>(({ className, showIcon = false, ...props }, ref) => {
|
||||||
// Random width between 50 to 90%.
|
const id = React.useId();
|
||||||
const width = React.useMemo(() => {
|
const width = React.useMemo(() => {
|
||||||
return `${Math.floor(Math.random() * 40) + 50}%`;
|
let hash = 0;
|
||||||
}, []);
|
for (let i = 0; i < id.length; i += 1) {
|
||||||
|
hash = (hash * 31 + id.charCodeAt(i)) % 40;
|
||||||
|
}
|
||||||
|
return `${hash + 50}%`;
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue